Mozilla

Apps Articles

Sort by:

View:

  1. Creating a mobile app from a simple HTML site: Part 4

    How to polish your app and prepare it for market

    In previous sections of this step-by-step series (Part 1, Part 2, and Part 3) we’ve created an app that loads multiple school plans from the server.

    What we have so far is functional, but still has a number of issues, including two which are major: no offline mode and a hard-coded configuration. In this closing post, we’ll work on all of these issues.

    If you haven’t built up the examples from the previous parts, use stage7 app and stage7 server as a starting point. To get started, follow the instructions on how to load any stage in this tutorial.

    Refactoring

    First, let’s do some refactoring. We’ll add a User object type to make it easier to choose which user’s plan to display. This will allow us to remove some more code from the app’s main logic flow, making the code cleaner and more modular. This object will load data from the server to create the plan UI. At the moment, rendering is done in app.onDeviceReady and app.renderData methods. Our new onDeviceReady method creates an app.user object and runs the methods needed to get and render plans. Replace the existing onDeviceReady method with the following:

    onDeviceReady: function() {
        app.user = new User('johndoe');
        app.user.getPlans();
    
        app.activateFingerSwipe();
    },

    Next, delete the app.renderData method completely. We will store this functionality inside our new User object instead, as you’ll see below.

    Next, at the beginning of the index.js file (before the definition of Plan), add the following code to define the User object:

    function User(userid) {
        this.id = userid;
        this.plans = [];
    }
    
    User.prototype.getPlans = function() {
        var self = this;
        var request = new XMLHttpRequest({
            mozAnon: true,
            mozSystem: true});
        request.onload = function() {
            var plans = JSON.parse(this.responseText);
            for (var i = 0; i < plans.length; i++) {
                self.plans.push(new Plan(plans[i]));
            }
            self.displayPlans();
        };
        request.open("get", app.getPlansURL + this.id, true);
        request.send();
    };
    
    User.prototype.displayPlans = function() {
        var self = this;
        navigator.globalization.getDateNames(function(dayOfWeek){
            var deck = document.getElementById('plan-group');
            var tabbar = document.getElementById('plan-group-menu');
            for (var i = 0; i < self.plans.length; i++) {
                self.plans[i].createUI(deck, tabbar, dayOfWeek);
            }
        }, function() {}, {type: 'narrow', item: 'days'});
    };

    This contains functionality previously found in renderData and onDeviceReady. After the plan’s JSON is loaded from the server we iterate over the list and create a list of Plan objects in user.plans. We then call user.displayPlans, which creates the required DOM environment and invokes plan.createUI for each element of user.plans.

    You may notice that the above code uses app.getPlansURL to allow the XHR call to find the JSON. Previously the URL was hardcoded into request.open("get", "http://127.0.0.1:8080/plans/johndoe", true);. Since it’s better to keep settings in one place we’ll add this as a parameter of the app object instead.

    Add the following into your code:

    var app = {
        getPlansURL: "http://127.0.0.1:8080/plans/",
        // ....
    }

    Now try preparing your app again with the cordova prepare terminal command, starting your server, and reloading the app in WebIDE (as we’ve done in previous articles). The app should work as before.

    Offline mode

    Let’s turn our attention to making the app work offline.

    There are several technologies available to store data on the device. I’ve decided to use the localStorage API, as the data we need to store is basically just simple strings and doesn’t need any complex structuring. As this is a key/value store, objects need to be stringified (represented as a string). I will use only two functions — setItem() and getItem(). For example, storing a user id would require you to call localStorage.setItem('user_id', user.id);.

    There are two values we definitely need to store in the phone memory — the ids of the current user and the plans. To make the app fully open and allow users to use servers not provided by the app creators we also need to store the server address. We’ll start by storing the plans. The user id and server will be still hardcoded. Let’s add a new method — User.loadPlans — and call that from app.onDeviceReady, instead of calling both user.getPlans() and app.user.displayPlans. We will decide if the plans need to be loaded from the server and then display the plans by calling User.displayPlans() from User.loadPlans.

    First update onDeviceReady again, to this:

    onDeviceReady: function() {
        app.user = new User('johndoe');
        app.user.loadPlans();
        app.activateFingerSwipe();
    },

    Now add in the following definition for the loadPlans method, up near your other User object-defining code:

    User.prototype.loadPlans = function() {
        var plans = localStorage.getItem('plans');
        if (plans === null) {
            return this.getPlans();
        }
        console.log('DEBUG: plans loaded from device');
        this.initiatePlans(plans);
        this.displayPlans();
    };

    The above method calls another new method — initiatePlans. This parses the JSON representation of the plans and initiates Plan objects inside the User.plans array. In the future we would like to be able to reload the plans from the server, but the existing code always adds new plans instead of replacing them.

    Add the following definition just below where you added the loadPlans code:

    User.prototype.initiatePlans = function(plansString) {
        var plans = JSON.parse(plansString);
        for (var i = 0; i < plans.length; i++) {
            this.plans.push(new Plan(plans[i]));
        }
    }

    We also need to store the plans on the client. The best point to do this is right after the plans have been loaded from the server. We will call localStorage.setItem('plans', this.responseText) inside the success response function of getPlans. We must also remember to call the initiatePlans method.

    Update your getPlans definition to the following:

    User.prototype.getPlans = function() {
        var self = this;
        var request = new XMLHttpRequest({
            mozAnon: true,
            mozSystem: true});
        request.onload = function() {
            console.log('DEBUG: plans loaded from server');
            self.initiatePlans(this.responseText);
            localStorage.setItem('plans', this.responseText);
            self.displayPlans();
        };
        request.onerror = function(error) {
            console.log('DEBUG: Failed to get plans from ``' + app.getPlansURL + '``', error);
        };
        request.open("get", app.getPlansURL + this.id, true);
        request.send();
    };

    At this moment, successful loading of the user’s plans happens only once. Since school plans change occasionally (usually twice a year) you should be able to reload the plans from the server any time you want. We can use User.getPlans to return updated plans, but first we need to remove existing plans from the UI, otherwise multiple copies will be displayed.

    Let’s create User.reloadPlans — add the code below your existing User object-defining code:

    User.prototype.reloadPlans = function() {
        // remove UI from plans
        for (var i = 0; i < this.plans.length; i++) {
            this.plans[i].removeUI();
        }
        // clean this.plans
        this.plans = [];
        // clean device storage
        localStorage.setItem('plans', '[]');
        // load plans from server
        this.getPlans();
    };

    There is a new method to add to the Plan object too — Plan.removeUI — which simply calls Element.remove() on a plan’s card and tab. Add this below your previous Plan object-defining code:

    Plan.prototype.removeUI = function() {
        this.card.remove();
        this.tab.remove();
    };

    This is a good time to test if the code has really reloaded. Run cordova prepare again on your code, and reload it in WebIDE.

    We’ve got two console.log statements inside loadPlans and getPlans to let us know if the plans are loaded from device’s storage or the server. To initiate a reload run this code in Console:

    app.user.reloadPlans()

    Note: Brick reports an error at this point, as the link between each tab and card has been lost inside the internal system. Please ignore this error. Once again, our app still works.

    reloadPlans from Console

    Let’s make the reload functional by adding a UI element to reload the plans. In index.html, wrap a div#topbar around the brick-tabbar. First, add a reload button:

    <div id="reload-button"></div>
    <div id="topbar">
        <brick-tabbar id="plan-group-menu">
        </brick-tabbar>
    </div>

    Now we’ll make the button float to the left and use calc to set the size of the div#topbar. This makes Brick’s tabbar calculate the size when layout is changed (portrait or horizontal). In css/index.css, add the following at the bottom of the file:

    #topbar {
    	width: calc(100% - 45px);
    }
    
    #reload-button {
    	float: left;
    	/* height of the tabbar */
    	width: 45px;
    	height: 45px;
    	background-image: url('../img/reload.svg');
    	background-size: 25px 25px;
    	background-repeat: no-repeat;
    	background-position: center center;
    }

    We will download some Gaia icons to use within the application. At this stage, you should save the reload.svg file inside your img folder.

    Last detail: Listen to the touchstart event on the #reload-button and call app.user.reloadPlans. Let’s add an app.assignButtons method:

    assignButtons: function(){
        var reloadButton = document.getElementById('reload-button');
        reloadButton.addEventListener('touchstart', function() {
            app.user.reloadPlans();
        }, false);
    },

    We need to call it from app.deviceReady before or after app.activateFingerSwipe().

    onDeviceReady: function() {
        // ...
        app.activateFingerSwipe();
        app.assignButtons();
    },

    At this point, save your code, prepare your cordova app, and reload it in WebIDE. You should see something like this:

    reload button

    Here you can find current code for app and server.

    Settings page

    Currently all of the identity information (the address of the server and user id) is hardcoded in the app. If the app is to be used with others, we provide functionality for users to set this data without editing the code. We will implement a settings page, using Brick’s flipbox component to change from content to settings and vice versa.

    First we must tell the app to load the component. Let’s change index.html to load the flipbox widget by adding the following line into the HTML <head> section:

    <link rel="import" href="app/bower_components/brick-flipbox/dist/brick-flipbox.html">

    Some changes must happen inside the HTML <body> as well. brick-topbar and brick-deck need to be placed inside switchable sections of the same flipbox, and the reload button also moves to inside the settings. Replace everything you’ve got in your <body> so far with the following (although you need to leave the <script> elements in place at the bottom):

    <brick-flipbox>
        <section id="plans">
            <div id="settings-button"></div>
            <div id="topbar">
                <brick-tabbar id="plan-group-menu">
                </brick-tabbar>
            </div>
            <brick-deck id="plan-group">
            </brick-deck>
        </section>
        <section id="settings">
            <div id="settings-off-button"></div>
            <h2>Settings</h2>
        </section>
    </brick-flipbox>

    The idea is to switch to the “settings” view on pressing a settings-button, and then back to the “plans” view with the settings-off-button.

    Here’s how to wire up this new functionality. First up, inside your app definition code add a toggleSettings() function, just before the definition of assignButtons():

    toggleSettings: function() {
        app.flipbox.toggle();
    },

    Update assignButtons() itself to the following (we are leaving the reloadButton code here, as we’ll need it again soon enough):

    assignButtons: function() {
        app.flipbox = document.querySelector('brick-flipbox');
        var settingsButton = document.getElementById('settings-button');
        var settingsOffButton = document.getElementById('settings-off-button');
        settingsButton.addEventListener('click', app.toggleSettings);
        settingsOffButton.addEventListener('click', app.toggleSettings);
            
        var reloadButton = document.getElementById('reload-button');
        reloadButton.addEventListener('touchstart', function() {
            app.user.reloadPlans();
        }, false);
    },

    Next: we need to retrieve some more icons for the app. Save the settings.svg and back.svg files inside your img folder.

    Let’s update our css/index.css file too, to add the additional icons in and style our new features. Add the following at the bottom of that file:

    #form-settings {
        font-size: 1.4rem;
        padding: 1rem;
    }
    #settings-button, #settings-off-button {
        float: left;
        width: 45px;
        height: 45px;
        background-size: 25px 25px;
        background-repeat: no-repeat;
        background-position: center center;
    }
    #settings-button {
        background-image: url('../img/settings.svg');
    }
    #settings-off-button {
        background-image: url('../img/back.svg');
        border-right: 1px solid #ccc;
        margin-right: 15px;
    }
    brick-flipbox {
        width: 100%;
        height: 100%;
    }

    If you now prepare your cordova app again with cordova prepare, and reload the app inside WebIDE, you should see something like this:

    flipbox working

    Adding a settings form

    To add a form and some data, replace your current <section id="settings"></section> element with the following:

    <section id="settings">
        <div id="settings-off-button"></div>
        <h2>Settings</h2>
        <form id="form-settings">
            <p><label for="input-server">Server</label></p>
            <p><input type="text" id="input-server" placeholder="http://example.com/"/></p>
            <p><label for="input-user" id="label-user">User ID</label></p>
            <p><input type="text" id="input-user" placeholder="johndoe"/></p>
            <p><button id="reload-button">RELOAD ALL</button></p>
        </form>
    </section>

    Making it look good is easy! We’ll use some ready-made CSS available in Firefox OS Building Blocks. Download input_areas.css and buttons.css and save them in your www/css directory.

    Then add the following to your HTML <head>, to apply the new CSS to your markup.

    <link rel="stylesheet" type="text/css" href="css/input_areas.css">
    <link rel="stylesheet" type="text/css" href="css/buttons.css">

    Go into the css/index.css file and delete the #reload-button { ... } rule to make the “RELOAD ALL” button display correctly.

    Run cordova prepare again and reload the app in WebIDE, and you should see something like this:

    settings

    We’ve got the view — now to support it. When the app first installs there will be no setting, so the app will have no idea how to load the data. Instead of showing empty plans we immediately toggle the view to the settings. Update your onDeviceReady function as shown:

    onDeviceReady: function() {
        app.plansServer = localStorage.getItem('plansServer');
        app.userID = localStorage.getItem('userID');
        app.activateFingerSwipe();
        app.assignButtons();
    
        if (app.plansServer && app.userID) {
            app.user = new User(app.userID);
            app.user.loadPlans();
        } else {
            app.toggleSettings();
        }
    },

    The app needs to read the data from our form inputs, so save them into the phone’s storage, and reload after the change. Let’s add this functionality at the end of the app.assignButtons function. First we will stop the form from submitting (otherwise our app would just reload the index.html instead of redrawing the content). Add the following code to the end of the assignButtons function, just before the closing },:

    document.getElementById('form-settings').addEventListener('submit', function(e) {
        e.preventDefault();
    }, false)

    We will read and set the input value for the server. I’ve decided to read the input value on the blur event. It is dispatched when the user touches any other DOM Element on the page. When this happens, the server value is stored in the device’s storage. If app.plansServer exists on app launch, we set the default input value. The same has to happen for the userID, but there is a special step. Either we change the app.user or create a new one if none exists. Add the following just above the previous block of code you added:

    var serverInput = document.getElementById('input-server');
    serverInput.addEventListener('blur', function() {
        app.plansServer = serverInput.value || null;
        localStorage.setItem('plansServer', app.plansServer);
    });
    if (app.plansServer) {
        serverInput.value = app.plansServer;    
    }
    
    var userInput = document.getElementById('input-user');
    userInput.addEventListener('blur', function() {
        app.userID = userInput.value || null;
        if (app.userID) {
            if (app.user) {
                app.user.id = app.userID;
            } else {
                app.user = new User('app.userID');
            }
            localStorage.setItem('userID', app.userID);
        }
    });
    if (app.userID) {
        userInput.value = app.userID;    
    }

    After preparing your app and reloading it in WebIDE, you should now see the settings working as expected. The settings screen appears automatically if no plans are found in the localstorage, allowing you to enter your server and userID details.

    Test it with the following settings:

    • Server: http://127.0.0.1:8080
    • User ID: johndoe

    The only inconvenience is that you need to switch back to the plans view manually once Settings are loaded. In addition, you might get an error if you don’t blur out of both form fields.

    settings are working

    Improving the settings UX

    In fact, switching back to the plans view should be done automatically after plans are successfully loaded. We can implement this by adding a callback to the User.getPlans function, and including it in the call made from User.reloadPlans:

    First, update your getPlans() function definition to the following:

    User.prototype.getPlans = function(callback) {
        var self = this;
        var url = app.plansServer + '/plans/' + this.id;
        var request = new XMLHttpRequest({
            mozAnon: true,
            mozSystem: true});
        request.onload = function() {
            console.log('DEBUG: plans loaded from server');
            self.initiatePlans(this.responseText);
            localStorage.setItem('plans', this.responseText);
            self.displayPlans();
            if (callback) {
                callback();
            }
        };
        request.open("get", url, true);
        request.send();
    };

    Next, in User.reloadPlans add the callback function as an argument of the this.getPlans call:

    this.getPlans(app.toggleSettings);

    Again, try saving, preparing, and reloading your new code. You should now see that the app displays the plans view automatically when correct server and user information is entered and available. (It switches to the Settings view only if there is no error in self.initiatePlans(this.responseText).).

    final

    Where do we go from here

    Congratulations! You’ve reached the end of this 4-part tutorial, and you should now have your own working prototype of a fun school plan application.

    There is still plenty to do to improve the app. Here are some ideas to explore:

    • Writing JSON files is not a comfortable task. It would be better to add new functionality that lets you edit plans on the server. Assigning them to the user on the server opens new possibilities, and this would just require an action to sync the user account on both server and client.
    • Displaying hours would also be nice.
    • It would be cool to implement a reminder alarm for some school activities.

    Try implementing these yourself, or build out other improvements for your own school scheduling app. Let us know how it goes.

  2. Creating a mobile app from a simple HTML site: Part 3

    Adding a server to separate the app from its data

    This is the third part in our series of posts about creating a dynamic mobile app from a simple HTML site. In Part 2 we separated the data from its visual representation, but the data is still contained inside the app. In this article, we take that data, remove it from the app altogether, and serve it from the server instead, to maximise reusability between different users.

    We will go through the steps required to create a suitable server using NodeJS. Then we will make the app work as a client of this server.

    If you have your code handy from last time, then you are ready to go. Make sure you have NodeJS installed. If you don’t have a previous code base setup you can follow the instructions on how to load any stage in this tutorial. Use stage5 as a starting point.

    Refactoring our code

    Let’s quickly look at the code created in part 2.

    Two things happen in the app.onDeviceReady method (see js/index.js) — data is loaded from a JSON file and touch events are listened for on <brick-deck id="plan-group">. After data is loaded, app.renderData parses the JSON string and app.createUI creates a UI for each plan separately.

    onDeviceReady and createUI are currently not very efficient, so let’s update them. Let’s split onDeviceReady by creating a new method called activateFingerSwipe that will contain all the code after the request.send() line and simply call it without arguments. Update your code as follows:

    onDeviceReady: function() {
         var request = new XMLHttpRequest();
         request.onload = app.renderData;
         request.open("get", "app_data/plans.json", true);
         request.send();
    
         app.activateFingerSwipe();
    },
    
    activateFingerSwipe: function() {
        // Switching from one tab to another is done automatically
        // We just need to link it backwards - change menu if slides
        // changed without touching the menu
        app.planGroupMenu = document.getElementById('plan-group-menu');
        
        // Implementing one finger swipe to change deck card
        app.planGroup = document.getElementById('plan-group');
    
        var startX = null;
        var slideThreshold = 100;
    
        // ...
    

    Now, let’s move on to updating createUI. Instead of having the code to create the UI for each individual plan inside createUI, we will create a new user-defined object type called Plan and instantiate it in the loop. Update app.renderData to look like the block below:

    renderData: function() {
        var plans = JSON.parse(this.responseText);
        var deck = document.getElementById('plan-group');
        var tabbar = document.getElementById('plan-group-menu');
        navigator.globalization.getDateNames(function(dayOfWeek){
          for (var i = 0; i < plans.length; i++) {
              var plan = new Plan(plans[i]);
              plan.createUI(deck, tabbar, dayOfWeek);
          }
        }, function() {}, {type: 'narrow', item: 'days'});
    },

    We will define the Plan type as an object having two methods — createUI and selectTab — which are copied from the existing methods and functions. The only changes are related to the data now having an object nature. Instead of plan.week we use this.schedule. this always relates to the current plan’s scope. All changes related to storing parameters in the object are as follows:

    • plan.title -> this.title
    • plan.week -> this.schedule
    • plan.id -> this.id
    • plan.active -> this.active
    • var tab -> this.tab
    • var card -> this.card
    • var table -> this.table
    • selectTab(deck, tab) -> this.selectTab(deck)

    At the top of your index.js file, add the following code:

    function Plan(plan) {
        this.schedule = plan.week;
        this.title = plan.title;
        this.id = plan.id;
        this.active = plan.active;
        this.tab = null;
        this.card = null;
        this.table = null;
    };
    
    Plan.prototype.selectTab = function(deck) {
        var self = this;
        function selectActiveTab() {
            if (!self.tab.targetElement) {
                return window.setTimeout(selectActiveTab, 100);
            }
            deck.showCard(self.tab.targetElement);
        }
        selectActiveTab();
    }
    
    Plan.prototype.createUI = function(deck, tabbar, dayOfWeek) {
        // create card
        this.card = document.createElement('brick-card');
        this.card.setAttribute('id', this.id);
        deck.appendChild(this.card);
    
        //create tab
        this.tab = document.createElement('brick-tabbar-tab');
        this.tab.setAttribute('target', this.id);
        this.tab.appendChild(document.createTextNode(this.title));
        tabbar.appendChild(this.tab);
    
        // link card and tab DOM Elements
        this.card.tabElement = this.tab;
        this.card.addEventListener('show', function() {
            this.tabElement.select();
        });
    
        // create plan table
        this.table = document.createElement('table');
    
        var numberOfDays = this.schedule.length;
        var cleanPlan = [];
        for (var j = 0; j  0) {
                cleanPlan.push(this.schedule[j]);
            }
        }
    
        var daysInHours = [];
        for (j = 0; j < cleanPlan.length; j++) {
            for (var k = 0; k < cleanPlan[j].length; k++) {
                if (!daysInHours[k]) {
                    daysInHours[k] = [];
                }
                daysInHours[k][j] = cleanPlan[j][k];
            }
        }
    
        for (var j = 0; j < daysInHours.length; j++) {
            var tr = this.table.insertRow(-1);
            var td = tr.insertCell(-1);
            td.appendChild(document.createTextNode(j + 1));
            for (var k = 0; k < cleanPlan.length; k++) {
                var td = tr.insertCell(-1);
                if (daysInHours[j][k]) {
                    td.appendChild(document.createTextNode(daysInHours[j][k]));
                }
            }
        }
    
        var thead = this.table.createTHead();
        var tr = thead.insertRow();
        var th_empty = document.createElement('th');
        tr.appendChild(th_empty);
        var weekDayNumber;
         for (var j = 0; j  0) {
                var th = document.createElement('th');
                th.appendChild(document.createTextNode(dayOfWeek.value[weekDayNumber]));
                tr.appendChild(th);
            }
        }
    
        this.card.appendChild(this.table);
    
        if (this.active) {
          this.selectTab(deck);
        }
    }

    You can check your work against the full refactored code source in github.

    You might notice var self = this; and then deck.showCard(self.tab.targetElement); in Plan.prototype.selectTab. this in scope of selectActiveTab would represent a different value then outside of it.

    Read more about the new operator and the this keyword on MDN if you want more information.

    Building the server

    Building a server using NodeJS is fairly simple — http is the only module required, although we will also include fs (as we need to load the file from disk) and sys (to display logs.)

    We will place the server’s code into a separate directory (it’s named stage6-server/ in the related Github project) as it is not part of the app.

    First of all, create a new directory in the same level as school-plan called server. Move the plans.json file out from the school-plan/www/app-data directory to the server directory, then remove school-plan/www/app-data. You could do these actions using the following terminal commands from school-plan‘s parent directory:

    mkdir server
    mv school-plan/www/app_data/plans.json server/
    rm -rf school-plan/www/app_data
    

    Now onto the NodeJS server code. Create a new file called server.js inside the server directory, and fill it with the following content — this simply reads the file from disk:

    var fs = require('fs'),
        sys = require("sys");
    
    fs.readFile(__dirname + "/plans.json", function (err, data) {
        sys.puts(data);
    });

    Run it with the terminal command node server.js — you should see the content of the plans.json file listed in terminal.

    Now we will serve this file using the http module. Modify your NodeJS code like so:

    var fs = require('fs'),
        sys = require("sys"),
        http = require('http');
    
    http.createServer(function (request, response) {
      fs.readFile(__dirname + "/plans.json", function (err, data) {
        response.writeHead(200, {"Content-Type": "application/json"});
        response.end(data);
        sys.puts("accessed");
      });
    }).listen(8080);
    sys.puts("Server Running on http://127.0.0.1:8080");

    First we’ve created the server using the http.createServer method and ordered it to listen on port 8080. On every single request, the plans.json file is read and returned as an HTTP response. Then we simply log accessed to the terminal.

    Test your file again, as before.

    Navigate to http://127.0.0.1:8080 using your browser and you should see the contents of the file inside the browser. (I use the JSON View add-on to make the code look nice too — it’s worth getting.)

    So we’re able to serve the file from our machine — now we just need to read it in our app.

    Note: The server is serving for any address – it works on http://localhost:8080 and also on your local network address (usually http://192.168.xx.xx:8080). This address should be used when testing the app on a device.

    Reading the data from the server

    We need to change the app to read from the server instead of the file distributed inside the app. Let’s change the request.open call (inside deviceReady method) to read from the URL instead of the local file. Replace app_data/plans.json with http://127.0.0.1:8080, so it will look like this:

    request.open("get", "http://127.0.0.1:8080", true);
    

    Now for a test. Run the cordova prepare command in the terminal, and reload the app in WebIDE.

    You will receive an error in the browser console: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8080/. This can be fixed by moving the resource to the same domain or enabling CORS..

    CORS, and cross-origin fixes

    The error is due to a CORS security policy preventing us from loading a script from a different domain.

    There are two ways of loading a script from a different domain. Whitelisting it in Cordova is one of them, but as we’ve got control over the server, we can simply allow cross-site HTTP requests. This is done by setting a header call Access-Control-Allow-Origin in the request. Its value is the domain name(s) that can request content from the server. As our app will be used on phones, all domains should have access. The needed headers are as follows:

    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"
    

    We will add these headers to the existing Content-Type one specified in our server.js script — update the response.writeHead() call to the following:

    response.writeHead(200, {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"});

    Save your NodeJs code, stop (Control + C) the server running in your terminal, and run it again. The application should now be able to read from the server as desired.

    If you are having problems getting the app to work, check your code for the app and server.

    Serving and loading user plans

    Currently we load all plans available in plans.json. As we want to be able to serve plans for any user we need to identify and serve a set.

    We will need to identify a plan. To find a plan in our “database” (JSON file) we will change the plan structure from array to object. For example:

    {
      "somehashtag": {
      "title": "A",
      "id": "a",
      "active": 1,
      "week": [
        // ...
      ]
    }, {
      "otherhashtag": {
      "title": "B",
      "id": "b",
      "week": [
        // ...
      ]
    }

    We also need to find a way to store hashtags. Most of the users will have a different list of them. Should they be stored only on the device or on the server? Considering that many users change their phone fairly often, it’s best to keep the list of hashtags on the server. In production we would have separate user accounts, but for the sake of this tutorial and the simplicity of the server’s code no security is provided.

    We again need to modify the JSON file, to allow for different users and separate plans:

    {
      "users": {
        "userA": {
          "name": "John Doe",
          "plans": ["somehashtag", "otherhashtag"]
        },
        // ...  
      },
      "plans": {
        "somehashtag": {
        "title": "A",
        "id": "a",
        "active": 1,
        "week": [
          // ...
        ]
      }, {
        "otherhashtag": {
        "title": "B",
        "id": "b",
        "week": [
          // ...
        ]
      }
    }

    From now on, data.plans will represent all plans and data.users will represent all of the users.

    At this point, download the updated plans.json file from Github, and replace old with new.

    I defined the URL to access a user’s plans as follows:

    http://{address}/plans/{userid}

    Reading URLs is easy with the url module — now you should add it to the loaded modules:

    var fs = require('fs'),
        sys = require("sys"),
        http = require('http'),
        url = require('url');
    

    Now remove the current response.end(); and sys.puts() calls, replacing them with:

    var args = url.parse(request.url).pathname.split('/');
    var command = args[1];
    var data = JSON.parse(data);
    
    if (command === 'plans') {
        // get user from data.users
        var userid = args[2];
        var user = data.users[userid];
        var plans = [];
        var i, hashtag;
        for (i = 0; i < user.plans.length; i++) {
           hashtag = user.plans[i];
           // copy each user's plan to plans
           plans.push(data.plans[hashtag]);
        }
        response.end(JSON.stringify(plans));
        sys.puts("accessed user - " + userid);
    }

    The only change in the app’s usage now is that we can load the data for different users using the new URL scheme:

    http://127.0.0.1:8080/plans/someuser

    Go to your index.js file and update the following:

    request.open("get", "http://127.0.0.1:8080/", true);

    to

    request.open("get", "http://127.0.0.1:8080/plans/johndoe", true);

    Conclusion

    We have now provided a way to display different plans for different users. However, we are still downloading the plans every time the app is used, which is not much good for an offline experience, plus we currently can’t change the plans displayed unless we edit the XHR call in the code itself. We’ll tackle these problems in our next article.

    For now, check out the final code for the app and server. And look out for Part 4, coming in May!

  3. Easier in-app payments with fxpay

    For developers building web applications on Firefox OS or Firefox Desktop, supporting payments is easy with Mozilla’s fxpay library. In addition to accepting credit cards, Mozilla’s payment system lets users charge purchases directly to their phone bill in many countries—making it ideal for mobile commerce.

    Since our first introduction to fxpay the library has received a lot of bug fixes and new features. Based on developer feedback, we also decided to offer a new interface supporting native promises (shimmed on older browsers) for flexibility and better error handling. This article explains how to retrieve products, process payments, and restore products from receipts.

    If you’ve already set up in-app payments using the mozPay API directly, consider porting to fxpay for the convenience of Mozilla-hosted products and additional features such as desktop payments. At the very least, please make sure your JWT libraries are patched for the latest security vulnerabilities.

    Let’s get started! After installing the fxpay library, you can begin testing it out with some fake products.

    fxpay.configure({fakeProducts: true});
    fxpay.getProducts()
      .then(function(products) {
        products.forEach(function(product) {
          addBuyButtonForProduct(product);
        });
      })
      .catch(function(error) {
        console.error('error getting products: ' + error);
      });
    

    This retrieves some pre-configured fake products that you can play around with. Once you’ve configured real products on the Firefox Marketplace Developer Hub, you can remove this configuration value to work with real products.

    In the example function called above, you could display a buy button per product like this:

    function addBuyButtonForProduct(product) {
      var button = document.createElement('button');
      button.textContent = 'Buy ' + product.name;
      button.addEventListener('click', function () {
     
        fxpay.purchase(product.productId)
          .then(function(purchasedProduct) {
            console.log('product purchased! ', 
                        purchasedProduct);
          })
          .catch(function(error) {
            console.error('error purchasing: ' + error);
          });
     
      });
      document.body.appendChild(button);
    }
    

    The fxpay library does all the payment processing behind the scenes using Mozilla’s web services so when the promise resolves, it’s safe to deliver the product. At this time, fxpay also installs a receipt on the user’s device. When the user returns to your app later on, you’ll want to check for any receipts so you can restore their purchases.

    Here’s a rewrite of the product fetching code to restore purchased products:

    fxpay.getProducts()
      .then(function(products) {
        products.forEach(function(product) {
        
          if (product.hasReceipt()) {
            product.validateReceipt()
              .then(function(restoredProduct) {
                console.log('restored product from receipt:', 
                            restoredProduct);
              })
              .catch(function(error) {
                console.error('error validating receipt: ' + 
                              error);
              });
          } else {
            addBuyButtonForProduct(product);
          }
        
        });
      });
    

    We’re hoping this new interface makes experimenting with in-app payments even easier than before. You never know what kind of business model will work in your app so why not try out some ideas?

    The complete usage guide to fxpay is here on MDN.

  4. Creating a mobile app from a simple HTML site: Part 2

    Or: Making our simple app work for others

    In the first part of this series, which began late last year, we worked through the process of developing a school planner app. At this point (see the final code from Part 1) we’ve got multiple school plans displayed at once, and we’ve got Web, iOS and Android support via Cordova.

    Stage4 Result Screenshot

    Let’s imagine that others have seen the benefits and would like to use our app as well. Creating an app tailored to them should be possible by simply replacing one file (let’s name it www/app_data/plans.json) with their family’s data and making a few other tweaks. This is where we will focus in the beginning of this part.

    Our eventual goal is to create an app to display data (school plans) stored on a server, so every user can view their own data from any computer. In this tutorial, the server part will be minimized to avoid distraction from the main goal — the database of plans is going to be written into a JSON file. If you wish to extend it into SaaS please do so, and let us know in the comments section below.

    What will be built

    A mobile application which will:

    1. Display school plan(s).
    2. Work offline.
    3. Work on many platforms.
    4. Use school plans stored on a server/in a JSON file (our goal for next parts of the series).

    Prerequisites

    If you haven’t worked through the first article then we suggest you do so now. You should at least go through the part 1 prerequisites and make sure you are familiar with them.

    Then follow the steps below, updating your code example from the previous article. If you don’t have this code example, (i.e., if you’ve not gone through the previous article), follow the instructions on how to load any stage in this tutorial. Use stage4 as a starting point.

    You should also make sure you have NodeJS installed.

    Adding an icon

    In Part 1, I omitted the icon, so Cordova added a default one. Let’s make the app look more professional with a custom icon. I’ve downloaded a backpack icon from findicon.com resized it and copied to www/img. In the app directory (school-plan/ — created after running cordova create) — edit the config.xml file and add the following:

    <icon src="www/img/backpack-128.png" />

    If you wish you can add more specific information – for example, more image sizes for each desired platform. You can find more information in Cordova docs. Here is the special definition for Firefox OS:

    <platform name="firefoxos">
    	<icon width="128" height="128" src="www/img/backpack-128.png" />
    	<icon width="64" height="64" src="www/img/backpack-64.png" />
    	<icon width="32" height="32" src="www/img/backpack-32.png" />
    </platform>
    

    Due to an error in the Firefox OS part of Cordova, please also create the www/icon directory using the following command.

    mkdir www/icon

    Modifying the data code

    In this stage we will modify the school plan app to work with data instead of plain HTML (see finished stage 5 code on GitHub.)

    Before we had all school plan’s data hard coded into index.html. Now we will separate the data from its visual representation. To do so, we will remove the old data from www/index.html, leaving only the minimal structure behind.

    Empty both the <brick-tabbar> and <brick-deck> elements in your index.html file, so they are left looking like so:

    <brick-tabbar id="plan-group-menu" selected-index="0">
    </brick-tabbar>
    <brick-deck id="plan-group" selected-index="0">
    </brick-deck>
    

    The most common data structure used in JavaScript projects is JSON; we will therefore add our data in this format. For now it’s OK to distribute the school plan together with the app (later on we will probably serve it from a server instead, for greater flexibility). Our JSON contains an Array of plans. Plan is an Object containing title, id, optional active field and week. The latter is itself an Array of lesson plans.

    Note: use jsHint to check the quality of your code.

    [
        {
          "title": "Name of the plan",
          "id": "id-of-the-plan",
          "active": 1,  // should the plan be active now?
          "week": [
            [
              "first hour of Monday",
              "second hour",
              "third",
              // ... and so on
            [],  // no activities on Tuesday
            ], [
              "",
              "",
              "Wednesday is starting on third hour",
              "fourth"
            ]
        }
    ]
    

    You can find a sample file on Github. Copy this into your project at www/app_data/plans.json.

    Now we need to read this JSON file from the app’s directory.

    As the cards and tabs do not exist at the moment of loading the JavaScript file we should now remove the part where these were linked together from www/js/index.js. Go to this file, find the assignTabs() function, and remove it completely, along with the call to it just below.

    The application will show nothing without the data. Data needs to be loaded just after the app is ready — find the onDeviceReady method and just inside it at the top enter the following lines of code:

    var request = new XMLHttpRequest();
    request.onload = app.renderData;
    request.open("get", "app_data/plans.json", true);
    request.send();
    

    Note: Previously I planned to use Cordova’s FileSystem plugin, but it only made the code more complicated.

    After the request successfully returns our JSON, it is passed to app.renderData (seen below). It parses the text in JSON format to JavaScript and sends data to app.createUI so the necessary DOM elements will be created to form the UI.

    Add the following code block below the onDeviceReady method:

    renderData: function() {
        var plans = JSON.parse(this.responseText);
        app.createUI(plans);
    },
    

    To create the UI we will need weekday names. The best option is to use Cordova’s Globalization plugin. To add this plugin to the application simply run the following command in your terminal, making sure you are inside your root school-plan directory:

    cordova plugin add org.apache.cordova.globalization
    

    Next, add the createUI() method we referenced earlier underneath the renderData() method. It looks like so:

    createUI: function(plans) {
        var deck = document.getElementById('plan-group');
        var tabbar = document.getElementById('plan-group-menu');
        navigator.globalization.getDateNames(function(dayOfWeek) {
            // render UI in the callback
        }, function() {}, {type: 'narrow', item: 'days'});
    },

    Week days are retrieved using the navigator.globalization.getDateNames method. dayOfWeek will hold an Array of week day names, for example (Polish in my case) — ['Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So', 'Nd']. If you’d like to use full day names, just change type: 'narrow' to type: 'wide'.

    Now we need to create DOM elements for individual plans. This time brick-tabbar-tab and brick-card elements are created using JavaScript. Tab refers to the corresponding card using its target parameter. It has the same value as the card’s id in each case. Brick will parse this value and create a tab.targetElement, which will link to the card element. Inside the callback of getDateNames, enter the following code (in place of the “// render UI in the callback” comment seen above):

    for (var i = 0; i < plans.length; i++) {
        var plan = plans[i];
    
        // create card
        var card = document.createElement('brick-card');
        card.setAttribute('id', plan.id);
        deck.appendChild(card);
    
        //create tab
        var tab = document.createElement('brick-tabbar-tab');
        tab.setAttribute('target', plan.id);
        tab.appendChild(document.createTextNode(plan.title));
        tabbar.appendChild(tab);
    
        // link card to tab
        card.tabElement = tab;
        card.addEventListener('show', function() {
            this.tabElement.select();
        });
    
        // create plan table
        var table = document.createElement('table');
    }
    

    Unlike when writing plain HTML, we will create the table body now and then the header. This is because table.insertRow() either creates a new tbody and tr inside or adds a row to any existing HTMLTableSectionElement (thead if already created). We could also call table.tBodies(0) instead but it would complicate the code.

    We don’t want to display days which don’t have any lessons. Let’s copy the plan with only non-empty days to new cleanPlan array. Place this code after the line that creates the <table> element (see above listing):

    var numberOfDays = plan.week.length;
    var cleanPlan = [];
    for (j = 0; j < numberOfDays; j++) {
        if (plan.week[j].length > 0) {
            cleanPlan.push(plan.week[j]);
        }
    }
    

    There is a problem that needs to be solved before we can create the other DOM elements (<tr> and <td>) — we’re representing the plan in a JSON file structured as humans understand it — hours inside days. Unfortunately tables in HTML are created row by row (days inside hours), which means that the array representing the plan needs to be flipped over.

    For example our array stored in JSON will look like this: (dXhY represents day X and hour Y):

    d1h1 d1h2 d1h3 ...
    d2h1 d2h2 d2h3 ...
    d3h1 d3h2 d3h3 ...
    ...
    

    But our <table> structure will look like this:

    d1h1 d2h1 d3h1 ...
    d1h2 d2h2 d3h2 ...
    d1h3 d2h3 d3h3 ...
    ...
    

    Add the following code block right after the last one, to start performing this data transformation for us:

    var daysInHours = [];
    for (j = 0; j < cleanPlan.length; j++) {
        for (var k = 0; k < cleanPlan[j].length; k++) {
            if (!daysInHours[k]) {
                daysInHours[k] = [];
            }
            daysInHours[k][j] = cleanPlan[j][k];
        }
    }
    

    The most important line above is the daysInHours[k][j] = cleanPlan[j][k]; where the indexes are reversed — the kj element of one array becomes the jk element of the other. d3h2 takes the place of d2h3 and vice versa.

    The daysInHours array should now hold the plan prepared for the UI. Now we can iterate over it to render the plan into the HTML table. There is an important thing to note here — table.insertRow needs to use the (otherwise optional) index set to -1, as by default Android inserts the row on top of the table.

    Add the following block right below the previous one:

    for (var j = 0; j < daysInHours.length; j++) {
        var tr = table.insertRow(-1);
        var td = tr.insertCell(-1);
        td.appendChild(document.createTextNode(j + 1));
        for (var k = 0; k < cleanPlan.length; k++) {
            var td = tr.insertCell(-1);
            if (daysInHours[j][k]) {
                td.appendChild(document.createTextNode(daysInHours[j][k]));
            }
        }
    }
    

    We iterate over all the hours (index j). The first <tr> is created at the bottom of the array, then the <td> with the textNode containing that row number. After that we iterate over the days inside the hour (index k) and create more cells — if there is a plan for this hour and day a textNode is created.

    You might be surprised to see this code using cleanPlan.length instead of daysInHours[j].length. This is because we need to create a cell on each day even if no lesson is planned, otherwise we will end up with a broken table structure like this:

    short row issue

    Now we’re ready to create a header with days. Add the following code block right below the previous one:

    var thead = table.createTHead();
    var tr = thead.insertRow();
    var th_empty = document.createElement('th');
    tr.appendChild(th_empty);
    var weekDayNumber;
    for (var j = 0; j < numberOfDays; j++) {
        var weekDayNumber = (j + 1) % 7;
        if (plan.week[j].length > 0) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(dayOfWeek.value[weekDayNumber]));
            tr.appendChild(th);
        }
    }
    

    First, an empty header cell is created for the column, containing the corresponding hour of the day. Then we iterate over the days and create a new <th> with a day name only if there is a plan for the current day (in the same manner as for cleanPlan before.)

    Next we need a line to place the created <table> inside the brick-card element — add the following line below the previous block:

    card.appendChild(table);

    When all tabs are ready, we’ll want the first tab to be selected when the app is loaded. To do this we’ll use the following block — add this just below the previous line:

    if (plan.active) {
        selectTab(deck, tab);
    }
    

    selectTab is a helper function created outside of the app object. It is using polling on activeTab.targetElement to detect if Brick had already linked tab with cards, as seen below. Add this block at the very bottom of your index.js file:

    function selectTab(deck, activeTab) {
        function selectActiveTab() {
            if (!activeTab.targetElement) {
                return window.setTimeout(selectActiveTab, 100);
            }
            deck.showCard(activeTab.targetElement);
        }
        selectActiveTab();
    }
    

    Testing time

    At this moment the app should work exactly the same as on Stage 4 in Part 1. The only difference may be in the localized names of the days in the plan’s header.

    When you want to test your app, type in the following terminal commands (from the root directory of your school-plan app):

    cordova prepare
    cordova serve
    

    This will make the app and its different platforms available to you at localhost:8000.

    If you also want to test the app in Firefox OS, you’ll need to open WebIDE in Firefox, go to Open App > Open Packaged App, and from the file chooser select to open the school-plan/platforms/firefoxos/www directory as a packaged app. From here you can choose to load the app in a simulator or a real Firefox OS device. See the MDN WebIDE page for more details.

    Note: If you’d like to tailor the application for different people who want to use it, at this stage you can do so by replacing the www/app_data/plans.json file with different information for each individual user.

    [EDIT] See the <a href="https://hacks.mozilla.org/2015/04/creating-a-mobile-app-from-a-simple-html-site-part-3/"next part where we added the server.

  5. An analytics primer for developers

    There are three kinds of lies: lies, damned lies, and statistics – Mark Twain

    Deciding what to track (all the things)

    When you are adding analytics to a system you should try to log everything. At some point in the future if you need to pull information out of a system it’s much better to have every piece of information to hand, rather than realising that you need some data that you don’t yet track. Here are some guidelines and suggestion for collecting and analysing information about how people are interacting with your website or app.

    Grouping your stats as a best practice

    Most analytics platforms allow you to tag an event with metadata. This lets you analyse stats against each other and makes it easier to compare elements in a user interaction.

    For example, if you are logging clicks on a menu, you could track each menu item differently e.g.:

    track("Home pressed");
    track("Cart pressed");
    track("Logout pressed");

    Doing this makes it harder to answer questions such as which button is most popular etc. Using metadata you can make most analytics platforms perform calculations like this for you:

    track("Menu pressed","Home button");
    track("Menu pressed","Cart button");
    track("Menu pressed","Logout button");

    The analytics above mean you now have a total of all menu presses, and you can find the most/least popular of the menu items with no extra effort.

    Optimising your funnel

    A conversion funnel is a term of art derived from a consumer marketing model. The metaphor of the funnel describes the flow of steps a user goes through as they engage more deeply with your software. Imagine you want to know how many users clicked log in and then paid at the checkout? If you track events such as “Checkout complete” and “User logged in” you can then ask your analytics platform what percentage of users did both within a certain time frame (a day for instance).

    Imagine the answer comes out to be 10%, this tells you useful information about the behaviour of your users (bear in mind this funnel is not order sensitive, i.e., it does not matter in which order the events happen in login -> cart -> pay or cart -> login -> pay). Thus, you can start to optimise parts of your app and use this value to determine whether or not you are converting more of your users to make a purchase or otherwise engage more deeply.

    Deciding what to measure

    Depending on your business, different stats will have different levels of importance. Here are some common stats of interest to developers of apps or online services:

    Number of sessions
    The total number of sessions on your product (the user opening your product, using it, then closing it = 1 session)
    Session length
    How long each session lasts (can be mode, mean, median)
    Retention
    How many people come back to your product having used it before (there are a variety of metrics such as rolling retention, 30 day retention etc)
    MAU
    Monthly active users: how may users use the app once a month
    DAU
    Daily active users: how may users use the app once a day
    ARPU
    Average revenue per user: how much money you make per person
    ATV
    Average transaction value: how much money you make per sale
    CAC
    Customer acquisition cost: how much it costs to get one extra user (normally specified by the channel for getting them)
    CLV
    Customer lifetime value: total profit made from a user (usually projected)
    Churn
    The number of people who leave your product in a given time (usually given as a percentage of total user base)
    Cycle time
    The the time it takes for one user to refer another

    Choosing an analytics tool or platform

    There are plenty of analytics providers, listed below are some of the best known and most widely used:

    Google Analytics

    Website
    Developer documentation

    Quick event log example:

    ga('send', 'event', 'button', 'click');

    Pros:

    • Free
    • Easy to set up

    Cons:

    • Steep learning curve for using the platform
    • Specialist training can be required to get the most out of the platform

    Single page apps:

    If you are making a single page app/website, you need to keep Google informed that the user is still on your page and hasn’t bounced (gone to your page/app and left without doing anything):

    ga('set' , 'page', location.pathname + location.search + location.hash);
    ga('send', 'pageview');

    Use the above code every time a user navigates to a new section of your app/website to let Google know the user is still browsing your site/app.

    Flurry

    Website
    Developer documentation

    Quick event log example:

    FlurryAgent.logEvent("Button clicked");
    FlurryAgent.logEvent("Button clicked",{more : 'data'});

    Pros:

    • Free
    • Easy to set up

    Cons:

    • Data normally 24 hours behind real time
    • Takes ages to load the data

    Mixpanel

    Website
    Developer documentation

    Quick event log example:

    mixpanel.track("Button clicked");
    mixpanel.track("Button clicked",{more : 'data'});

    Pros:

    • Free trial
    • Easy to set up
    • Real-time data

    Cons:

    • Gets expensive after the free trial
    • If you are tracking a lot of points, the interface can get cluttered

    Speeding up requests

    When you are loading an external JS file you want to do it asynchronously if possible to speed up the page load.

    <script type="text/javascript" async> ... </script>

    The above code will cause the JavaScript to load asynchronously but assumes the user has a browser supporting HTML5.

    //jQuery example
    $.getScript('https://cdn.flurry.com/js/flurry.js', 
    function(){
       ...
    });

    This code will load the JavaScript asynchronously with greater browser support.

    The next problem is that you could try to add an analytic even though the framework does not exist yet, so you need to check to see if the variable framework first:

    if(typeof FlurryAgent != "undefined"){
       ...
    }

    This will prevent errors and will also allow you to easily disable analytics during testing. (You can just stop the script from being loaded – and the variable will never be defined.)

    The problem here is that you might be missing analytics whilst waiting for the script to load. Instead, you can make a queue to store the events and then post them all when the script loads:

    var queue = [];
     
    if(typeof FlurryAgent != "undefined"){
       ...
    }else{
       queue.push(["data",{more : data}]);
    }
     
    ...
     
    //jQuery example
    $.getScript('https://cdn.flurry.com/js/flurry.js', 
    function(){
       ...
     
       for(var i = 0;i < queue.length;i++)
       {
          FlurryAgent.logEvent(queue[i][0],queue[i][1]);
       }
       queue = [];
    });

    Analytics for your Firefox App

    You can use any of the above providers above with Firefox OS, but remember when you paste a script into your code they generally are protocol agnostic: they start //myjs.com/analytics.js and you need to choose either http: or https:https://myjs.com/analytics.js (This is required only if you are making a packaged app.)

    Let us know how it goes.

  6. Open Web Apps feedback: Consolidating our channels

    In August 2014 we announced the opening of a new feedback channel for web apps on UserVoice. It has led to some good discussions and here are a few highlights:

    We would like to sincerely thank those who have provided input and participated in all of the discussions. Unfortunately, after some great initial levels of participation we have since seen usage fall, so at this time we think it is best to discontinue use of openwebapps.uservoice.com. With UserVoice’s handy data exporting services, we’ve made sure we have an archived copy of the ideas posted and interactions that took place on the site.

    Please keep the feedback and feature suggestions coming. It’s essential for us to continue to hear from developers like you. We’d simply like to redirect feedback back to mailing lists/newsgroups (see this list of IRC channels and mailing lists for some Mozilla app-specific channels), specifiction.org/ which aims to be a forum for building web standards, and Bugzilla instances like Mozilla’s.

    Note: The Firefox Developer Tools feedback channel hosted on UserVoice (ffdevtools.uservoice.com) is very active and is most definitely remaining open!

  7. Introducing node-firefox

    NOTE: we presented this project last Sunday at FOSDEM, but not everyone could make it to Brussels, so here’s a post explaining what node-firefox is and how can it help you superturbocharge your Firefox OS app development!

    At Mozilla we’re always looking for ways in which we can make developers’ lives easier. When aspiring app developers told us that it was cumbersome to get started writing Open Web Apps, we worked on turning App Manager into a more beginner friendly environment, which in turn gave way to WebIDE. This tool simplifies many actions that were slow and tedious before, such as creating a new app, downloading and installing simulators or running and debugging apps.

    But there was still a segment of developers that felt left out: power users! They already have their node.js-based build toolchains, with tasks such as asset optimisation, code hinting, or test running. They often also use tools such as Browserify, and perhaps they don’t even write JavaScript, favouring alternatives such as CoffeeScript instead, but all these goodies require you to build the app or website before you push it again to your device or reload the browser.

    Essentially, we were telling these developers to leave their beloved command line (or editor shortcuts!) to go to WebIDE and click a button to deploy the app, and then go back to their editor of choice. And they most unanimously answered: “But we don’t like to click! We like the terminal!”

    How can we make this more efficient?

    People didn’t like this because it implied changing contexts. It is inefficient, we are engineers, and if there one thing that engineers like more than building new things it is probably optimising and streamlining processes.

    Since we already have a build script, the only step that is left in order to get our apps onto the runtime is deploying, and that’s what we are using WebIDE for. So the obvious question would be: can we do whatever WebIDE is doing to deploy, but programmatically?

    Servers and actors

    Every Firefox runtime has something called the remote debugger server. This is not enabled by default, for obvious security reasons, but when enabled, clients can connect to it and take advantage of its various functionalities, such as installing apps, accessing the console, etc. And this is what WebIDE does internally.

    Each of these functionalities is provided by an actor. For example, suppose we want to list the installed apps. We would…

    • first find the webApps actor
    • then run the getAll command
    • and get a list of apps in response

    Another example would be installing a packaged app. The steps would be:

    • first zip the app contents, using whatever library or way you like
    • then get the webApps actor
    • call the uploadPackage command in the webApps actor with the contents of the ZIP file
    • the result of this call is a File actor
    • call the install command in the webApps actor with the returned File actor
    • done!

    Therefore all the magic for installing apps is not in WebIDE—it is in the servers! We can take advantage of this magic programmatically, but building a client from scratch involves establishing TCP connections and parsing packets, which is not what you want to be doing: you want to write apps and push them to devices instead!

    Despair not, as node-firefox will abstract that for you. It is not a monolithic piece of code, but a series of node.js modules, each one of them performing a different task, hosted on its own separate repository and published to the npm registry like good module citizens. You can use as many of them as you need in your scripts or task runners, and thus you can finally build and run your app without ever leaving the command line.

    Show, don’t tell

    But enough of talking and describing; let’s see how to write a script that starts a simulator!

    First install the module in your project, using npm:

    npm install --save node-firefox-start-simulator

    And this would be the script:

    var startSimulator = require('node-firefox-start-simulator');
     
    startSimulator({ version: '2.2' })
      .then(function(simulator) {
        console.log('Listening in port', simulator.port);
      });

    That’s it! With just a few lines of code you are able to programmatically start a version 2.2 simulator. If you don’t care about the version, just don’t pass in any option to startSimulator, and it will start the first simulator it finds:

    startSimulator().then(function(simulator) {
      // your code
    });

    We can also see this in action. Here’s us starting a simulator, installing an app and launching it, all from a node.js script:

    Starting simulator, running app from node.js

    The code for this example is actually the example for the node-firefox-uninstall-app module. Each of the node-firefox modules come with an examples folder so you can get started rather quickly.

    As we mentioned at the beginning, many web developers that move to app development want to keep using their task runners, so we also wrote an example of how to use node-firefox with gulp.

    Let’s run the default-one task. This starts a simulator, deploys an app, and for a bit more of a challenge, also keeps watching for CSS changes. If you edit and save any of the app’s stylesheets, the file watcher will detect the change, and send the new file contents to the runtime, which will replace them on the fly, without having to stop, push and relaunch the whole app. Look at me changing the background colour from austere dark blue to the timeless Paul Rouget pink!

    Starting simulator, launching app with gulp

    Live CSS reloading is really great to build and experiment with UI interfaces. Not having to reload the app and then navigate to the particular layout you want to work in saves lots of time—I wish I’d had that when I was programming Android apps!

    But we can outdo this. The default-all task will do the same as default-one, but for all the simulators installed in your system, so you can see the effect of your CSS changes in all the simulators at the same time:

    Starting all simulators, launching app and live CSS reload with gulp.

    Unfortunately there is a bug in the 2.1 and 2.2 simulators, and those don’t reload the stylesheet changes, but it’s been filed and will be fixed.

    What can we do so far?

    The current set of modules lets you find ports where runtimes are listening, find and start simulators; connect to runtimes; find, install, uninstall and launch apps, and reload stylesheets.

    Philosophy

    You might have noticed a pattern already, but just in case it wasn’t evident enough, we are trying to write deliberately simple modules. Each module should perform only one action, return a Promise and use as few dependencies as possible.

    Small modules are easier to understand, use, and test. Also, most of the future Web APIs are designed to work with Promises, and we want to write code for the future, not for the past. In addition, reducing the number of dependencies also makes it easier for new people to get started on contributing to a module, as there are fewer new unfamiliar elements to understand.

    Finally, since all the modules work the same way, when you know how to use one module you know how to use the rest—the only thing that changes is the parameters, and the result.

    Dream ideas (or: what we cannot do yet)

    There’s a number of things that we’d like to see happen in the future. Some people call them features, but I call them ‘dream ideas.’

    A recurrent one is the WebCLI: a counterpart equivalent to WebIDE, where everything you can do with WebIDE could be done with a command line tool. I keep switching back and forth between “this is a good idea” and “perhaps we don’t need this at all and a library of tasks will be enough”, but people seemed to like the idea, so maybe it’s not that bad!

    Another great feature would be the ability to attach the DevTools debugger to an app that was launched from the command line but that just crashed. Launching apps from the command line is great, but command line debuggers are not that exciting! Why not use the best of both worlds?

    Or maybe it would be neat to control any browser from the command line, interfacing via Valence!

    And finally, there is my favourite dream idea: Firefox OS custom editions. Imagine if we could just write a script that would create an empty Firefox OS slate, pull in our favourite apps and settings, and generate a whole Firefox OS image that we could then flash to devices. And since it is not a binary blob but a script, we could just publish it on its repository, and other people could remix and build their own Firefox OS based editions.

    How do we get there?

    There’s still a long way ahead of us, and lots of areas that need work. Perhaps the most urgent task is to get better multiplatform support. Currently we can only interact with runtimes through the network, but no physical devices. Also, support on platforms other than Mac OS is largely lacking.

    Testing is another important aspect. If we test early, often and profusely we will be able to detect problems such as the CSS bug I stumbled upon when building the gulp demo. We want to have these modules running on several platforms and connecting to other different platforms, including physical devices.

    Of course we need more modules, and more examples! To make sure no two people start writing the same module, we are discussing and proposing new modules in the top project issue tracker. And we’d love to see more examples, or even just better examples that hook existing functionality in other node modules with our code. For example, one could add manifest validation via the firefox-app-validator-manifest module.

    And, as always, we need you. We are not you, so we cannot know what you need or what thoughts cross your mind. And we certainly cannot use software the same way you use it either. We need your input and your contributions!

    We’re looking forward to seeing what you create with node-firefox. File issues, or talk to us on irc if you have questions. We hang out mostly in the #apps and #devtools channels in irc.mozilla.org.

    Thanks

    It would be dishonest not to thank Nicola Greco, whom I mentored last summer when he was interning at Mozilla. He came up with the initial idea of building individual node modules that would help you develop Firefox OS apps. Go check out his final intern presentation, as it’s really entertaining and illustrative!

    Many thanks to all the (infinitely patient) DevToolers Ryan Stinnet, Alexandre Poirot, Jeff Griffiths and Dave Camp, who helped us find our way around remote servers and actors and whatnot, and huge thanks to Heather Arthur who wrote firefox-client and made writing node-firefox way more pleasant than it would have been otherwise.

  8. Creating a mobile app from a simple HTML site

    This article is a simple tutorial designed to teach you some fundamental skills for creating cross platform web applications. You will build a sample School Plan app, which will provide a dynamic “app-like” experience across many different platforms and work offline. It will use Apache Cordova and Mozilla’s Brick web components.

    The story behind the app, written by Piotr

    I’ve got two kids and I’m always forgetting their school plan, as are they. Certainly I could copy the HTML to JSFiddle and load the plan as a Firefox app. Unfortunately this would not load offline, and currently would not work on iOS. Instead I would like to create an app that could be used by everyone in our family, regardless of the device they choose to use.

    We will build

    A mobile application which will:

    1. Display school plan(s)
    2. Work offline
    3. Work on many platforms

    Prerequisite knowledge

    • You should understand the basics of HTML, CSS and JavaScript before getting started.
    • Please also read the instructions on how to load any stage in this tutorial.
    • The Cordova documentation would also be a good thing to read, although we’ll explain the bits you need to know below.
    • You could also read up on Mozilla Brick components to find out what they do.

    Preparation

    Before building up the sample app, you need to prepare your environment.

    Installing Cordova

    We’ve decided to use Apache Cordova for this project as it’s currently the best free tool for delivering HTML apps to many different platforms. You can build up your app using web technologies and then get Cordova to automatically port the app over to the different native platforms. Let’s get it installed first.

    1. First install NodeJS: Cordova is a NodeJS package.
    2. Next, install Cordova globally using the npm package manager:
      npm install -g cordova

    Note: On Linux or OS X, you may need to have root access.

    Installing the latest Firefox

    If you haven’t updated Firefox for a while, you should install the latest version to make sure you have all the tools you need.

    Installing Brick

    Mozilla Brick is a tool built for app developers. It’s a set of ready-to-use web components that allow you to build up and use common UI components very quickly.

    1. To install Brick we will need to use the Bower package manager. Install this, again using npm:
      npm install -g bower
    2. You can install Brick for your current project using
      bower install mozbrick/brick

      but don’t do this right now — you need to put this inside your project, not just anywhere.

    Getting some sample HTML

    Now you should find some sample HTML to use in the project — copy your own children’s online school plans for this purpose, or use our sample if you don’t have any but still want to follow along. Save your markup in a safe place for now.

    Stage 1: Setting up the basic HTML project

    In this part of the tutorial we will set up the basic project, and display the school plans in plain HTML. See the stage 1 code on Github if you want to see what the code should look like at the end of this section.

    1. Start by setting up a plain Cordova project. On your command line, go to the directory in which you want to create your app project, and enter the following command:
      cordova create school-plan com.example.schoolplan SchoolPlan

      This will create a school-plan directory containing some files.

    2. Inside school-plan, open www/index.html in your text editor and remove everything from inside the <body> element.
    3. Copy the school plan HTML you saved earlier into separate elements. This can be structured however you want, but we’d recommend using HTML <table>s for holding each separate plan:
      </head>
      <body>
        <h1>Angelica</h1>
        <table>
          <thead>
            <tr>
              <th></th>
              <th>Monday</th>
              <th>Tuesday</th>
              <th>Wednesday</th>
              <th>Thursday</th>
              <th>Friday</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>1.</td>
              <td>Art</td>
              <td>English</td>
          ...
    4. Change the styling contained within www/css/index.css if you wish, to make the tables look how you want. We’ve chosen to use “zebra striping” for ease of reading.
      table {
        width: 100%;
        border-collapse: collapse;
        font-size: 10px;
      }
      th {
        font-size: 12px;
        font-weight: normal;
        color: #039;
        padding: 10px 8px;
      }
      td {
        color: #669;
        padding: 8px;
      }
      tbody tr:nth-child(odd) {
        background: #e8edff;
      }
    5. To test the app quickly and easily, add the firefoxos platform as a cordova target and prepare the application by entering the following two commands:
      cordova platform add firefoxos
      cordova prepare

      The last step is needed every time you want to check the changes.

    6. Open the App Manager in the Firefox browser. Press the [Add Packaged App] button and navigate to the prepared firefoxos app directory, which should be available in school-plan/platforms/firefoxos/www.

      Note: If you are running Firefox Aurora or Nightly, you can do these tasks using our new WebIDE tool, which has a similar but slightly different workflow to the App Manager.

    7. Press the [Start Simulator] button then [Update] and you will see the app running in a Firefox OS simulator. You can inspect, debug and profile it using the App Manager — read Using the App Manager for more details. App Manager buttons
    8. Now let’s export the app as a native Android APK so we can see it working on that platform. Add the platform and get Cordova to build the apk file with the following two commands:
      cordova platform add android
      cordova build android
    9. The apk is build in school-plan/platforms/android/ant-build/SchoolPlan-debug.apk — read the Cordova Android Platform Guide for more details on how to test this.

    Stage1 Result Screenshot

    Stage 2

    In Stage 2 of our app implementation, we will look at using Brick to improve the user experience of our app. Instead of having to potentially scroll through a lot of lesson plans to find the one you want, we’ll implement a Brick custom element that allows us to display different plans in the same place.

    You can see the finished Stage 2 code on Github.

    We will be using the brick-deck component. This provides a “deck of cards” type interface that displays one brick-card while hiding the others

    1. First, run the following command in www directory to install the needed entire Brick codebase into the app/bower_components.
      bower install mozbrick/brick
    2. To make use of it, add the following code to the <head> of your index.html file, to import its HTML and JavaScript:
      <script src="app/bower_components/brick/dist/platform/platform.js"></script>
      <link rel="import" href="app/bower_components/brick-deck/dist/brick-deck.html">
    3. Next, all the plans need to be wrapped inside a <brick-deck> custom element, and every individual plan should be wrapped inside a <brick-card> custom element — the structure should end up similar to this:
      <brick-deck id="plan-group" selected-index="0">
        <brick-card selected>
          <table>
            <!-- school plan 1 -->
          </table>
        </brick-card>
        <brick-card>
          <table>
            <!-- school plan 2 -->
          </table>
        </brick-card>
      </brick-deck>
    4. The brick-deck component requires that you set the height of the <html> and <body> elements to 100%. Add the following to the css/index.css file:
      html, body {height: 100%}
    5. When you run the application, the first card should be visible while the others remain hidden. To handle this we’ll now add some JavaScript to the mix. First, add some <link> elements to link the necessary JavaScript files to the HTML:
      <script type="text/javascript" src="cordova.js"></script>
      <script type="text/javascript" src="js/index.js"></script>
    6. cordova.js contains useful general Cordova-specific helper functions, while index.js will contain our app’s specific JavaScript. index.js already contains a definition of an app variable. The app is running after app.initialize() is called. It’s a good idea to call this when window is loaded, so add the following:
      window.onload = function() {
          app.initialize();
      }
    7. Cordova adds a few events; one of which — deviceready — is fired after all Cordova code is loaded and initiated. Let’s put the main app action code inside this event’s callback — app.onDeviceReady.
      onDeviceReady: function() {
          // starts when device is ready
      },
    8. Brick adds a few functions and attributes to all its elements. In this case loop and nextCard are added to the <brick-deck> element. As it includes an id="plan-group" attribute, the appropriate way to get this element from the DOM is document.getElementById. We want the cards to switch when the touchstart event is fired; at this point nextCard will be called from the callback app.nextPlan.
      onDeviceReady: function() {
          app.planGroup = document.getElementById('plan-group');
          app.planGroup.loop = true;
          app.planGroup.addEventListener('touchstart', app.nextPlan);
      },
      nextPlan: function() {
          app.planGroup.nextCard();
      }

    Stage2 Result Animation

    Stage 3

    In this section of the tutorial, we’ll add a menu bar with the name of the currently displayed plan, to provide an extra usability enhancement. We will use Brick’s brick-tabbar component. See the finished Stage 3 code on GitHub.

    1. We need to import the component. Add the following lines to the <head> of your HTML:
      <script src="app/bower_components/brick/dist/platform/platform.js"></script>
      <link rel="import" href="app/bower_components/brick-deck/dist/brick-deck.html">
      <link rel="import" href="app/bower_components/brick-tabbar/dist/brick-tabbar.html">
    2. Next, add an id to all the cards and include them as the values of target attributes on brick-tabbar-tab elements like so:
      <brick-tabbar id="plan-group-menu" selected-index="0">
          <brick-tabbar-tab target="angelica">Angelica</brick-tabbar-tab>
          <brick-tabbar-tab target="andrew">Andrew</brick-tabbar-tab>
      </brick-tabbar>
      <brick-deck id="plan-group" selected-index="0">
          <brick-card selected id="angelica">
          ...
    3. The Deck’s nextCard method is called by Brick behind the scenes using tab’s reveal event. The cards will change when the tabbar element is touched. The app got simpler, as we are now using the in-built Brick functionality, rather than our own custom code, and Cordova functionality. If you wished to end the tutorial here you could safely remove the <script> elements that link to index.js and cordova.js from the index.html file.

    Stage3 Result Animation

    Stage 4

    To further improve the user experience on touch devices, we’ll now add functionality to allow you to swipe left/right to navigate between cards. See the finished stage 4 code on GitHub.

    1. Switching cards is currently done using the tabbar component. To keep the selected tab in sync with the current card you need to link them back. This is done by listening to the show event of each card. For each tab from stored in app.planGroupMenu.tabs:
      tab.targetElement.addEventListener('show', function() {
          // select the tab
      });
    2. Because of the race condition (planGroupMenu.tabs might not exist when the app is initialized) polling is used to wait until the right moment before trying to assign the events:
      function assignTabs() {
          if (!app.planGroupMenu.tabs) {
              return window.setTimeout(assignTabs, 100);
          }
          // proceed

      The code for linking the tabs to their associated cards looks like so:

      onDeviceReady: function() {
          app.planGroupMenu = document.getElementById('plan-group-menu');
          function assignTabs() {
              if (!app.planGroupMenu.tabs) {
                  return window.setTimeout(assignTabs, 100);
              }
              for (var i=0; i < app.planGroupMenu.tabs.length; i++) {
                  var tab = app.planGroupMenu.tabs[i];
                  tab.targetElement.tabElement = tab;
                  tab.targetElement.addEventListener('show', function() {
                      this.tabElement.select();
                  });
              }
          };
          assignTabs();
      
          // continue below ...
    3. Detecting a one finger swipe is pretty easy in a Firefox OS app. Two callbacks are needed to listen to the touchstart and touchend events and calculate the delta on the pageX parameter. Unfortunately Android and iOS do not fire the touchend event if the finger has moved. The obvious move would be to listen to the touchmove event, but that is fired only once as it’s intercepted by the scroll event. The best way forward is to stop the event from bubbling up by calling preventDefault() in the touchmove callback. That way scroll is switched off, and the functionality can work as expected:
      // ... continuation
      app.planGroup = document.getElementById('plan-group');
      var startX = null;
      var slideThreshold = 100;
      
      function touchStart(sX) {
          startX = sX;
      }
      
      function touchEnd(endX) {
          var deltaX;
          if (startX) {
              deltaX = endX - startX;
              if (Math.abs(deltaX) > slideThreshold) {
                  startX = null;
                  if (deltaX > 0) {
                      app.previousPlan();
                  } else {
                      app.nextPlan();
                  }
              }
          }
      }
      
      app.planGroup.addEventListener('touchstart', function(evt) {
          var touches = evt.changedTouches;
          if (touches.length === 1) {
              touchStart(touches[0].pageX);
          }
      });
      
      app.planGroup.addEventListener('touchmove', function(evt) {
          evt.preventDefault();
          touchEnd(evt.changedTouches[0].pageX);
      });

    You can add as many plans as you like — just make sure that their titles fit on the screen in the tabbar. Actions will be assigned automatically.

    Stage4 Result Screenshot

    To be continued …

    We’re preparing the next part, in which this app will evolve into a marketplace app with downloadable plans. Stay tuned!

    [EDIT] See the next part where we separated data from DOM.

  9. The Missing SDK For Hybrid App Development

    Hybrid vs. native: The debate has gone on, and will go on, for ages. Each form of app development has its pros and cons, and an examination of the key differences between the two methods reveals that a flat correlation is like comparing apples to oranges. Many hybrid app developers understand that they’re not starting on a level playing field with native developers, but it’s important for them to understand exactly how that field fails to be level. We should analyze the differences at the app development framework level, instead of simply comparing hybrid to native.

    Native App Development

    Native software development kits (SDKs) come prepackaged with the required tools to compile and sign apps–all that boring stuff that “just works.” Their canned templates and the common code they provide determines “how” an app works, which are hugely beneficial features hiding in plain sight.

    For example, Apple’s own development environment, Xcode, is a suite of tools that make it easier to develop software for Apple platforms. As soon as you launch Xcode, it provides a list of templates for a new project. Let’s create an iOS tabbed application and see just how many tools native app developers have available to them from the start.

    After plugging in a few settings, I quickly have a working tabbed app that can be run within a minute of Xcode’s launch. Awesome! The easy part is done, and now it’s time to sport the hoodie and headphones and focus on my goal of creating a best-selling iOS app.

    Let’s drill into this a little further and take a look at what Xcode built for me, without me even having to give it a second thought. The app fills 100% of the device’s display; the tabs are aligned to the base with a gray background; one tab is highlighted blue, the other one gray, with icons on top of the tab’s title. Event listeners are added to the tab buttons, and when I click the second tab, it then turns blue, and the other one turns gray, along with switching out the content for the second view.

    After a little more development, my app will have a separate navigation stack for each tab; the back button will show when needed; the title of each view will switch out in the header; and smooth transitions will happen as I navigate between different views. Fonts, colors, gradients, and styles will all follow what an app should look like on that platform. By expanding folders in Xcode’s project navigator, I notice the ridiculous amount of files that were placed there for me. I have no clue what they do and honestly don’t really care, but they’re actually extremely important: They’re the files that made the development of my app so seamless.

    Hybrid App Development

    Honestly, though, I’m a web developer. My organization isn’t filled with Objective-C and Java developers ready to build multiple apps with the ultimate goal of just trying to be the same app (but built entirely differently, from the ground up). I have existing HTML/CSS/JS skills, and I want my app to hit not only Apple’s App Store, but Google’s Play Store and all the others. Instead of locking myself into each platform and having to learn and maintain multiple proprietary languages, hybrid app development makes more sense, given my skill set and my time constraints.

    Don’t let hybrid app development scare you. In its simplest form, a hybrid app is still just a web browser, but without the URL bar and back button. This means you can build a full-fledged app using HTML/CSS/JS but still have it work like a native app, with all the same superpowers, like accessing bluetooth, GPS, camera, files, device motion, etc. Hybrid apps offers the best of both worlds.

    Let’s Build A Hybrid App

    For my use-case, I’ve decide a hybrid app is perfect, so let’s fire up….wait, there is no Xcode for hybrid apps! Where do I get all those files that makes my app do app things? Where’s the built-in UI, cool animations, and smooth transitions? The disadvantage to hybrid app development is that those starting with the web platform have to start from scratch. It’s the Wild West, and each developer is on his or her own.

    I absolutely love the web platform. It provides a core set of tools to not only share information but consume it. However, the web platform offers only the basic building blocks for structuring content; styling and interacting with that content; and navigating between documents. The core of the web platform does not provide pre-packaged libraries like those in iOS and Android. At the lowest level, a web browser represents one “view” and one navigation/history stack. This is another area in which native apps have an advantage, and hybrid devs burn up time generating code to give browsers the ability to handle multiple views with multiple stacks.

    Hybrid app developers quickly find themselves dealing with countless issues just to get their heads above water: figuring out the CSS to fill 100% of the viewport, sticking the tabs on the bottom and hovering content, adding event listeners, switching active states for icons and views, keeping track of navigation stacks of each tab, implementing touch gestures, generating a common way to navigate and transition between view, using hardware accelerated animations for the transitions, removing 300ms delays, knowing when to show or hide the back button, updating and animating the header title, interacting with Android’s back button, smooth scrolling through thousands of items, etc. All of these things come with native SDKs. And as an app grows, it’ll seem as though recreating natural, native interactions is a constant uphill battle–all of which native developers rarely think about, because for them it was baked in from the start.

    I believe the solution is to use a framework/library that levels the playing field, giving hybrid developers a toolkit similar to that of native developers. Most developers have a sense of pride in writing everything themselves, and there are certainly plenty of use-cases in which frameworks and/or libraries are not required. But when it comes to hybrid app development, there is a whole world of common code being recreated by each developer, for each app, and that’s a lot of work and time to invest in something that native developers already get.

    Cordova (the guts to Phonegap) is the predominant open-source software to morph the web platform into a native app. But just like the web platform, it only offers the basic building blocks, rather than a built-in framework to start with.

    Frameworks and libraries amplify app development and allow devs to focus on how their apps work, not the logic of how apps work.

    Hybrid Development Frameworks: Hybrid’s Missing SDK

    I define frameworks and libraries as any software package that increases productivity. In my experience, hybrid developers’ main frustration lies not in creating their app, but rather in getting their app to work like an app, which is a subtle, yet significant difference.

    With the advent of development frameworks focused on hybrid app development, developers finally have that missing SDK that levels the playing field with native. Cross-platform frameworks that embrace HTML/CSS/JS allow web developers to take their skills to app development. To increase productivity, hybrid app development frameworks should not only stick to the standards with pretty CSS and markup; they should also provide a MVC for serious, large-scale app development by a team.

    Sworkit, a hybrid app built using the Ionic Framework, featured in the iOS App Store.

    Sworkit, a hybrid app built using the Ionic Framework, featured in the iOS App Store.

    Frameworks like the Ionic Framework provide a common set of logic used to create apps, which isn’t already baked into the web platform (full disclosure: I am a co-creator of Ionic and a core contributor to AngularJS Material Design). Rather than inventing yet another MVC, Ionic chose to build on top of AngularJS, which has proven itself to be a leader among popular front-end frameworks in the last few years. Ionic is backed by a large and active community, solid documentation, and its own command-line tool to increase productivity (the CLI isn’t required but is highly recommended).

    Ionic’s use of AngularJS directives to create the user-interface allows for fast development, but most importantly, it’s highly customizable, through its use of Sass and simple markup. If you’re unfamiliar with AngularJS, Ionic offers a great way to learn it quickly, via numerous examples and templates.

    Google’s Polymer project and Mozilla Brick are built on top of the cutting-edge Web Components specification. As with AngularJS directives, Web Components offer a great way to abstract away complex code with simple HTML tags.

    Other proven hybrid development frameworks that have been around longer and offer their own powerful MVC/MVVM include Sencha Touch and Kendo UI. Both frameworks come with a large list of built-in components and widgets that enable developers to build powerful apps that work on iOS, Android, BlackBerry, Windows Phone, and more.

    I do not consider front-end frameworks, such as AngularJS, Ember, and Backbone to be hybrid app development frameworks. While I highly recommend front-end frameworks for large-scale app development, they’re focused on the logic, rather than the user interface.

    Frameworks like Twitter Bootstrap and Foundation offer a great user interface and responsive design capabilities. However, CSS frameworks focus on the UI and a responsive design pattern; but a UI alone still requires each developer to recreate the way a native app “works.”

    App frameworks abstract away the complexity of building an app and offer developers both the core logic and the UI, similar to what iOS and Android provide. I believe it is important to embrace the web standards and the web platform at the core, so it can be easily run from Cordova and built for multiple platforms (including a standard web browser).

    Software Engineering: Still Required

    Hybrid app development frameworks have come a long way in the last few years, and they have a bright future ahead of them. However, Developing Hybrid Apps Still Requires Actual Software Engineering, just as developing native apps does. As in any form of web development, if only a few hours are put into a project, the result is rarely a highly polished and production-ready piece of work.

    Hybrid fans sometimes offer the impression that frameworks create a unicorn and rainbow filled wonderland during development, which may mislead new developers. Just because you can write once and deploy everywhere doesn’t mean you’re exempt from the rules of software development. Hybrid offers many benefits, but as with so many things in life, cutting corners does not produce quality results, so please don’t claim all is lost after minimal effort.

    Making An Accurate Comparison

    The comparison of a hybrid app without a framework to a native app isn’t a fair one. To accurately examine hybrid vs. native development, we need to analyze each method at the framework level (hybrid with a framework vs. native, like Ionic Framework vs. iOS or Polymer vs. Android). As for hybrid vs. native: It’s a matter of studying your use-case first, rather than a choosing a definitive level of abstraction over everything else. It all ends up as 1s and 0s anyway, so jumping up another level of abstraction (HTML/CSS/JS) may be the right fit for your use-case and organization.

    I often feel that opinions regarding hybrid app development were formed well before hybrid was ready for prime time. The world has quickly been transitioning to faster devices and browsers, as well as improved web standards, and there’s no signs of either one slowing down. There is no better time than now to leverage your existing web development skills and build quality apps by standing on the shoulders of hybrid SDKs.

    @adamdbradley

  10. Introducing fxpay for in-app payments

    A while ago Mozilla announced navigator.mozPay() for accepting payments on Firefox OS. This was our first step toward helping developers do commerce on the web. It solved the problem of processing payments but what about the rest? Today we’re announcing an early peek at fxpay, a library for the rest of what you need as a developer to sell digital products in your app. This small JavaScript library (11kB minified) gives you some nice additional features on top of payment processing:

    • Work with a catalog of in-app products without hosting your own server
    • Securely verify each payment in one simple JavaScript callback
    • Restore previous purchases from receipts

    The mozPay() API is great if you need to integrate with an existing system but building a product catalog from scratch is a lot of work. If you have an idea for adding revenue to your app, why not quickly toss in some products and see what sells?

    The fxpay library is designed to get you selling as quickly as possible. Because it interacts with the Firefox Marketplace API you don’t need to manage code on your own server — you enter product details on Firefox Marketplace and call some JavaScript functions in your app to sell them.

    First: A Warning

    fxpay is in an early experimental state. We’re looking for your help to make it a concise, intuitive library which means the API might change rapidly. There’s a section below for details about how to get involved.

    Offer Some Products For Sale

    After running bower install fxpay (installation details), show some products for sale within your app using getProducts():

    // First, let's test with some fake products.
    fxpay.configure({fakeProducts: true});
     
    fxpay.getProducts(function(error, products) {
      if (error) {
        console.error(error);
      } else {
        products.forEach(function(product) {
          addBuyButtonForProduct(product);
        });
      }
    });

    The code here would add a buy button for each product so that tapping the button would start a purchase. Here’s a simplistic example:

    function addBuyButtonForProduct(product) {
      var button = document.createElement('button');
      button.textContent = 'Buy ' + product.name;
      button.addEventListener('click', function () {
     
        fxpay.purchase(product.productId,
                       function(error, product) {
          if (error) {
            console.error(error);
          } else {
            console.log(product.name + ' bought!');
          }
        });
     
      });
      document.body.appendChild(button);
    }

    That’s it. The call to fxpay.purchase() guides the user through payment via mozPay() and verifies the result on the Firefox Marketplace server. If you receive the callback without an error that means the purchase was verified and funds will be transferred to your account upon completion. It is safe to deliver the product.

    Try It Out

    Install an app on the Firefox OS Simulator using the App Manager and tap your custom buy button. Since this is a test with fake products, you’ll see a payment simulation screen. Tap Continue and your purchase callback will fire, allowing you to get your product delivery code working.

    Restoring Purchases

    When a user completes a purchase, a portable receipt is installed on their device behind the scenes. This is a cryptographically signed JSON object that proves ownership of the product. Each time the user returns to your application, you’ll want to check for any receipts so you can restore their purchased products. The fxpay library does the validation for you but you have to define a callback for when each product is restored like this:

    fxpay.init({
      onerror: function(error) {
        console.error('init error:', error);
      },
      onrestore: function(error, product) {
        if (error) {
          console.error('onrestore error:', error);
        } else {
          console.log(product.name + ' restored');
        }
      }
    });

    Just like the purchase callback, if you don’t get an error it means the receipt is valid; the user owns the product so you should make it available. You probably only need to run init() once when your app starts up.

    Publishing Your Application

    Once you get everything working with fake products, the next step is to submit your app to the Firefox Marketplace developer hub so users can install it. The submission process is detailed in the documentation; it includes entering bank account details to receive payouts and submitting real names and prices of the products you wish to sell.

    Once all of your products are in place and your app has been approved, set {fakeProducts: false} and the same code you see here will begin to work with real products. The getProducts() function will use your app’s origin to retrieve the configured products.

    Example App

    The fxpay library ships with an example app that you can install from the App Manager and play around with.

    Get Involved

    Do you have an idea for an app with in-app payments? Try it out. We’re announcing an early version of fxpay so we can make it work well for you, the developer. If something breaks or just feels awkward or if you have an idea to make the library better, get in touch!

    Going Further

    The Payments team at Mozilla is small (but mighty!). This release has a lot of missing features but we wanted to start getting early feedback. Here are a few ideas for features we’d like to add next:

    Support More App Types

    Right now, fxpay is limited to privileged packaged apps on mobile only. See all prerequisites. We’re working to open up access to other app types such as hosted apps and desktop apps.

    Receipt Recovery

    If a user loses their device or deletes the app they will lose their in-app purchase receipts. Those receipts are backed up on the Marketplace server but users don’t have an interface to reinstall them yet. We’re trying to figure out a way to do that in bug 1045228.

    Exact Product Prices

    Price points and currencies vary by mobile network but non-certified apps don’t have a reliable way to detect users’ networks. Currently you can’t display prices next to your custom buy buttons. We’d like to offer some solutions for this even if it’s just a manual currency selector. See bug 1063758.

    Product Assets

    This first release of fxpay is best suited for local content such as unlocking a level in a game. If an app were to distribute remote content such as a MP3 file as a ring tone, there would be no way to securely protect the contents of that file. We’ll be considering how to add support for this in bug 1063059.

    We’re planning lots of other stuff too!