Appcelerator Blog

The Leading Resource for All Things Mobile

Building a custom front-end to ACS using Node.ACS [Part 2]

0 Flares 0 Flares ×

On my previous post we went through the process of setting up a Node.ACS app, and understanding the basics of its built-in MVC framework. It’s now time to integrate ACS back-end services. Our main goal for this mini-project is to use a responsive framework (Bootstrap) to provide a custom, clutter-free front-end for ACS. Think about it this way: You’re using ACS to store locations. Why give your client access to ALL of ACS, just to curate data? ACS’ interface is designed for the developer, not for the end-user. We’ll build a phone and tablet-friendly web user interface what will allow them to easily enter data into the system. So let’s get started!

Structure of our website:

We’ll have a Login screen that will be used to login against the ACS User service. Upon successful login, the user will see a screen displaying a list of places that will be part of the ACS Places service. This screen will provide options for Adding and Deleting places, as well as launching a “Google Map” when a location is clicked. On the top navigation area we’ll have two links, About and Contact with sample content only to illustrate linking and session management. The website will look like this:

App.js

In this file you initialize your website and your session with ACS.

var ACS = require('acs').ACS;
var ACS_KEY='YOUR_ACS_KEY';
var ACS_SECRET='YOUR_ACS_SECRET';

// initialize app
function start(app, express) {
     app.use(express.favicon(__dirname + '/public/images/favicon.ico'));          //set favicon

     // this line is explained @
     // http://docs.appcelerator.com/cloud/latest/#!/guide/node_mvc
     app.use(express.session({ key: 'node.acs', secret: ACS_SECRET }));

     ACS.init(ACS_KEY, ACS_SECRET);
}

Config.json

{
  "routes":
  [
    { "path": "/", "callback": "application#index" },
    { "path": "/error", "callback": "application#error" },
    { "path": "/home", "callback": "home#home" },
    { "path": "/login", "method":"post", "callback": "useraccess#login" },
    { "path": "/logoff", "callback": "useraccess#logoff" },
    { "path": "/about", "callback": "application#about" },
    { "path": "/contact", "callback": "application#contact" },
    { "path": "/api/delete", "callback": "api#deleterec" },
    { "path": "/api/addplace", "method":"post","callback": "api#addrec" }
  ],
  "filters":
  [
    {"path": "/home", "callback": "session_filter#validateSession"},
    {"path": "/about", "callback": "session_filter#validateSession"},
    {"path": "/contact", "callback": "session_filter#validateSession"},
    {"path": "/api/delete", "callback": "session_filter#validateSession"},
    {"path": "/api/add", "callback": "session_filter#validateSession"}
  ],
  "websockets":
  [
    { "event": "", "callback": ""}
  ]
}

This file is in many ways the heart of your app. Here you define how the user is allowed to interact with the website, or how a page should interact with another. The first section, the routes, are a fundamental part of an MVC website. Defining this section is part of your website planning process and provides you a clear idea of how your website will behave.

Path Method File Function
/ GET application index
/error GET application error
/home GET home home
/login POST useraccess login
/logoff GET useraccess logoff
/about GET application about
/contact GET application contact
/api/delete GET api deleterec
/api/addplace POST api addrec
 

I have stored functions in different files, but only for organization purposes. This is more an issue of your personal programming style. You can certainly use a single file for all your functions. This table should be read like: When the user browses to /home, I will execute the home function inside /controllers/home.js and the data will be available via GET.

Now let’s look at the filters section, which has a similar format.

Path File Function
/home session_filter validateSession
/about session_filter validateSession
/contact session_filter validateSession
/api/delete session_filter validateSession
/api/add session_filter validateSession

All entries are using the same function inside the same file. This block is actually providing a way for us to protect certain areas of our website. Instead of having to validate the user session by code on every page, we set a filter on the pages we want to protect. The validateSession function simply verifies the validity of the session. If invalid redirects to error page, if valid, control is returned to the page controller.

function validateSession(req, res, next) {
  if(!req.session.session_id) {
    res.redirect('/error');
  } else {
    next();
  }
}

ACS

Access to ACS methods is consistent with the way you’d used them with Titanium, where you call the method and pass an object and a callback.

For our login method, we grab data from the HTTP POST and then send to ACS.

function login(req,res){
    ACS.Users.login({
        // grab data from http post
        login: req.body.username,
        password: req.body.password
    }, function(data) {
    	if (data.success){
    		// set session data to be used later througout the app
		req.session.session_id=data.meta.session_id;
                req.session.user_id=data.users[0].id;
		req.session.fullname=data.users[0].first_name + ' ' +
data.users[0].last_name;
                // the "home" controller will pick it up
		res.redirect('/home');
    	}else{
    		res.redirect('/error');
    	}
    });
}

When the user successfully logs in, he/she is taken to Home, where a list of Places is displayed. The controller for the “Home” template looks like this:

function home(req, res) {
	ACS.Places.query({
        order: "-created_at" // new on top
    }, function(data) {
        if(data.success) {
            console.log(data);
            res.render('home', {
                                title: 'Beer Places',
                                places:data.places,
                                page:'Home',
                                fullname:req.session.fullname
                                });
        } else {
            res.send('Error occured. Error code: ' + data.error + '.
Message: ' + data.message);
        }
    })
}

The line that reads res.render(‘home’,{}) is sending some data to the View, and one of these pieces is the actual places array that was returned by ACS. This Array is send as a static, client-side array to the view. The templating engine can now loop through the array and write the rows in HTML.

<tbody>
<% places.forEach(function(place){ %>
  <tr id="<%=place.id%>">
    <td><%=place.name%></td>
    <td><%=place.city%></td>
    <td><%=place.phone_number%></td>
    <td><a href="http://maps.google.com/?q=<%=place.name%>@
<%=place.latitude%>,<%=place.longitude%>" target="_blank"
title="Map">Map</a></td>
    <td><a href="javascript:void(0);" onClick="deleterec('<%=place.id%>')"
title="Delete"><div style="text-align: center">
<i class="icon-remove-circle"></i></div></a>
</td>
  </tr>
<%    }) %>
</tbody>

If you look closely, you’ll see that the places array effectively is available to the template. Also notice that you can execute Javascript code by encapsulating it in tags, in this case performing a forEach loop on the Places array, and displaying HTML rows. Just remember, when you want to execute Javascript code you use <%= your_code %> and to echo values you use <% your_variable %>.

An API on top of an API

You could use Node.ACS to provide high-level methods, an API if you like, that interact with the ACS API. An example of this technique is providing a “delete” method that in turn will call the ACS delete place method. Why do this? Well, for one, you don’t want to have your API key available in your Javascript source. With this method, your API key resides inside Node.ACS, that is in the server-side. Another reason could be because you’d like to perform other operations with the data returned by ACS, perhaps even return a new JSON string to your front-end. If you look at our Javascript code for deleting a record, it looks like this:

function deleterec(recid){
      var r=confirm("Delete this record?");
      if (r==true){
          $.ajax({
            url: '/api/delete/?id=' + recid,
            type: 'GET',
            success: function(data) {
              location.href='/home';
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                //alert("Status: " + textStatus);
                //alert("Error: " + errorThrown);
            }
        });
      }
    }

NOTE: In a real-world scenario you’d want to provide a more secure way of deleting your records and not by simply calling a Delete URL with your record id, but those security measures are out of the scope of this post.

Using Node.ACS’ built-in is really fun. The fact of not having to install a web server is a very convenient feature, and being able to take advantage of node.js definitely provides virtually endless possibilities.

The full source code for this example is available at https://github.com/ricardoalcocer/nodeacs_sample_website, simply fork/clone, add your own ACS Key and Secret, and hack away. This is a sample/proof-of-concept so feel free to send me pull request with error corrections or features you’ve added to the project.

0 Flares Twitter 0 Facebook 0 Google+ 0 LinkedIn 0 Email -- 0 Flares ×

4 Comments

  1. Sarat

    Hi Ricardo,
    I would like to know if Appcelerator provides any database service. You had used MongoLab.com to create a database account in response to my previous question (Part-1 post). Obviously external providers means additional concerns about scalability / reliability.

    Thanks
    Sarat

    • Ricardo Alcocer

      Hi Sarat,

      The database service provided by Appcelerator is ACS (http://www.appcelerator.com/cloud/) with its 20 pre-built services, one of which is “Custom Object” (http://docs.appcelerator.com/cloud/latest/#!/api/CustomObjects). This will provide you a scalable and secure database layer for your App, although it can be used by itself with no mobile App.

      Other than that, hosting any other type of database on Node.ACS is not recommended/supported.

      Regards,

      R

  2. Sarat

    Great – I will look into it. Thanks

Comments are closed.

Sign up for updates!

Become a mobile leader. Take the first step to scale mobile innovation throughout your enterprise.
Get in touch
computer and tablet showing Appcelerator software
Start free, grow from there.
0 Flares Twitter 0 Facebook 0 Google+ 0 LinkedIn 0 Email -- 0 Flares ×