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.
node.xs applications contain at least 2 files:
- application.xml: application definition
- application.xs: application implementation
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"/>
- 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");
}
- 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"/>
- 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"/>
- 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'
};
- 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");
}
- 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"/>
- 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');
- 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>
- 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;
}
- 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;
}
- 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:
- 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");
}
- 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/"/>