sailsCasts

Learning about sails.js one screencast at a time.

Building an Angular Application in Sails: Ep3 - Understanding Asset Delivery Options in Our SignUp Page.

| Comments

Follow me on twitter here.

Subscribe to the sailscasts mailing list here.

The repo for this episode can be found here.

Transcript

In activityOverlord v1.0 the user interface or Views of the app were built primarily on the server before being sent to the client and rendered by the client’s browser. This approach is the V of the MVC architecture and also known as Server Rendering even though the web page is actually being rendered on the client’s browser.

This so called Server Rendering is the server preprocessing one or more templates of markup, usually some combination of HTML, CSS, and Javascript and combining them with data via a Template Engine. All template engines use some form of tags that surround variables that when processed yield a result. Sails uses the EJS template engine by default. For example, this template contains some standard HTML mark-up along with EJS tags.

1
2
3
4
5
6
<!DOCTYPE html>
<html>
  <head>
    <title><%- title %></title>
  </head>
  ...

Between the tags is a variable title. When the Sails server processes this template it will attempt to replace the title variable with a value, hopefully the page title. This is also called String Interpolation, but I only mention that because I’m a geek.

Server Rendered Views

So let’s review this approach. When a client browser makes a request to the server, the router parses the request and determines where to send it. Now, the resulting route typically points to a controller/action that might execute some logic. For example, that logic might include accessing a mongo database that retrieves a stored twitter id and access token. The id and token are then used to request additional details of the user from the Twitter API, all before the server pre-processes a template which contains tags with variables that are replaced by the Twitter details into a View. The View is then sent back to the client and ultimately rendered by the browser.

Now, this traditional approach to web applications has at least two weaknesses. First, its reliance on the server for page creation means the responsiveness of the app is impeded by the constant round trips necessary to update changes to the View from the server. Second, is that the API being tightly coupled to the View makes it less flexible for other potential consumers to use.

A more modern approach to web applications solves both weaknesses by pushing the responsibility for changing the UI to the client as well as decoupling the API to act as an independent provider of endpoints. These endpoints can then be accessed by the browser UI, a native app UI, a mobile UI, or even a smart refrigerator UI.

Now this doesn’t mean that we’ll be abandoning Server Rendered Views entirely. Instead activityOverlordv2.0 will take a hybrid approach using a blend of Server Rendered Views combined with Front-end Framework Components to deliver a UI that makes authentication and SEO manageable. But as usual I’m getting ahead of myself.

So let’s go back to the project and see what Server Rendered Views look like in action. By default, Sails generated three files — homepage.ejs, layout.ejs, and a route to the homepage contained in /config/route.js. So when I make a request in the browser to localhost:1337, the Sails router looks at the request and matches it with the route in the routes file (e.g. route.js). This triggers the View Engine to pre-process layout.ejs with homepage.ejs to produce the View that’s being rendered by my browser. The Layout.ejs file is actually a wrapper around homepage.ejs. And what I mean by that is if we look at both files you can see that layout.ejs contains typical mark-up that we want for every page, things like DOCTYPE, html, and head tags. So we have EJS tags here with a body variable which when processed by the Sails View Engine is replaced with the contents of homepage.ejs . The result is our View.

Despite it’s power, and to make this example crystal clear, I’m going to disable the layout functionality in Views at least for the time being. To do this I’ll go into into /config/views.js and change the layout parameter from layout to false.

The first part of activityOverlord v2.0 consists of the Signup Page and when completed will look something like this. Let’s get started. I’ll first create a new file named startup.ejs and I’ll put a simple header in it <h1> Signup Page</h1>. Next, I’ll remove the existing route to the homepage and replace it with a route to the new The Signup Page:

1
'GET /signup': {view: 'signup'}

Let’s take a look. I’ll start Sails using sails lift and then navigate my browser to localhost:1337/signup. Okay good, here’s our signup page.

Let’s go back to the Signup Page and I’ll start by copying in some boilerplate html. Next, I’ll add our first Angular directives into the body tag — ng-app, ng-controller, and ng-cloak. Let’s refresh the browser and see what happens. Well nothing happens, because we haven’t yet added Angular yet. We could manually add the script tags that reference Angular, however, Sails can do this automatically for us using it’s own tagging system (e.g. <!--SCRIPTS--> tag). You know what, it’s easier just to show you. So I’ll add the tags to signup.ejs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
  <head>
    <title>Sign Up for Activity Overlord (angular)</title>

    <!-- Viewport mobile tag for sensible mobile support -->
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  </head>

  <body ng-app="SignupModule" ng-controller="SignupController" ng-cloak>
    <h1>The Signup Page
    <!--SCRIPTS-->
    <!--SCRIPTS END-->
  </body>
</html>

I’ll restart the Sails server using Sails lift and then go back into the browser but also open up the browser console…okay let’s refresh the page and see what happens. Okay, that new, we get this console message that we’re “connected to Sails”, but where did that come from? If we look at the page source we’ll get a clue. Towards the bottom of the page a link to a file namedsails.io.js has been added to our signup page (e.g. signup.ejs). But where did that link come from? Let’s go into our project in Sublime and navigate to assets/js. In the dependencies foler there’s our sails.io.js file. First, this file deals with web sockets and socket.io, which we’ll be covering in later episodes. The big question now is, how did a link to that file get inserted into signup.ejs? The short answer is that Grunt did it for us. Okay, so what’s Grunt?

Grunt calls itself a JavaScript Task Runner but really Grunt is all about automation. It allows you to create repetive tasks that can be executed automatically. For example, there’s a Grunt task that’s looking for changes in the Sails assets folder. Sails creates a .tmp folder in the root of our project. As we can see here any files in the assets folder are copied into .tmp/public which acts like a static file folder you’d find on any web server. But what about the link to sails.io.js? This is yet another Grunt Task that will place a link to any javascript file found in assets/js that has the corresponding Script tags we just placed in signup.ejs (e.g. <!--SCRIPTS--><!--SCRIPTS END-->).

Of course all of these tasks are happening automatically without us having to be aware of them. However, for those of you who want a little more detail behind the magic, I created a doc that goes into much more specifics of the Grunt/Sails integration than I do in this screencast. The documentation can be found here.

So let’s go ahead and place the main Angular file in the assets/js/dependencies folder and I’ll refresh the browser. Looking at the page source we can see that a link to angular has been automatically placed in our signup page. But when I refreshed the browser, Angular is mad at us because we have some directives in the signup page without any associated javascript files. But not to worry, we can easily fix that. Let’s navigate back to the assets/js folder in Sublime and we’ll create a folder called public. We’ll put all of our Angular files that have to do with those parts of the application that don’t require authentication. Within public we’ll create a folder called signup. And within signup we’ll create two files SignupModule.jsandSignupController.js. Okay, first let's take a look atSignupModule.js` and I’ll add the bare bones Angular code to define our new module.

1
angular.module('SignupModule', []);

Next, I’ll open SignupController.js and add the bare bones Angular code to define our new controller.

1
angular.module('SignupModule').controller('SignupController', function(){});

Let’s go back to the browser and refresh the page. As you can see, we still have an Angular issue. If we look at the page source, we can see what the issue is, SignupController is getting loaded before SignupModule. This is easy to fix and touches on another aspect of Grunt. Most of the configuration for Grunt can be found within the activityOverlord20/tasks folder. For this issue we’re going to look in the root of the tasks folder for a file named pipeline.js. This file contains the configuration for how the SCRIPTS tags are used (e.g. <!--SCRIPTS-->) as well as some other tags we’ll be using shortly.

Next, I’ll open pipeline.js in Sublime. We want to load the SignupModule after dependencies but before any other javascript files. So we’ll put the path to our file here.

1
2
3
4
5
6
7
8
9
10
11
...

  // Dependencies like jQuery, or Angular are brought in here
  'js/dependencies/**/*.js',

  // All of the rest of your client-side js files
  // will be injected here in no particular order.
  'js/public/signup/SignupModule.js',
  'js/**/*.js'

...

Now when I refresh the browser, Angular no longer complains and if look back at the page source we can see that SignUpModule is being loaded before SignupController.

So let’s go back to signup.ejs in Sublime and insert the remainder of the markup for our Signup Page. Let’s start-up Sails using Sails lift and navigate the browser to localhost:1337/signup. Although the page loads without errors, there’s some obvious dependencies that need to be added in order for this page to look right. First I’ll add Bootstrap (e.g. bootstrap.css) to the /assets/sytles folder. Similar to what we did with javascript files in the previous episode, Sails will automatically include links to the CSS in signup.ejs. Taking a look at signup.ejs we can see the Styles tags that were added when I brought in the earlier mark-up as well as a link Grunt automatically created to the bootstrap file I just added.

1
2
3
4
5
6
...

    <!--SCRIPTS-->
    <!--SCRIPTS END-->
  </body>
</html>

Next, I’m going to create a fonts folder and add some fonts we’ll be using later in the interface.

I’m also going to add some .less files into the assets/styles folder that we’ll being using later in the interface as well.

You may have noticed the importer.less file which let’s us control which less files are included the Styles Tag as well as their order. Note that the Grunt task included in Sails will only compile the .less files that are referenced in this file.

I’m also be adding Jesús Rodríguez’s https://github.com/Foxandxss fantastic angular messaging library Angular-toastr. The library contains both a javascript and a css file.

Finally, I’ll add the CompareTo Angular Directive which will help us with comparing the value of form fields. I’ll place it in the /js/dependencies folder.

So let’s see where we are after these dependencies were added. I’ll head back to the browser refresh the page. The console displays an Uncaught reference error that Angular is not defined. Since this is coming from the toastr library my hunch is that we have a loading order error. And sure enough if we look at the page source, the toaster library is being loaded before Angular. I’ll head back to our project and pipeline.js in sublime and add a reference to Angular here (e.g. 'js/dependencies/angular.1.3.js',) that will load Angular first before any of the other depedencies.

