Front end selfnotes - backbonejs + requirejs + firebase

So I found this cool cloudbase database thingo called firebase... perfect for Internet of Thing or little projects that you want to make, for example a todo list between husband and wife, pebble watch app, etc...

Nevertheless, in the course of building a "husband todo & shoping list" app which is a combination of firebase+requirejs and backbonejs, I have learnt a few things and just want to note it down here for future references. Note that most of these codes are straight from the example provided by firebase with minimal or no changes. It took me a little longer than expected to understand all these JS-lingo :-/ .

RequireJS (http://requirejs.org/)

Requirejs asynchronously loads your javascript libraries and simplify your index.html to include only 1 script tag. Here is the requirejs reference in my index.html. The data-main points to the location of your main.js file:

<!-- data-main points to js/main.js which tells require.js to load main.js first -->  
<script data-main="js/main" src="bower_components/requirejs/require.js"></script>  

then in my main.js, i can have the requirejs configuration which declares all the JS library I'm using, their dependencies, their exported global variables:

require.config({  
    // The shim config allows us to configure dependencies for
    // scripts that do not call define() to register a module
    shim: {
        underscore: {
            exports: '_'
        },
        backbone: {
            //dependencies for backbone are underscore and jquery
            deps: [
                'underscore',
                'jquery'
            ],
            exports: 'Backbone'
        }
    },
    paths: {
        jquery: '../bower_components/jquery/jquery',
        underscore: '../bower_components/underscore/underscore',
        backbone: '../bower_components/backbone/backbone',
        text: '../bower_components/requirejs-text/text',
        firebase: '../bower_components/firebase/firebase',
        backbonefire: 'backbonefire'
        //backbonefire is the backbone firebase library.
    }
});

Notice in the above, "bower_components" is in the path for these javascript libraries. This is also something new i learnt as part of this journey, bower is powerful tool built ontop of git, npm, nodes. You can think of bower like apt-get or python-pip for javascript. You can define a bower.json with all the dependencies, version for the javascript and use "bower install" command when deploy the application to make sure you have all the required javascript library.

Backbonejs (http://backbonejs.org/)

I have done a bit of backbonejs development for my other project but it was an ugly piece of code that was mashed together to learn the basic of backbonejs and to test out the possibility of having localstorage sync with my django model.

This time, i discovered from the firebase example that requirejs make backbonejs code looks a lot more readable and modular. An example of a View is shown below. I have taken out half of its code to make the code shorter but enough to explain what goes into a view.

define([  
    'jquery',
    'underscore',
    'backbone',
    'common
    'text!templates/item.html'
    //the template for the view is stored in ./templates/item.html.
    //each of the variables parsed to function below is mapped back to those above,
    //eg: jquery -> $, underscore -> _, ...
], function ($, _, Backbone, itemTemplate, Common) {
    'use strict';

    var ItemView = Backbone.View.extend({
        //parent class name that wrap around item.html template is div tag
        tagName:  'div', 
        template: _.template(itemTemplate),

        // The DOM events specific to an item.
        events: {
            //when lable is double click, call edit callback.
            'dblclick label':    'edit',
            //listen for keypress in class edit (the input field)
            'keypress .edit':   'updateOnEnter',
        },

        initialize: function () {
            //listen to change of model and re-render the item.
            this.listenTo(this.model, 'change', this.render);
        },

        render: function () {
            //parsing JSON through this template.
            this.$el.html(this.template(this.model.toJSON()));
            this.$input = this.$('.edit');
            return this;
        },

        //edit callback
        edit: function () {
            //add the "editing" class to the parent div. In CSS, we already pre-defined that the label text will not be displayed (display:none) and the input field will be visible.
            this.$el.addClass('editing');
            this.$input.focus();
        },

        updateOnEnter: function (e) {
            if (e.keyCode === Common.ENTER_KEY) {
                this.close();
            }
        },

And the corresponding template (item.html):

    <h1 style="display: block;" class="lead"><div class="col-xs-1 col-md-1 col-md-offset-1 col-xs-offset-0" style="display: block;">
            <span id="complete" class="glyphicon glyphicon-ok text-success"></span>
        </div><div class="col-xs-1 col-md-1 col-md-offset-0 col-xs-offset-0" style="display: block;">
            <span id="destroy" class="glyphicon glyphicon-remove text-danger"></span>
        </div><div class="col-xs-8" style="display: block;">
            <span <%= completed ? 'class="item-view text-muted" style="text-decoration:line-through;"' : 'class="item-view"' %>><%- title %></span>
            <input class="edit" value="<%- title %>">
        </div>
    </h1>

And the following css to display input field (edit) only when "editing" class is applied to the parent div:

.edit {
    display: none;
}

.editing .edit{
    display: block;
    width: 100%;
}

.editing .item-view{
    display: none;
}

Backbonejs can easily has its own post since it's a full client MVC framework. Let us move on to the next topic, firebase, model, colletion on auth.

Backbonefire and authentication

Firebase is fantastic and so is the example for the todo app provided by firebase. Unfortunately there isnt' any login in the example and people have been asking around as to what they can do with backbonefire to have auth.
In my app, i picked one of the simplest form of authentication with firebase using email and password.

Lets enforce authentication on read and write to object in firebase under Security & Rules:

{
    "rules": {
        ".read": "auth != null",
        ".write": "auth != null"
    }
}

After creating a user with firebase using the UI, lets move on to backbonefire.

Here is a model for a todo item which is extended from Backbone.Model:

/*global define*/
define([  
    'underscore',
    'backbone'
], function (_, Backbone) {
    'use strict';

    var Todo = Backbone.Model.extend({
        // Default attributes for the todo
        // and ensure that each todo created has `title` and `completed` status.
        defaults: {
            title: '',
            completed: false
        },

        // Toggle the `completed` state of this todo item.
        toggle: function () {
            this.save({
                completed: !this.get('completed')
            });
        }
    });

    return Todo;
});

And next is the collection of the todo item model I created previously. Note that this time, the collection is extended from Backbone.Firebase.Collection:

/*global define */
define([  
    'underscore',
    'backbone',
    'models/todo',
    'firebase',
    'backbonefire'
], function (_, Backbone, Todo) {
    'use strict';

    var TodosCollection = Backbone.Firebase.Collection.extend({
        // Reference to this collection's model.
        model: Todo,

        // Save all of the todo items under the "todos" namespace.
        url: 'https://lazyhusband.firebaseio.com/todos',

        // Filter down the list of all todo items that are finished.
        completed: function () {
            return this.where({completed: true});
        },

        // Filter down the list to only todo items that are still not finished.
        remaining: function () {
            return this.where({completed: false});
        }
    });

    return new TodosCollection();
});

And here is the most asked question regarding this particular example: How do you authenticate with firebase and how do you apply it across all collections?
The answer is quite obvious , as stated in firebase authwithpassword page:

All references to a Firebase share the same authentication status, so if you call new Firebase() twice and call any authentication method on one of them, they will both be authenticated.

What does this mean? Well, simply put, if you call this random function anywhere in the app, you will authenticate this user and all the future request to your firebase end point when using this page:

var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");  
ref.authWithPassword({  
  "email": "ya_mail@address.com",
  "password": "correcthorsebatterystaple"
}, function(error, authData) {
  if (error) {
    console.log("Login Failed!", error);
  } else {
    console.log("Authenticated successfully with payload:", authData);
  }
});

So, Below is the login view i made which uses a login bootstrap modal template.

/*global define*/
define([  
    'jquery',
    'underscore',
    'backbone',
    'text!templates/loginmodal.html',
    'common'
], function ($, _, Backbone, loginTemplate, Common) {
    'use strict';

    var LoginView = Backbone.View.extend({

        // Instead of generating a new element, bind to the existing skeleton of index.html
        el: '#loginapp',

        // Compile our login modal template
        template: _.template(loginTemplate),

        // When the loginbtn is pressed, login procedure is fired off.
        events: {
            'click #loginbtn':      'loginfirebase'
        },

        initialize: function () {
            this.$el.html(this.template());
        },

        render: function () {
            this.$el.html(this.template());
            return this;
        },
        //Login firebase here!
        loginfirebase: function(e) {
            //That's right.. i just declare a new Firebase object and auth... this is because all firebase instances share the authentication.
            var REF = new Firebase("https://lazyhusband.firebaseio.com");
            REF.authWithPassword({
                'email': $("#email").val(),
                'password': $("#password").val()
            }, function (error, authData) {
                if (error) {
                    alert("Login Failed! Try again!");
                } else {
                    $("#loginmodal").modal('hide');
                }
            });
            Common.AUTH = true;
        }
    });

    return LoginView;
});

And voila, my little app now requires authentication and our shopping list is secured.

comments powered by Disqus