Single-Page Web App

Single-page web apps are great for mobile devices. As they are on a single URL, they can provide a smooth user experience and give a native feel to a website.

This tutorial will help you create a single page-web app for the Moovweb blog.

In order to do this, the tutorial will cover a number of Moovweb techniques, including using AngularJS, the HTML to JSON mixer, and adding some Ajax to your site.

Prerequisites: Building a Site tutorials, knowledge of AngularJS

Duration: 1 hour

Outline


Introduction

In order to create a single-page web app, the first thing you need to do is create a static template accessible from a unique path.

Then, you’ll define an AngularJS controller for the page that will inject JSON objects into the static template.

Next, you will convert the regular homepage of the site into JSON objects, rather than relying on your own handwritten JSON.

Finally, you will use Ajax to pull in separate content from different pages.

Creating a Static Page

Firstly, if you want to follow along, start up the Developer Dashboard, and click on “Generate New Project”.

For this lab, please use these settings:

Using Moovweb SDK version 4.7 or earlier?

To create a static page, you must have SDK version 4.5+. If you are using Moovweb 4.7 or earlier, and you used the moov generate command instead of the Developer Dashboard’s generate project feature, you need to make sure that in your Mixer.lock file, you have the following:

core-rewriter (2.1.109)
stdlib (2.0.64)

This is not necessary if you are using Moovweb 5 or later, as the new core mixer included when generating a project using the Developer Dashboard includes the functionality to support static files.

To create a static file, go into config.json in your project. This file is where you set up static paths. After the host map is defined, add another member with the key static_paths. Set its value to be an object with members in the following format:

"/foo": "bar.html"

When the user hits “site.com/foo”, they will be shown the static HTML page bar.html. The file named bar.html should be placed in the root of your assets folder.

For this demo, create a file called index.html in the root of the assets folder and map the “/home” path to index.html in your config.json file.

Your config.json will end up looking like so:

{
  "host_map": [
    "blog.moovdemos.com => 64.13.232.64"
  ],
  "static_paths": {
    "/home": "index.html"
  }
}
Seeing “$.blog.moovdemos.com => blog.moovdemos.com” in your config.json?

If you are using Moovweb 4.7 or earlier, or if you generated your project using the “rehosted” instead of “single domain” option, you will see “$.” at the beginning of your host map. That’s used for mapping a new subdomain to an existing source domain.

{
  "host_map": [
    "$.blog.moovdemos.com => blog.moovdemos.com"
  ],
  "static_paths": {
    "/home": "index.html"
  }
}

A rehosted project is handled differently when it comes to hosting and redirecting source traffic, but either approach will work for purposes of this locally hosted tutorial.

Next, you will add some content to the static file you set up. You’re going to create a template based around AngularJS. In the example here, you want to put the post name and a post snippet.

In the index.html file you created, add some scaffold HTML:

<html ng-app>
  <head></head>
  <body>
    <div ng-controller="PostListCtrl">
      <div class="main-content">
        <ul class="posts">
          <li ng-repeat="post in posts">
            <a ng-href="{{post.url}}">{{post.name}}</a>
            <p>{{post.snippet}}</p>
          </li>
        </ul>
      </div>
    </div>
  </body>
</html>

This is some standard AngularJS boilerplate:

To make a more complete page, you will probably want to add some extras: copy over the header and footer from your main site, add a DOCTYPE and some <meta> elements, for example.

See the final index.html boilerplate
<!DOCTYPE html>
<html ng-app>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- You don't need to add in your stylesheets and JavaScript -
         as this page passes through Moovweb, they're added in automatically. -->
  </head>
  <body>

    <!-- HEADER -->
    <header id="header" class=" clearfix"><!-- header holder --><div class="header-holder">
      <div class="header-frame" data-ur-set="toggler" data-ur-id="1" data-ur-state="enabled">
        <div class="mw_nav_button" data-ur-toggler-component="button" data-ur-state="disabled"></div>
          <!-- logo -->
          <strong class="logo vcard"><a class="fn org url sprites-logo" href="http://m.blog.moovdemos.com">Moovweb Blog</a></strong>
          <!-- header info -->
          <section class="header-info mw_nav_content" data-ur-toggler-component="content" data-ur-state="disabled">
            <div class="mw_inner_nav_container">
              <!-- form search -->
              <form method="get" class="form-search" action="http://m.blog.moovdemos.com">
                <fieldset>
                  <div class="text">
                    <span class="sprites-search mw_search_icon"></span>
                    <input data-max-width="110" type="text" name="s" value="" placeholder="Search here...">
                  </div>
                </fieldset>
              </form>
              <nav id="nav" class="menu-custom-placeholder-header-container">
                <ul>
                  <li id="menu-item-1364" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1364">
                    <a title="Moovweb's Corporate Site" href="http://www.moovweb.com">Visit our company's site</a>
                  </li>
                </ul>
              </nav>
            </div>
          </section>
        </div>
      </div>
    </header>

    <!-- ANGULAR JS BOILERPLATE -->
    <div ng-controller="PostListCtrl">
      <div class="main-content">
        <!--Body content-->
        <ul class="posts">
          <li ng-repeat="post in posts">
            <a ng-href="{{post.url}}">{{post.name}}</a>
            <p>{{post.snippet}}</p>
          </li>
        </ul>
      </div>
    </div>

    <!-- FOOTER -->
    <footer id="footer"><div class="footer-holder">
      <div class="footer-frame">
        <div class="footer-container">
          <div class="footer-content">
            <p class="copyright">&copy; 2013 Moovweb All rights reserved. | For additional information, please visit our <a target="_blank" href="http://www.moovweb.com/">corporate site</a></p>
            </div>
          </div>
        </div>
      </div>
    </footer>
  </body>
