Tuesday, 3 September 2013

Building an assessment app in two days -- 5th commit.

So, it's late on Wedndesday morning and I probably should get a wriggle on if this is going to be done by tomorrow.

The commits I've been making so far are mostly basic plumbing -- wiring together the different components we're going to use. At some point, I'll publish these as a template project (using giter8), so that future projects can start from a pre-plumbed base.

But, someone has to plumb the first one.

Fifth commit: The basics of the Angular.js app

The 5th commit sets up the basics of our Angular.js app.

Here's a little 404 pic for evidence:

It might look a little odd that a 404 Not Found is telling me that I've got that part of the app wired up correctly, but it's because that error page is part of our Angular.js app -- it's not just plain HTML from the server.

Angular.js app

The Angular.js app is set up in two parts

  • app/assets/javascripts/modules/base.coffee

    This declares the Assessory module, and is the first thing we call

  • app/assets/javascripts/modules/app.coffee

    This configures the app and declares the routes, and will usually be the last thing we call (because it's going to be using a few controllers we haven't created yet)

We use require.js to enforce the order in which all the scripts are run, and also to combine and minify the scripts in production so that the browser doesn't have to make so many requests for javascript files.

The rest of this post is gory details about how we're getting Angular.js to render the error page, and how the header at the top is our first Angular directive.

(It's here because I feel I ought to explain what's going on, but the chances are you'll want to skip it for more interesting posts later.)

That 404 page comes from the client

That friendly little fellow with the map (sorry for my poor drawing) is also being rendered by our Angular.js app.

What happens is this: the / route (the home page) is configured to use a template that I haven't written yet. So, Angular.js gets a 404 error when it requests it.

Our app has configured Angular.js that when it has a routing error (such as this), it should set an error variable in $scope.

# Handles route change errors so that the user doesn't just see a blank page
Assessory.angularApp.controller('ErrorController', ['$scope', ($scope) ->
  $scope.$on("$routeChangeError", (event, current, previous, rejection) ->
    $scope.error = rejection
  )
  $scope.$on("$routeChangeSuccess", () ->
    $scope.error = null
  )
])

$scope is an Angular.js concept -- it contains the data for rendering a fragment of the page. There are usually quite a few scopes, corresponding to different parts that are showing.

The error in this scope (the root scope) causes Angular.js to show the error portion of the main template:

  <div ng-show="error">
    <ng-include src="'client-error-template'"></ng-include>
  </div>

The client error template is pre-embedded in the first HTML we sent the browser, and includes some switches to change what's shown depending on what the error was.

Site header directive

At the top of the image, you can see a site header. That's our first directive. The template is just a fragment of HTML in views.components.directive_siteHeader.scala.html.

If you look in includeDirectives.scala.html you can see how we use Play's templating to insert the template into the HTML that we initially send the browser -- this means that Angular.js never has to make a request to the server to fetch that template, because we've already provided it.

<!-- Site UI components --> 
<script type="text/ng-template" 
        id="directive_siteHeader.html"
>
  @views.html.partials.components.directive_siteHeader()
</script>

(includeDirectives is in turn embedded into the main HTML in main.scala.html.)

The other side of the directive is the Javascript code that declares it to Angular.js

That is in assets.javascripts.components.SiteHeader.coffee and looks like this

define(["modules/base"], (l) -> 

  Assessory.angularApp.directive("siteHeader", () -> 
    {
      restrict: 'E'
      templateUrl: "directive_siteHeader.html"
    }
  )

)

Around the outside, we have a require.js call that makes sure modules/base is loaded first. (That's going to create Assessory.angularapp)

And then we create a directive that tells Angular.js to replace <site-header></site-header> with the content of our site header template.

No comments: