Chapter 10. Displaying Views

The heart and soul of most SPAs is a dynamic frontend. SPAs move a lot of the heavy lifting, related to display logic, onto the browser. Modern browsers have fast and powerful JavaScript engines that can handle a lot more computation than just a few years ago. In fact, Node.js is built on top of the VB engine, which is a standard part of the Chrome browser.

Most importantly, however, the main idea of an SPA is to give the user an experience approaching that of a desktop application. Complete page loads are a thing of the past, replaced by snappy changes in state.

In this chapter, we will build out the heart of our own SPA. This will be a dashboard where users can build giftlists and share them with other users. We will have to build out a couple more routes and data structures on the backend, but we will focus on the frontend. We will build an Express view that will load AngularJS - a JavaScript toolkit designed specifically for rapid creation of SPAs.

We will build AngularJS routes, views, services, and controllers that will implement the core functionality of the SPA. Using the AngularJS plugin, UI-router, we will manage the state of our application. We will also implement services to communicate to the end so that data can flow freely in our application.

In this chapter we will cover the following topics:

  • Developing the initial dashboard view in Express
  • Implementing AngularJS
  • AngularJS routing
  • Using AngularJS $resource to access RESTful endpoints

Setting up our dashboard

Since this is a SPA, we need to set up a single page to contain our application. In our case, we are going to build a user dashboard. That dashboard will allow a user to create giftlists (such as birthday wish lists), choose who they want to share them with, and see lists that have been shared with them. In the next chapter, we're going to build authentication so that individual users will only be able to see their own dashboards, but for now we need to mock things up without authentication a bit.

We'll need a couple of routes, and a view. We're also going to use Bootstrap to style our view a little.

Building the view

We need to create a view for our dashboard. Create a new folder in your views directory called dash. Inside that folder, create a file called dashboard.ejs:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Dashboard for <%= user.firstName %> <%= user.lastName %> </title> 
</head> 
<body> 
<h1><%= user.firstName %> <%= user.lastName %> Dashboard</h1> 
<div> 
    <h2>My Lists</h2> 
</div> 
 
<div> 
    <h2>Lists Shared With Me</h2> 
</div> 
 
</body> 
</html> 

So there's nothing too exciting here yet. We have set up some placeholders, and we are assuming that we'll have a user object to display. We can't see our view yet - for that we need a route which will render the view.

Let's set up the route to display our dashboard. In your routes directory, create a new file called dashboard.js:

var express = require('express'); 
var router = express.Router(); 
 
 
router.get('/', function(req, res, next) { 
    res.send('respond with a resource'); 
}); 
 
router.param('id', function(req, res, next, id) { 
    var db = req.db; 
    var collection = db.get('users'); 
    collection.findOne({ "_id": id }, {}, function(err,User){ 
        if(err){ 
            res.send(err); 
        }else if(User){ 
            req.user = User; 
            next(); 
        } else { 
            res.send(new Error('User not found.')); 
        } 
    }); 
}); 
 
router.get('/:id', function(req, res, next){ 
    res.render('dash/dashboard', {user: req.user}); 
}); 
 
module.exports = router; 

We have done a couple of things here. First, we set up our middleware to respond to routes with an id parameter as we did with our users' routes. Next, we set up a route for displaying our dashboard.

Unless you have the ID for a user memorized, it's going to be hard to test our new view. Let's make it a little easier by modifying the view that lists our users. Open up views/users/show.ejs:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Show Users</title> 
    <link rel='stylesheet' href='/stylesheets/style.css' /> 
</head> 
<body> 
<h1>User List: <%= appName %></h1> 
 
<table> 
    <thead> 
        <tr> 
 
            <th>First Name</th> 
            <th>Last Name</th> 
            <th>Email Address</th> 
            <th>Dashboard</th> 
        </tr> 
    </thead> 
    <tbody> 
    <% users.forEach(function(user, index){ -%> 
        <tr> 
            <td><a href="show/<%= user._id%> "><%= user.firstName %></a></td> 
            <td><%= user.lastName %></td> 
            <td><%= user.email %></td> 
            <td><a href="/dash/<%= user._id %>">View</a></td> 
        </tr> 
    <% }); %> 
    </tbody> 
</table> 
 
 
</body> 
</html> 

We added a new column in our users table with a link to the dashboard for each user. We still can't display our dashboard yet. We have to make a change to our app.js file:

var express = require('express'); 
var path = require('path'); 
var favicon = require('serve-favicon'); 
var logger = require('morgan'); 
var cookieParser = require('cookie-parser'); 
var bodyParser = require('body-parser'); 
var isJSON = require('./utils/json'); 
var routing = require('resource-routing'); 
var controllers = path.resolve('./controllers'); 
 
//Database stuff 
var mongodb = require('mongodb'); 
var monk = require('monk'); 
var db = monk('localhost:27017/giftapp') 
 
var routes = require('./routes/index'); 
var users = require('./routes/users'); 
var dashboard = require('./routes/dashboard'); 
 
var app = express(); 
 
// view engine setup 
app.set('views', path.join(__dirname, 'views')); 
app.set('view engine', 'ejs'); 
 
app.set('x-powered-by', false); 
 
app.locals.appName = "My Gift App"; 
 
// uncomment after placing your favicon in /public 
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 
app.use(logger('dev')); 
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({ extended: false })); 
app.use(cookieParser()); 
app.use(express.static(path.join(__dirname, 'public'))); 
app.use(isJSON); 
 
//Database middleware 
app.use(function(req,res,next){ 
    req.db = db; 
    next(); 
}); 
 
app.use('/', routes); 
app.use('/users', users); 
app.use('/dash', dashboard); 
 
routing.resources(app, controllers, "giftlist"); 
routing.expose_routing_table(app, { at: "/my-routes" }); 
 
// catch 404 and forward to error handler 
app.use(function(req, res, next) { 
  var err = new Error('Not Found'); 
  err.status = 404; 
  next(err); 
}); 
 
// error handlers 
 
// development error handler 
// will print stacktrace 
if (app.get('env') === 'development') { 
  app.use(function(err, req, res, next) { 
    res.status(err.status || 500); 
    res.render('error', { 
      message: err.message, 
      error: err 
    }); 
  }); 
} 
 
// production error handler 
// no stacktraces leaked to user 
app.use(function(err, req, res, next) { 
  res.status(err.status || 500); 
  res.render('error', { 
    message: err.message, 
    error: {} 
  }); 
}); 
 
 
module.exports = app; 

The two key changes here are that we import the dashboard router, we then map any requests to /dash to that router.

Make sure your MongoDB daemon is still running, and restart it if it isn't. Start or restart your server. Navigate to your list of users at http://localhost:3000/users/show and then click on one of the view links in the right of the table:

Building the view

The URL should look something like this: http://localhost:3000/dash/566dd0cb1c09d090fd36ba83. You should see a page that looks like this:

Building the view

Now we have a view template and routing set up to display the page. The next thing we need to do is to build out some data.

Connecting to initial data

Our application is going to allow users to build giftlists and share them with other users. We want to think a little bit about how we want to represent our data. A good data model will serve us, even as we add and change features.

As we have learned, MongoDB is very flexible, and we could just embed documents inside documents. This might work; we could just have each user with an array of lists. The issue with that is that our individual user documents would be highly mutable, and could grow to an enormous size easily. It also doesn't offer a lot of flexibility down the road if we want to do something like having shared lists.

The type of relationship that we want to have for now is a one-to-many relationship. One user can have many lists. The way we'll accomplish this is to store a reference to the user who owns the list on the list itself. Later, if we want to have more than one user own a list, the change would be pretty straightforward.

We want to use our giftapp database, and we are going to be creating a new collection of giftlists. Start up the MongoDB command-line tool in a new terminal window. Note that you'll want to copy the exact ID of one of your users since it will differ from mine:

>use giftapp
switched to db giftapp
> db.giftlist.insert({'name':'Birthday List', 'gifts':[{'name':'ball'},
     {'name':'pony'},{'name':'gift card'}], 'owner_id':
       566dff161c09d090fd36ba85"})
WriteResult({ "nInserted" : 1 })
> db.giftlist.insert({'name':'Christmas  List', 'gifts':[{'name':'TV'},
      {'name':'Corvette'},{'name':'gift card'}], 'owner_id':
       566dff161c09d090fd36ba85"})
WriteResult({ "nInserted" : 1 })

The important part here is the format of the insert statement. Let's break it apart a little bit.

db.giftlist.insert({ 
    'name':'Christmas  List',  
    'gifts':[{'name':'TV'},{'name':'Corvette'},{'name':'gift card'}],  
    'owner_id': 566dff161c09d090fd36ba85") 
    } 
}) 

We insert this object into the giftlist collection, which will be created if it doesn't already exist. The object has a name property and a gifts property. The gifts property is an array of objects, each containing a name property.

We also have an owner_id property. This property is a reference to the user to whom the giftlist belongs. It's just the string of the user's _id. Since MongoDB is a non-relational database, we will just stash this in here to do lookups in the users collection.

We know we're going to be looking things up by the owner, so let's add an index:

> db.giftlist.ensureIndex({'owner_id':1})
{
  "createdCollectionAutomatically" : false,
  "numIndexesBefore" : 1,
  "numIndexesAfter" : 2,
  "ok" : 1
}

Now, let's see what we have got by running a query on the command line:

>db.giftlist.find({'owner':{'$ref':'users','$id':ObjectId('566dff161c09d090fd36ba85')}}).pretty()
{
  "_id" : ObjectId("569bd08d94b6b374a00e8b49"),
  "name" : "Birthday List",
  "gifts" : [
    {
      "name" : "ball"
    },
    {
      "name" : "pony"
    },
    {
      "name" : "gift card"
    }
  ],
  "owner_id" : 566dff161c09d090fd36ba85"
}
{
  "_id" : ObjectId("569bd0d794b6b374a00e8b4a"),
  "name" : "Christmas  List",
  "gifts" : [
    {
      "name" : "TV"
    },
    {
      "name" : "Corvette"
    },
    {
      "name" : "gift card"
    }
  ],
  "owner_id" : "566dff161c09d090fd36ba85"
}

Just what we would expect.

Now, let's modify our dashboard.js route:

var express = require('express'); 
var router = express.Router(); 
 
 
router.get('/', function(req, res, next) { 
    res.send('respond with a resource'); 
}); 
 
router.param('id', function(req, res, next, id)
 {

    var db = req.db;

    var collection = db.get('giftlist');

    collection.find({'owner_id':id}, {}, function(err,giftlists)
{
        if(err){

            res.send(err);

        }else if(giftlists)
{

            req.giftlists = giftlists;

            collection = db.get('users');
            collection.findOne({"_id":id}, function(err, user)
{

                if(err){

                    res.send(err);

                } else
 {

                    req.user = user;

                    next();

                }

            });

        } else {

            res.send(new Error('User not found.'));

        }

    });

}); 
 
router.get('/:id', function(req, res, next){ 
    res.render('dash/dashboard', {user: req.user, giftlists: req.giftlists}); 
}); 
 
module.exports = router; 

We have modified the call to router.param() to search the giftlists collection based on the user id passed in. If we get a giftlist back, we then search the users collection to get the user data.

Yes, there are two calls to the database here. This is a bit of a trade-off in performance for flexibility. Remember that we decided earlier not to embed giftlists in the user document. This trade-off is something you will want to think through in your own applications.

Let's also modify our dashboard.ejs view template:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Dashboard for <%= user.firstName %> <%= user.lastName %> </title> 
</head> 
<body> 
<h1><%= user.firstName %> <%= user.lastName %> Dashboard</h1> 
<div> 
    <h2>My Lists</h2> 
 
    <ul>
    <% giftlists.forEach(function(giftlist, index){ -%>
        <li><%= giftlist.name %></li>
    <% }); %>
    </ul> 
</div> 
 
<div> 
    <h2>Lists Shared With Me</h2> 
</div> 
 
</body> 
</html> 

Now we have an unordered list that renders the name of each of our giftlists. When we start adding AngularJS, we'll link each of these to a state that displays the lists. Navigating to the user dashboard page, you should see this:

Connecting to initial data

We now have a list of our user's giftlists and a placeholder for lists that have been shared with them. In a little bit, when we add AngularJS, we will also add the code for adding, editing, and sharing lists.

Right now, our dashboard is somewhat ugly. Let's fix that a little bit.

Implementing Bootstrap

If you haven't heard of Bootstrap before, it is an extremely popular CSS framework. Plugging in Bootstrap helps frontend developers do things such as layout, painting buttons, and implementing controls without writing a lot of code by hand.

You can get Bootstrap, and see its documentation at https://getbootstrap.com.

Let's sweeten up our dashboard.ejs template a little:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Dashboard for <%= user.firstName %> <%= user.lastName %> </title> 
 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"> 
 
</head> 
<body> 
<nav class="nav navbar-default"> 
    <div class="container-fluid"> 
        <div class="navbar-header"> 
            <a class="navbar-brand" href="#"><%= user.firstName %> <%= user.lastName %> Dashboard</a> 
        </div> 
    </div> 
</nav> 
 
 
<div class="container"> 
    <div class="row"> 
        <div class="col-xs-12 col-md-6"> 
            <h2>My Lists</h2> 
 
            <ul class="list-unstyled"> 
            <% giftlists.forEach(function(giftlist, index){ -%> 
                <li><a class="btn btn-link" href="#" role="button"><%= giftlist.name %></a></li> 
            <% }); %> 
            </ul> 
        </div> 
 
        <div class="col-xs-12 col-md-6"> 
            <h2>Lists Shared With Me</h2> 
        </div> 
    </div> 
</div> 
 
</body> 
</html> 

In the head of our document, you'll see three new lines. The first is a meta tag, which sets the viewport for mobile devices. The next two load Bootstrap and a Bootstrap theme from a CDN.

We then place what we had inside an H1 tag into a number of elements, which will paint a nav bar at the top of the page.

The next section is a div element with a class of container. This is necessary for the Bootstrap layout to work. Bootstrap uses a grid system for a layout with rows and columns. Basically, there are 12 columns of equal width in a row.

Classes such as col-xs-12 tell Bootstrap that, when the view port is extra small (like on a phone), that particular element should take up the entire width of the container. The  col-md-6 class, makes the element half the width (six columns) when the screen is medium width or greater. By combining these classes, we can have a variable layout that makes sense based upon screen width. This is a main component of what's referred to as responsive design.

Looking at our dashboard in full width, we see this:

Implementing Bootstrap

In full size, our dashboard is divided into two equal width columns. You can also see our top nav bar is with Mark Smith Dashboard is rendering. Now, if you drag the side of your browser to make it narrow like a mobile phone screen, you'll see this:

Implementing Bootstrap

Our columns are now stacked one on top of each other, which makes a lot more sense for a mobile form factor. Let's add a button element to add new lists, which we'll actually connect later:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Dashboard for <%= user.firstName %> <%= user.lastName %> </title> 
 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"> 
 
</head> 
<body> 
<nav class="nav navbar-default"> 
    <div class="container-fluid"> 
        <div class="navbar-header"> 
            <a class="navbar-brand" href="#"><%= user.firstName %> <%= user.lastName %> Dashboard</a> 
        </div> 
    </div> 
</nav> 
 
 
<div class="container"> 
    <div class="row"> 
        <div class="col-xs-12 col-md-6"> 
            <h2>My Lists</h2> 
            <button class="btn btn-primary">
                <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
                Add List</button> 
            <ul class="list-unstyled"> 
            <% giftlists.forEach(function(giftlist, index){ -%> 
                <li><a class="btn btn-link" href="#" role="button"><%= giftlist.name %></a></li> 
            <% }); %> 
            </ul> 
        </div> 
 
        <div class="col-xs-12 col-md-6"> 
            <h2>Lists Shared With Me</h2> 
        </div> 
    </div> 
</div> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> 
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" ></script> 
</body> 
</html> 

We added a button with a class of btn-primary. Inside that button we have a span with a couple of glyphicon classes. These classes actually use a font to paint different types of common symbols.

Viewing our page now, we'll see a pretty blue button with a plus sign:

Implementing Bootstrap

We'll be developing further visual components as AngularJS views.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.119.81