</html>

You will focus on AngularJS next!

AngularJS

The first step is pretty simple: including the angular.js script in your project. It’s incredibly simple if you follow our instructions on adding JavaScript to your project.

Create a new file in assets/javascript/main called controller.js. Much as with the angular.js file, this file will automatically get bundled and placed on your page. Inside the file, you can write your controller.

See a sample controller stub.
function PostListCtrl($scope) {
  $scope.posts = [
    {
      "name": "Finding the tools to move the Earth",
      "url": "http://blog.moovdemos.com/2013/06/finding-the-tools-to-move-the-earth/",
      "snippet": "Archimedes asked only for a lever and a place to stand"
    },
    {
      "name": "The Backend, the Scrapers, and Me",
      "url": "http://blog.moovdemos.com/2013/06/the-backend-the-scrapers-and-me/",
      "snippet": "New studies show web sites fending off 95% more exploits than they"
    }
  ];
}

Using the stub, going to the static page (http://blog.moovdemos.com/home) will put the two JSON arrays into the template.

You should create dynamic JSON from the HTML on the homepage of the blog. Set that up in your controller first.

Replace the content above with the following:

function PostListCtrl($scope, $http) {
  $http.get('/').success(function(data) {
    $scope.posts = data;
  });
}

Instead of reading in some pre-written JSON, the controller will now go to the homepage of the current site and pass that in as the JSON for AngularJS to process.

The next step is to turn the homepage HTML of the site into JSON.

Creating JSON

In order to use your AngularJS template, you must create some JSON to act as an input. With Moovweb, you can actually use the HTML from the original site and convert it to JSON using the JSON mixer.

The first step is to make sure the jsonlib mixer is included in your Mixer.lock file. Underneath any existing mixers, add:

jsonlib (2.0.12)

Next, make sure mappings are set up correctly. As you are working on the homepage, this should be the case. Open up the home.ts file.

You are going to use some jsonlib functions to convert HTML to JSON. Check out the annotated code below. NB: all functions used in the following snippet are prefixed with “jsonlib” — this is because you are in a different namespace.

jsonlib.array() {
  $("/html/body//div[@class='post-content']") {
    jsonlib.append() {
      jsonlib.hash() {
        jsonlib.key("name", yank("./h1[@class='entry-title']//a"))
        jsonlib.key("url", fetch("./h1[@class='entry-title']//a/@href"))
        jsonlib.key("snippet", yank("./p[text()][1]"))
      }
    }
  }
}

Once your home.ts file has been set up to grab elements and convert them to JSON, all that is needed is a single function outside of the html() scope that sets up the JSON array.

In main.ts, add the function jsonlib.set_json() underneath the html() scope:

html("UTF-8") {
  @import html.ts
}
jsonlib.set_json()

Now, when you refresh the homepage (http://mlocal.blog.moovdemos.com), you will see a JSON array instead of the homepage.

The controller was already set up to pull this JSON in. Going to your static page (http://mlocal.blog.moovdemos.com/home) will pull in content from the homepage. You will see a list of titles (with appropriate links) and articles. AngularJS is populating the template with all the JSON information created from the homepage.

If you go to “/home”, you will see the list of posts populated with the title, snippet, and URL for the full content of each post.

As an exercise for the reader, you can create a posts.ts file to process these posts the same way we processed the list of blogs, and add a button under each post to load the full content with AJAX into an empty div following each post.

A lot has been covered in this tutorial: