Building a FirefoxOS app with AngularJS
« Friday, June 19, 2015 »

angular browserify firefoxos javascript nodejs

Building an application for FirefoxOS is fairly easy as applications are simply html pages. In this tutorial, we'll build a more advanced type of web app using AngularJS as a base and browserify to compact things together. In our example, we'll make a simple StartStop timer application.

In order to build a simple Timer application, you have to install NodeJS and Firefox.

Create a folder named Project and create a package.json file with the following content.

{
    "name": "StopStart",
    "version": "0.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "npm run build-js && npm run build-style",
        "build-js": "node build.js > static/application.js",
        "build-style": "lessc less/main.less > static/application.css"
    },
    "author": "Loïc Faure-Lacroix",
    "license": "ISC",
    "dependencies": {
        "angular": "^1.4.1",
        "angular-route": "^1.4.1"
    },
    "devDependencies": {
        "browserify": "^10.2.4",
        "less": "^2.5.1",
        "stringify": "^3.1.0"
    }
}

Then install the dependencies with the following command.

npm install

Then you should add an empty index.js file at the root of the project directory and add a new file named build.js with the following content:

var browserify = require('browserify'),                                                                                                     
stringify = require('stringify')

var bundle = browserify()
              .transform(stringify(['.html']))
              .add('./index.js')

bundle.bundle().pipe(process.stdout)

The build.js file is used to build the javascript assets into one application.js file. Building the javascript code in one asset will make it easier to reduce the code size of our application. The code above uses browserify to build the javascript code. It also use the browserify plugin stringify to make it possible to require directly html files. This will be needed to require html templates directly in the javascript code instead of doing any http request. It shouldn't matter much if the web app is installed as a packaged app. If the application will be hosted, then the browser should only have to load 3 files when loading the application (without counting images).
On an other hand, being able to load html files directly in the javascript will also allow us to build modules that could be hosted in different node packages. In other words, it could be possible to publish some common modules with templates and reuse them in different projects.

Now create a directory named static and add the following index.html file.

<html lang="en" ng-app="StartStop">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <meta name="description" content="">
    <meta name="author" content="">

    <title></title>

    <script src="application.js"></script>

    <link rel="stylesheet" href="application.css" />
  </head>

  <body>

    <div ng-view></div>

  </body>

</html>

This will be the main entry point for our application. There is nothing much to say here. It's a basic html template that loads the application called StartStop and has a single div for the angular view. It loads a script named application.js and stylesheet named application.css. If you load the file inside your browser, you shouldn't see anything as application.css and application.js don't exist yet.

Creating the main module:

Add a new folders to the project named modules and less. The modules folder will get required by the main index.js file. This is the one that the builder use to build the packages. The less folder is the folder in which stylesheets will be written in the less language.

When it's done, you should have a project structure that looks like this:

- package.json
- build.js
- index.js
- static/ index.html
- modules/ 
- less/

As we're using nodejs to build our application, we can use CommonJS to require modules. It isn't necessary to use “asynchronuous” solutions such as requirejs. This way, it is often much more easier to write code. On the other side, angularjs already handle lazy loading of dependencies. This way CommonJS is only needed to include any piece of code that might get used in the project.

module.exports = require('./modules')

Create a new folder named “main” in the folder modules and add a new file named index.js in this folder with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Loads angular js
var angular = require('angular')
var angularRoute = require('angular-route')

// Create the main module with the name "StartStop"
var mod = angular.module('StartStop', ['ngRoute'])
var mainTemplate = require('./main.html')

function padTwo(num) {
  /*
  *  Pad numbers two 2 chars with 0 to make numbers like
  *  01,02,03...09,10...59
  */
  num = num + ""
  if (num.length == 1) {
    return "0" + num
  } else {
    return num
  }
}

mod.filter('time', function () {
  /*
  *  Convert time delta to hours:minutes:seconds
  */
  var perMillis = 1000
    , perSecond = 60
    , perHour = 60 * 60

  return function (input) {
    var out = parseInt(input / perMillis)
    var seconds = padTwo(out % 60)
    var minutes = padTwo(parseInt(out / perSecond) % 60)
    var hours = padTwo(parseInt(out / perHour))
    return hours + ":" + minutes + ":" + seconds
  }
})

/*
*  Main controller of the application
*/
mod.controller('MainController',
['$scope', '$interval',
function ($scope, $interval) {

  $scope.start = function () {
    /* 
    *  Set the time to the current time
    */
    $scope.startTime = new Date()
    $scope.stopTime = new Date() 

    // Start an interval that will update the end time
    $scope.interval = $interval(function () {
      $scope.stopTime = new Date() 
    }, 10)
  }

  $scope.stop = function () {
    /*
    *  Cancel the interval to stop any update
    */
    $interval.cancel($scope.interval)
  }

}])

// Configure the routes
mod.config(['$routeProvider',
function ($routeProvider) {
  $routeProvider.
    when('/', {
      templateUrl: 'main/main.html',
      controller: 'MainController'
    }).
    otherwise({
      templateUrl: 'main/main.html',
      controller: 'MainController'
    })
}])

// Setup the template in the template cache
mod.run(['$templateCache', function ($templateCache) {
  $templateCache.put('main/main.html', mainTemplate)
  console.log('Running')
}])

// Export the module
module.exports = mod

In the same folder, create a file namedmain.html. It is the template used by the main modulee. Enter the following content:

1
2
3
4
5
6
7
8
9
<div class="timer">
  <div class="numbers">
    {{(stopTime - startTime) | time}}
  </div>
  <div class="buttons">
    <button class="btn" ng-click="start()">Start</button>
    <button class="btn" ng-click="stop()">Stop</button>
  </div>
</div>

As you can see the template is fairly easy. Now back the to main/index.js file. The template is first loaded with this line:

var mainTemplate = require('./main.html')

It requires, the template relatively of the module itself. The file is loaded as a string and is later loaded in the template cache in the module run section. And then the following section record the template in the template cache.

mod.run(['$templateCache', function ($templateCache) {
  $templateCache.put('main/main.html', mainTemplate)
}])

When the template is available in the $templateCache, it is then possible to use it anywhere in the angular application. In this application, we use it in the route that is defined in config section.

mod.config(['$routeProvider',
function ($routeProvider) {
  $routeProvider.
    when('/', {
      templateUrl: 'main/main.html',
      controller: 'MainController'
    }).
    otherwise({
      templateUrl: 'main/main.html',
      controller: 'MainController'
    })
}])

With that, the MainController will be used in any case with the main template defined in the run section of the module. The main template defines two method. It use the $interval object to create an evented loop.

  • Start: Start the timer
  • Stop: Stop the timer

The MainController:

mod.controller('MainController',
['$scope', '$interval',
function ($scope, $interval) {
  $scope.start = function () {
    $scope.startTime = new Date()
    $scope.stopTime = new Date() 

    $scope.interval = $interval(function () {
      $scope.stopTime = new Date() 
    }, 10)
  }

  $scope.stop = function () {
    $interval.cancel($scope.interval)
  }
}])

Applying Styles

Add the following styles in the file main.less in the less directory.

body, html {
    padding: 0;
    margin: 0;
}

.numbers {
    font-size: 4rem;
    text-align: center;
    margin: 1rem 0;
}

.buttons {
    display: flex;
    position: absolute;
    bottom: 0.5rem;
    left: 0;
    right: 0;

    .btn {
        flex: 1;
        background: #F25252;
        border: none;
        border-radius: 3px;
        margin: 0 0.25rem;
        font-size: 2rem;
        color: white;
    }
}

Compiling

When its done, you should be able to run the command npm run build. It will generate the styles and the javascript in the folder static. It is defined in the package.json file with these three lines:

"build": "npm run build-js && npm run build-style",
"build-js": "node build.js > static/application.js",
"build-style": "lessc less/main.less > static/application.css"

Testing and installing

As soon as you can compile the project, you should be able to open the index.html file in your browser and the application should show two buttons on the bottom to start or stop the timer. In order to install the application as a webapp, you have to add a file that looks like this:

{
  "version": "0.1.0",
  "name": "StartStop",
  "description": "A template with the bare minimum structure",
  "launch_path": "/index.html",
  "icons": {
    "16": "/img/icons/icon16x16.png",
    "48": "/img/icons/icon48x48.png",
    "60": "/img/icons/icon60x60.png",
    "128": "/img/icons/icon128x128.png"
  },
  "developer": {
    "name": "Your name",
    "url": "http://example.com"
  },
  "type": "privileged",
  "permissions": {
  },
  "installs_allowed_from": [
    "*"
  ],
  "locales": {
  },
  "default_locale": "en"
}          

Now, open the firefox WebIDE and browse to the static folder. Open the web app and install it with the play button.

comments powered by Disqus

Copyright © 2015 Loïc Faure-Lacroix