Click here to Skip to main content
15,880,503 members
Articles / Web Development / HTML

Mobile webapps with nodejs & typescript - Part III

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
16 Jan 2015CPOL6 min read 8.6K   39   2  
Mobile webapps with nodejs & typescript - Part III

Introduction

In Part I, we setup basic project and prepared output package for mobile device. This got us some benefits coming from statically typed system provided by Typescript, dependency loading and has given us some automation in building and possible integration with Continuous Integration systems.

In Part II, we layed out infrastructure for easy maintenance of large projects with Jade, Stylus and Q/A tools.
This part will cover bootstraping of Require.js application of data binding for applications, namely MVC and MVVC pattern with Knockout.js.

Bootstraping

In order to effectively use AMD system from Require.js library, we have to expose its components via export keyword instead of providing global variables. Although this is becoming a standard way to distribute JS libraries, there are still libraries that do it in the old way and that might confuse Require.js.

To be able to use such libraries, we can bootstrap our application by using shims. In this part, we will be using Zepto.js (lightweight counterpart to jQuery) and Knockout.js which both exposes global objects - in case of Zepto it's $ like in jQuery and in case of Knockout it's knockout.

We will continue in the project from last time where we add minified zepto.min.js and knockout-2.3.0.js into lib folder. Now we need to tell Require.js where to look for those files; this can be easily achieved by using shim functionality and defining configuration file:

JavaScript
requirejs.config({
    paths: {
        "zepto": "libs/zepto.min",
        "knockout": "libs/knockout-2.3.0"
    },
    shim: {
        zepto: {
            exports: "$"
        }
    }
});

Please note that we are not providing .js extension in paths section.

Since we will be using those libraries across the project, this configuration needs to occur before first module tries to load it up. This brings interesting chicken-and-egg problem since Typescript generates declare statements as first lines in the file so if we try to put this configuration into Typescript file and load, e.g., application.ts which in turn uses Zepto it will fail since at the moment of loading configuration was not yet processed.

There are two ways out of this problem - one is to write config.js in pure JavaScript and do imports in correct places or use mixed approach. In this example, we will use the latter one where we benefit from fact that JavaScript code is fully valid in Typescript files. We will update src/config.ts to the following:

JavaScript
/// <reference path='../extern/require.d.ts'/>

"use strict";

requirejs.config({
    paths: {
        "zepto": "zepto.min",
        "knockout": "knockout-2.3.0"
    },
    shim: {
        zepto: {
            exports: "$"
        }
    }
});

// Stage one
require(['zepto', 'knockout'], (zepto, knockout) => {

         // Stage two
    require(['main'], main => {
     new main.Main().run();
    });
});

As you can see, we are loading dependencies in two stages - reason for that is that Require.js is practicing lazy loading but in this special case we want to have all those libraries pre-loaded before continuing.

Other thing that is worth noticing is that we are referencing .d.ts files in header - without this information Typescript will not know about Require.js objects and functions. We can obtain this file from e.g. DefinitelyTyped site and put:

  • require.d.ts files into /extern/require.d.ts
  • zepto.d.ts into /extern/zepto.d.ts (we will use this one later)
  • knockout.d.ts into /extern/knockout.d.ts (we will use this one later)
  • knockout.amd.d.ts into /extern/knockout.amd.d.ts (we will use this one later - this one is AMD wrapper around standard Knockout)

Those files stands for Definition TypeScript - they contain only definitions of objects not real implementation. You can find more information about this in Typescript Language Reference.

Note: Typescript recently got support for generics and some libraries are already using this concept; previous package.json was pointed to older version of Typescript so it should be updated (line 14) to:

"grunt-typescript": "0.2.1",

Last piece of change we need to do is to tell Grunt to copy all libraries from lib folder; change line 19 in Gruntfile.js to:

JavaScript
{ flatten: true, expand: true, src: ['lib/*.js'], dest: 'dist/www/js/'}

With all infrastructure in place, we can start using all libraries as usual with all benefits of Typescript and lazy-loading. Quick example could be updating src/main.ts to the following:

JavaScript
/// <reference path='../extern/zepto.d.ts'/>

var $ = require("zepto");

export class Main  {
    constructor() {
        console.log("main.init()");

 $(".corpus").click(() => {
     alert("hello world!");
 }));
    }

    run() {        
        console.log("main.run()");
    }
}

Which should display message box with message "hello world" after clicking on text in browser.

Knockout.js

So far, it was more about talking on laying out infrastructure rather than doing the real work. In this chapter, we take a look at how to utilize all of the components we setup so far and we will build simple menu for us.

First, let's introduce Knockout.js library; it's Model-View-ViewModel pattern based binding library which will be familiar to Windows Phone developers.

For those unaware of concept, I recommend to try live examples on Knockout web pages but in our case we will split functionality as follows:

  • Model - simple JSON files with data to be displayed
  • View - HTML page generated from Jade and CSS styles from Stylus
  • ViewModel - Typescript compiled into JavaScript binding view to model and acting on events

Let's start with Model - we create a new file src/menuModel.ts which will contain items that we wish to display:

JavaScript
export interface MenuItem {
    name: string;
    icon: string;
    id: number;
};

export var items: MenuItem[] = [
    { id: 0, name: "Home", icon: "home.png" },
    { id: 1, name: "Items", icon: "items.png" },
    { id: 2, name: "Settings", icon: "settings.png" }
];

File defines interface on data displayed in menu and menu items itself - this information can also come e.g. from AJAX call.

Now to prepare ViewModel which will establish data relation between View and Model. We will update src/main.ts to the following:

JavaScript
/// <reference path='../extern/knockout.amd.d.ts'/>

var $ = require("zepto");

import menuModel = module("./menuModel");
import ko = module("knockout");

export class Main  {
    menuItems: KnockoutObservableArray<menuModel.MenuItem>;

    constructor() {
        console.log("main.init()");  
        this.menuItems = ko.observableArray(menuModel.items);
    }

    run() {        
        console.log("main.run()");
        ko.applyBindings(this);
    }
}

Here we prepare observable array of our menu items for Knockout.js and we will bind this as main object. This is everything we need to do for display purposes - Knockout will handle everything else for us.

Last part is preparing View - since we most probably will be using same menu on multiple pages we will extract all relevant definition into new file views\menu.jade:

ul.menu(data-bind="foreach: menuItems")
    li(data-bind="text: name") 

First line created UL element which will be bound to property named menuItems; since we are using foreach keyword, it is expected that this property will be Array. Everything that is declared within this element will be copied as many times as is count of items in collection. Second line says it should be LI element and text should be bound to property name of every item of collection menuItems.

Since we want to create separate HTML files, not one big one, we need to update Gruntfile.js lines 45-47:

files: [
     { expand: true, ext: ".html", cwd: "views/", src: ['*.jade'], dest: 'dist/www/' }
]

Last bit of functionality is to include menu into our index view - we will update views/index.jade:

html
    head
         link(rel='stylesheet', href='css/index.css')
  script(data-main='js/config', src='js/require.js')
    body
        include menu.jade
            .corpus Hello, world!

That was easy, wasn't it? Unfortunately, this doesn't look too much like menu so we extend this example bit more; let's style it into more conventional way (styles/index.styl):

CSS
body
    font: 100% "Trebuchet MS", sans-serif

.canvas
    margin: 8px

.menu
    list-style-type none
    padding 0px
    margin 0px

.menu li
    width 64px
    height 64px
    display inline-block
    margin 0 2px

.menu .selected
    font-weight bold

So we would like to make menu item bold when it's selected; in order to achieve this, we first need to know which menu item is selected. This should be extracted to menu component (e.g. src/menu.ts) but for the sake of simplicity, we put it into src/main.ts:

JavaScript
/// <reference path='../extern/knockout.amd.d.ts'/>

var $ = require("zepto");

import menuModel = module("./menuModel");
import ko = module("knockout");

export class Main  {
    menuItems: KnockoutObservableArray<menuModel.MenuItem>;
    selectedMenu: KnockoutObservable<number>;

    constructor() {
        console.log("main.init()");  
        this.menuItems = ko.observableArray(menuModel.items);
        this.selectedMenu = ko.observable(menuModel.items[0].id);
    }

    run() {        
        console.log("main.run()");
        ko.applyBindings(this);
    }

    selectMenu(id: number) {     
     this.selectedMenu(id);
    }
}

Please note that in order to update value, we need to perform function call instead. This is often source of issues.

Now we know which menu item is selected and we need to bind this information to UI and propagate these changes back to ViewModel; we will update views/menu.jade to achieve this:

HTML
ul.menu(data-bind="foreach: menuItems")
   li(data-bind="text: name, css: { selected: $root.selectedMenu() == id }, 
   event: { click: $root.selectMenu.bind($root, id) }")

Again, please note that in order to obtain value, we need to perform function call. Object $root in this case contains reference to top-level ViewModel object (in our case instance of class Main). Since Knockout.js processes events on our behalf context of methods called this will be always within Knockout unless we will bind it to correct context (in this case $root).

Compile with grunt and enjoy your menu that was built without doing any event handling or CSS operations!

Back to Part II.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
C# architect, HTML5 and Node.js developer and project manager

Comments and Discussions

 
-- There are no messages in this forum --