Let’s head back to the browser and refresh the page. Great, there’s no longer and error.

Now let’s take a look at some form validation in signup.ejs. There are three components to the form field validation we’re going to perform. So taking a look at the name field. We first use the ng-class directive to create the has-error class if the field name is invalid and dirty. This will insert a red border around the input field. Next we’ll configure the validation parameters of the field itself. In this case we’re requiring that the field have a value as well as have a maxlength of 50 characters. Finally, we’ll set-up the text for our error message here.

1
2
3
4
5
6
<!-- Also, if signup.name.$dirty is true, show the message depending upon the particular properties truthiness (e.g. required
and/or maxlength) -->
<span class="help-block has-error" ng-if="signup.name.$dirty">
  <span ng-show="signup.name.$error.required">Name is required.</span>
  <span ng-show="signup.name.$error.maxlength">The name cannot be more than 50 characters.</span>
</span>

Let’s see this in action. I’ll go back to the browser and type in a name. Now If I remove the name, the required validation is triggered and if I add to many character’s the maxlength validation will be triggered.

Let’s go back to signup.ejs and review the remaining form fields configuration.

So the Title field’s configuration is identical to the name, the title is required and has a max length of 50 characters.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- T I T L E -->

<div class="control-group form-group col-md-12"
  ng-class="{'has-error':signup.title.$invalid &&
                        signup.title.$dirty}">
  <label>Your title</label>
  <input type="text" class="form-control" placeholder="e.g. Genius" name="title" ng-model="signupForm.title" ng-maxlength="50" required>
  <span class="help-block has-error" ng-if="signup.title.$dirty">
    <span ng-show="signup.title.$error.required">Title is required.</span>
    <span ng-show="signup.title.$error.maxlength">The name cannot be more than 50 characters.</span>
  </span>
</div>

The Email field is required and requires a properly formatted email address.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- E M A I L -->

<div class="control-group form-group col-md-12"
ng-class="{'has-error':signup.email.$invalid &&
                      signup.email.$dirty}">
  <label>Your email address</label>
  <input type="email" class="form-control" placeholder="nikola@tesla.com" name="email" ng-model="signupForm.email" required >
  <span class="help-block has-error" ng-if="signup.email.$dirty">
    <span ng-show="signup.email.$error.required">Email address is required.</span>
    <span ng-show="signup.email.$error.email">Not a valid email address.</span>
  </span>
</div>

The Password field is required and must be at least 6 characters. Also notice that we’re using the CompareTo directive to compare the password field with the confirmPassword model.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- P A S S W O R D -->

<div class="control-group form-group col-md-6"
ng-class="{'has-error':signup.password.$invalid &&
                      signup.password.$dirty}">
  <label>Choose a password</label>
  <!-- Added the compareTo directive that compares the passowrds -->
  <input type="password" class="form-control" placeholder="at least 6 characters" name="password" ng-model="signupForm.password" id="password" required ng-minlength="6" compare-to="signupForm.confirmPassword" >
  <span class="help-block has-error" ng-if="signup.password.$dirty">
    <span ng-show="signup.password.$error.required">Password is required.</span>
    <span ng-show="signup.password.$error.minlength">Password must be at least 6 characters.</span>
  </span>
</div>

The Password Confirmation field is required and must match the Password Field.

1
2
3
4
5
6
7
8
9
10
<!-- C O N F I R M  P A S S W O R D -->

<div class="control-group form-group col-md-6">
  <label>Re-enter your password</label>
  <input type="password" class="form-control" placeholder="one more time" name="confirmation" ng-model="signupForm.confirmPassword" required>
  <span class="help-block has-error" ng-if="signup.confirmation.$dirty">
    <span ng-show="signup.password.$error.compareTo">Password must match.</span>
    <span ng-show="signup.confirmation.$error.required">Confirmation password is required.</span>
  </span>
</div>

Finally, we disable the form submission button via the ng-diabled directive until all of the form fields have valid values.

1
2
3
4
5
6
7
8
<!-- C O N F I R M  P A S S W O R D -->

<!-- Disable signup button until the form has no errors -->
<button class="btn btn-primary btn-lg btn-block" type="submit" ng-disabled="signup.$invalid">
  <span ng-show="!signupForm.loading">Create Account</span>
  <span class="overlord-loading-spinner fa fa-spinner" ng-show="signupForm.loading" ></span>
  <span ng-show="signupForm.loading">Preparing your new account...</span>
</button>

Okay let’s go back and take a look at the validations in action. I’m going to refresh the browser.

Believe it or not that rounds out our signup page. In the next episode we’ll start to flesh out our initial API that connects the signup page with an endpoint that creates a user account via a model into a database.

You can find all of the source code for this episode at the activityOverlord20 repo on github.

And If have a moment plese follow me on twitter and be sure to signup for the Sailscasts mailing list so I can finally prove to my wife that there are actually folks watching this stuff. As always thanks for watching.

Comments