Nxs walkthrough - xkp/Doc GitHub Wiki

##Overview

The sample we will show here is available at our site and have the following requirements:

1- Users will be able to register, log in and out of the site
2- Users will be able to upload files for the site to compress
3- User files will be displayed and downloadable when the user logs in
4- Users will provide a location, the service will show them a map of such location

The last requirement is silly, we know, but it will show how to access web services.

File structure

node.xs applications contain at least 2 files:

  • application.xml: application definition
  • application.xs: application implementation

Step 1: The home page

The home page will have no content and will simply redirect the user to the appropriate page, to implement it:

1.1: Declare home page in the definition file:

<page id="home" home="true"/>
Remarks
  • Every page will have an id by which it will be accessed.
  • By marking a page as home (home = "true") the user instructs the server to make it the entry point of your site

1.2: Implementing the page in xs:

on home.render()
{
    var logged = session.get("logged");
    if (!logged)
        return redirect(page = "login");

    return redirect(page = "file_list");
}
Remarks
  • The session object will always be available when needed on pages and services
  • Pages that only redirect do not need content, as in this case

##Step 2: login page

The login page is a plain html file containing a typical form with action="/do_login". To implement this:

2.1 Declaring a static page in the definition file:

<page id="login" src="login.html"/>
Remarks
  • Static pages do not need to implement a render event

##Step 3: Loggin the user in

The system must search in the db for the specific user id and match the password:

3.1: definition

<page id="do_login" post="true"/>
Remarks
  • node.xs pages will use the http's GET method by default unless post = "true" is specified

3.2: defining the data source In order to connect to a database the user must provide a connection, which is a simple object:

property compressor_db = 
{
    hostname: 'localhost', 
    user: 'root', 
    password: 'password', 
    database: 'Compressor'
};
Remarks
  • xs application files modify the application object, this means this is an application property accesible from anywhere.

3.3: Login logic

on do_login.render(user, password)
{
    var user_data;
    sql(connection = compressor_db)
    {
        user_data = select * from Users
                    where UserName = '@user'
    }

    if (!user_data)
        return redirect(page = "login");

    if (user_data.Password && user_data.Password == password)
    {
        session.set("logged",   true);
        session.set("user",     user);
        session.set("user_id",  user_data.UserID);
        session.set("user_map", user_data.Map);

        return redirect(page = "file_list");
    }
    else
        return redirect(page = "login");
}
Remarks
  • use the sql dsl to perform database queries, the results will be stored on the assigning variable (user_data, in this case)
  • Again, sessions are available at any time
  • If the logic fails, just redirect

##Step 4: Registering the user

If the user has not registered we must let him/her do so:

4.1 definition:

<page id="register" src="register.html"/>
<page id="do_register" post="true"/>
Remarks
  • Again, a presentation page referring to a static page which will invoke a dynamic page

4.2: implementation:

on do_register.render(user, password, location)
{
    //pseudo code
    check_if_user_exists(); //4.2.1
    grab_user_map();        //4.2.2
    store_user_info();      //4.2.3 
    
    //the user can now log in
    return redirect(page = "login");
}

4.2.1: the code to check if a user exists is depicted in step 3.3

4.2.2:

    var map_file = 'usermaps/' + user + '_map.png'; 
    var img_data = google_maps.staticmap(center = map_center, size="300x200");
    fs.writeFile('files/' + map_file, img_data, 'binary');
Remarks
  • The code invokes a web service (google_maps.staticmap) which return an image in PNG format, the image is later cached to avoid repeated calls.
  • fs mirrors node.js module with the same name, in general you can use any node object in xs.

4.2.3:

    sql(connection = compressor_db)
    {
        insert into Users (UserName, Password, Map)
        values ('@user', '@password', '@map_file')
    }

##Step 5 Main Page In this page, referred as file_list, the user will be able to upload/download/view his/her files. The upload is done by a simple html form (in file_list.html) that invokes the compression service next section.

5.1: definition: This page is implemented using AutoTemplates:

    <replicator id="files">
        <element id="file_name"/>
        <element id="link"/>
    </replicator>
</page>
Remarks
  • The element tags refer to html elements inside file_list.html, those will be modified at run time (file_list.render)
  • The tags inside of the html element "files" will be replicated as many times as files the user has. This is done with a simple ul/li construct in this case, but it could be actually anything you may think of.

5.2 implementation:

on file_list.render()
{
    //make sure the user is logged in
    var logged = session.get("logged");
    if (!logged)
        return redirect(page = "login");

    //obtain the list of files
    array files;
    var user_id = session.get("user_id");
    sql(connection = compressor_db)
    {
        files = select * from UserFiles
                where UserId = @user_id
    }

    //modify dynamic elements
    user_name.inner_html = session.get("user");
    user_map.src = session.get("user_map");
    
    //replicate items 
    files.data = files;
}
Remarks
  • Note you can use variables on the sql query (where UserId = @user_id)
  • Elements (user_name, user_map) are modified by either "inner_html" or any other html attribute (like "src" for images)
  • Replicators are invoked by means of the "data" property, which must be an array

5.2.1: Replicator rendering As the replication happens, every item must be fitted with dynamic elements. In this case we must set the name of the compressed file and its url for download.

on file_list.files.render(item)
{
    file_name.inner_html = item.File;
    link.href = item.Url;
}
Remarks
  • item here takes the value of every object in the "data" array mentioned a section back.

##Step 6: Compressing files Finally, the main function of the web site. Which is done rather simply:

6.1: definition:

Remarks
  • When uploading files the user must specify where the temporary files will be stored (upload_dir)

6.2 implementation:

on compress.render()
{
    //compress
    var uploaded_file = post.files[0].path;
    shell()
    {
        gzip @uploaded_file
    }

    //move it to the download folder
    var filename = post.files[0].name;
    var url = "compressed/" + filename + ".gz";
    fs.rename(uploaded_file + ".gz", 'files/' + url);

    //save to db
    var user_id = session.get("user_id");
    sql(connection = compressor_db)
    {
        insert into UserFiles (UserId, File, Url)
        values (@user_id, '@filename', '@url')
    }
    
    return redirect(page = "file_list");
}
Remarks
  • pages/services using the POST method will have access to a "post" instance containing uploaded files, fields, etc.
  • shell does what it says it does, runs a shell command using parameters from code

##Step 7: Serving files All hose uploaded files and cached images must be served back to the user, something node.xs does not do by default, you must be sure of specifying a file server

<file_server webroot="files/"/>
⚠️ **GitHub.com Fallback** ⚠️