codeigniter, JavaScript, jquery

Integrating Elfinder and ckeditor


1. In view file

// link the required js and css

<script src=”<?php echo assets_url( ‘admin/lib/jquery-1.7.2.min.js’ );?>” type=”text/javascript”></script>
<script type=”text/javascript” src=”http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js”></script&gt;
<link rel=”stylesheet” type=”text/css” media=”screen” href=”<?php echo assets_url( ‘js/plugins/elfinder/css/elfinder.min.css’ );?>”>
<link rel=”stylesheet” type=”text/css” media=”screen” href=”<?php echo assets_url( ‘js/plugins/elfinder/css/theme.css’ );?>”>

<script src=”<?php echo assets_url( ‘js/plugins/ckeditor/ckeditor.js’ );?>”></script>
<script src=”<?php echo assets_url( ‘js/plugins/ckeditor/ckeditor.js’ );?>”></script>
<script src=”<?php echo assets_url( ‘js/plugins/ckeditor/adapters/jquery.js’ );?>”></script>
<script src=”<?php echo assets_url( “js/plugins/elfinder/js/elfinder.min.js” ) ?>”></script>

2. initialize the elfinder

<script type=”text/javascript”>
    $(document).ready(function(){
 // setting base url for javascript [ using codeigniter ]
 var base_url = ‘<?php echo base_url(); ?>’;
// new config for ckeditor dialog box
CKEDITOR.on(‘dialogDefinition’, function (event) {
    var editor = event.editor;
    var dialogDefinition = event.data.definition;
   // var dialogName = event.data.name;
    var dialogName = “image”;
    var tabCount = dialogDefinition.contents.length;
    for (var i = 0; i < tabCount; i++) { //цикл для замены клика всех кнопок “Посмотреть на сервере”
        var browseButton = dialogDefinition.contents[i].get(‘browse’);
        if (browseButton !== null) {
            browseButton.hidden = false;
            browseButton.onClick = function (dialog, i) {
                $(‘<div id=”elfinderz” \>’).dialog({
                    modal: true,
                    width: “70%”,
                    title: ‘Togally Assets’,
                    dialogClass: “elfinderz-filez”,
                    zIndex: 9999999,
                    create: function (event, ui) {
                        $(this).elfinder({
                            resizable: false,
                            lang: ‘en’,
                            url: base_url + ‘assets/js/plugins/elfinder/php/connector.php?mode=’ + dialogName,
                            getFileCallback: function (url) {
                                var dialog = CKEDITOR.dialog.getCurrent();
                                if (dialog._.name == “image”) {
                                    var urlObj = ‘txtUrl’
                                } else if (dialog._.name == “flash”) {
                                    var urlObj = ‘src’
                                } else if (dialog._.name == “link”) {
                                    var urlObj = ‘url’
                                } else {
                                    return false
                                };
                                dialog.setValueOf(dialog._.currentTabId, urlObj, url);
                                $(‘a.ui-dialog-titlebar-close[role=”button”]’).click();
                return false;
                            }
                        }).elfinder(‘instance’)
                    }
                })
            }
        }
    }
});
// initializing elfinder
$( ‘textarea.editor-ck’ ).ckeditor({height:”500px”})
});
</script>
Resource
=============
// for stand alone purpose with out ckeditor ( in elfinder/elfinder.html )

<script type=”text/javascript” charset=”utf-8″>
// Helper function to get parameters from the query string.
function getUrlParam(paramName) {
var reParam = new RegExp(‘(?:[\?&]|&amp;)’ + paramName + ‘=([^&]+)’, ‘i’) ;
var match = window.location.search.match(reParam) ;

return (match && match.length > 1) ? match[1] : ” ;
}
$().ready(function() {
var funcNum = getUrlParam(‘CKEditorFuncNum’);

var elf = $(‘#elfinder’).elfinder({
url : “http://192.168.123.10/togally-dev/assets/js/plugins/elfinder/php/connector.php&#8221;,
getFileCallback : function(file) {
window.opener.CKEDITOR.tools.callFunction(funcNum, file);
window.close();
},
resizable: false
})
});
</script>

ajax, JavaScript, jquery

live updating select option with chosen


If you guys have ever used chosen plugin. you might have end up trying dynamically changing selection option with javascript.
Here is code below, that you can update option dynamically with javascript for chosen plugin.

 

<select name="names" id="names" >
    <option value="1"> Niraj </option>
    <option value="2" >Buddha</option>
    <option value="3">Dino</option>
    <option value="4">AL</option>
</select>

$("#name").chosen();

// updating again with javascript
document.getElementById('names').value = 4;
$('#names').trigger('liszt:updated');
CSS 3, Design Inspiration, JavaScript, jquery

Basic jquery plugin developement / css3 round image


Note:

-----------------------------------------

1. Just a quick plugin development, to fix my work. You may extend it use it distribute it and what ever....................

Objective of the post

--------------------------------------

1. Basic Plugin development

2. implementing css3 round image style

-------------------------------------------------

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>

// plugin start from here

// this hooks a new function to jquery [ $.fn.circle_me = function ]

/*

plugin name : circle_me

author : Jumper

email : neerooze@gmail.com

*/
$.fn.circle_me = function(options) {
console.log('Init circle me');

// default styles

var _style = {
             border: '1px solid #DDD',
             borderRadius: '50%',
             display: 'inline-block',
             height:"200px",
             width:"200px"
};
// over ridding the default style with users style
_style = $.extend( _style, options);

/*
Here   "  return "  is so important if you want to add changeable function.

eg:         $(".circle_now").circle_me().hide();

if you do not add return, here hide function will not work but still plugin will work  fine. If you do not want to add chain function you may ignore return function.

*/
return $(this).each(function(){
              $(this).css(_style);
              var _bg = $(this).attr('src');
              $(this).css({"backgroundImage":_bg});
        });

}

// plugin ends here
$(document).ready(function(){

     // initializing plugin basic

     $('.circle').circle_me();

    // initializing plugin with custom options

     $('.circle').circle_me( {  height: "300px", width: "400px", borderRadius:" 70%"});
});
</script>

// html part

<img class="circle"  src="https://fbcdn-sphotos-a-a.akamaihd.net/hphotos-ak-ash3/67563_10151525052674927_2120163905_n.jpg" />

DEMO
Cheers..
JavaScript, jquery

Jquery live custom search in table view


Here is some simple live searching tips tat you may implement easily.

table structure

<table id=”content_table” >

<thead>

<tr>…</tr>

<tr>…</tr>

<tr>…</tr>

</thead>

<tbody>

<tr>…</tr>

<tr>…</tr>

<tr>…</tr>

</tbody>

</table>

just create table structure with some ID, here below i have made some screen shot hope you guys will get it he he …..:D

Code :

(function($) {
$(document).ready( function() {
var _val = ”;
$(‘#q’).live(‘keyup’, function(){
_q = $(this).val();
$(‘#content_table tbody tr’).each(function(){
var _tr = this;
$(this).find(‘td’).each(function(){
_val += $(this).text()+’ ‘;
})
_val = _val.toLowerCase();
_data = _val.search(_q);
if(_data > -1){
console.log(‘found here’);
$(_tr).fadeIn();
}else{
$(_tr).fadeOut();
console.log(‘not found here’);
}
_val = ”;

})
});

});
})(jQuery)

backbone.js, JavaScript, jquery

Step by step from jQuery to Backbone / understanding-backbone


please see this page for clear view

url: https://github.com/kjbekkelund/writings/blob/master/published/understanding-backbone.md/

 

Step by step from jQuery to Backbone

I’ve seen many struggle when they first meet Backbone.js. In this blog post I will gradually refactor a bit of code from how I used to write JavaScript before, into proper Backbone.js code using models, collections, views and events. Hopefully this process will give you a firm understanding of the core abstractions in Backbone.js.

Let’s start with the code we’re going to work with throughout this blog post:

$(document).ready(function() {
    $('#new-status').submit(function(e) {
        e.preventDefault();

        $.ajax({
            url: '/status',
            type: 'POST',
            dataType: 'json',
            data: { text: $(this).find('textarea').val() },
            success: function(data) {
                $('#statuses').append('<li>' + data.text + '</li>');
                $(this).find('textarea').val('');
            }
        });
    });
});

And here’s the application up and running: Monologue. This simple application is based on a great JavaScript presentation by Brandon Keepers. However, while his focus is primarily on testing and design patterns — of which he does an amazing job — mine is on explaining the core Backbone.js abstractions one step at a time.

Basically, the app lets you to write some text, click on “Post”, and see what you’ve written show up in the list while the textarea is prepared for new input. Translating this to code, we start by waiting for the DOM to load, set a submit listener, and when the form is submitted we send the input to the server, append the response to the list of statuses and reset the input.

But, what’s the problem? This code does a lot of stuff at the same time. It listens for page events, user events, network events, it does network IO, handles user input, parses the response, and does HTML templating. All in 16 lines of code. It looks like most JavaScript code I wrote a year ago. Throughout this blog post I will gradually work my way to code that follows the single responsibility principle, and which is far easier to test, maintain, reuse and extend.

There are three things we want to achieve:

  • We want to move as much as possible out of $(document).ready, so we can trigger the code ourselves — we only want to bootstrap the application when the DOM is ready. In its current state the code is nearly impossible to test.
  • We want to adhere to the single responsibility principle, and make the code more reusable and easier to test.
  • We want to break the coupling between the DOM and Ajax.

Separating DOM and Ajax

We start with splitting Ajax and DOM from each other, and the first step is to create an addStatus function:

+function addStatus(options) {
+    $.ajax({
+        url: '/status',
+        type: 'POST',
+        dataType: 'json',
+        data: { text: $('#new-status textarea').val() },
+        success: function(data) {
+            $('#statuses ul').append('<li>' + data.text + '</li>');
+            $('#new-status textarea').val('');
+        }
+    });
+}
+
 $(document).ready(function() {
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        $.ajax({
-            url: '/status',
-            type: 'POST',
-            dataType: 'json',
-            data: { text: $('#new-status textarea').val() },
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
+        addStatus();
     });
 });

However, both within data and success we work with the DOM. We can break this coupling by sending these as arguments toaddStatus:

 function addStatus(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
-        data: { text: $('#new-status textarea').val() },
-        success: function(data) {
-            $('#statuses ul').append('<li>' + data.text + '</li>');
-            $('#new-status textarea').val('');
-        }
+        data: { text: options.text },
+        success: options.success
     });
 }

 $(document).ready(function() {
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        addStatus();
+        addStatus({
+            text: $('#new-status textarea').val(),
+            success: function(data) {
+                $('#statuses ul').append('<li>' + data.text + '</li>');
+                $('#new-status textarea').val('');
+            }
+        });
     });
 });

However, I want to wrap these statuses in an object and be able to write statuses.add instead of addStatus. To achieve this we can use the constructor pattern with prototypes to introduce a Statuses “class”:

-function addStatus(options) {
+var Statuses = function() {
+};
+Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
-}
+};

 $(document).ready(function() {
+    var statuses = new Statuses();
+
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        addStatus({
+        statuses.add({
             text: $('#new-status textarea').val(),
             success: function(data) {
                 $('#statuses ul').append('<li>' + data.text + '</li>');
                 $('#new-status textarea').val('');
             }
         });
     });
 });

Creating a view

Our submit handler now has one dependency, the statuses variable, and everything else within it is focused on the DOM. Let’s move the submit handler and everything inside it into its own class, NewStatusView:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

+var NewStatusView = function(options) {
+    var statuses = options.statuses;
+
+    $('#new-status form').submit(function(e) {
+        e.preventDefault();
+
+        statuses.add({
+            text: $('#new-status textarea').val(),
+            success: function(data) {
+                $('#statuses ul').append('<li>' + data.text + '</li>');
+                $('#new-status textarea').val('');
+            }
+        });
+    });
+};
+
 $(document).ready(function() {
     var statuses = new Statuses();

-    $('#new-status form').submit(function(e) {
-        e.preventDefault();
-
-        statuses.add({
-            text: $('#new-status textarea').val(),
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
-    });
+    new NewStatusView({ statuses: statuses });
 });

Now we only bootstrap our application when the DOM is loaded, and everything else is moved out of $(document).ready. The steps we have taken so far has given us two components which are easier to test and have more well-defined responsibilities. However, there is still much to clean up. Let’s start by splitting the submit handler in NewStatusView into its own addStatus method:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
-    var statuses = options.statuses;
+    this.statuses = options.statuses;

-    $('#new-status form').submit(function(e) {
-        e.preventDefault();
-        statuses.add({
-            text: $('#new-status textarea').val(),
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
-    });
+    $('#new-status form').submit(this.addStatus);
 };
+NewStatusView.prototype.addStatus = function(e) {
+    e.preventDefault();
+
+    this.statuses.add({
+        text: $('#new-status textarea').val(),
+        success: function(data) {
+            $('#statuses ul').append('<li>' + data.text + '</li>');
+            $('#new-status textarea').val('');
+        }
+    });
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Running this in Chrome we get this error:

Uncaught TypeError: Cannot call method 'add' of undefined

We get this error as this means different things in the constructor and the addStatus method, as it is jQuery that actually calls the latter when the user submits the form. (If you don’t fully grasp how this works, I recommend reading Understanding JavaScript Function Invocation and “this”.) To solve this problem we can use $.proxy, which creates a function where this is always the same — the context you specify as the second argument.

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

-    $('#new-status form').submit(this.addStatus);
+    var add = $.proxy(this.addStatus, this);
+    $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
             $('#statuses ul').append('<li>' + data.text + '</li>');
             $('#new-status textarea').val('');
         }
     });
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Let’s make the success callback call methods instead of working directly on the DOM, which makes the callback easier to read and work with:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

+    var that = this;
+
     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
-            $('#statuses ul').append('<li>' + data.text + '</li>');
-            $('#new-status textarea').val('');
+            that.appendStatus(data.text);
+            that.clearInput();
         }
     });
 };
+NewStatusView.prototype.appendStatus = function(text) {
+    $('#statuses ul').append('<li>' + text + '</li>');
+};
+NewStatusView.prototype.clearInput = function() {
+    $('#new-status textarea').val('');
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

This is much easier to test and to work with as our code grows. We are also far on our way to understand Backbone.js.

Adding events

For the next step we need to introduce our first bit of Backbone: events. Events are basically just a way to say: “Hi, I want to know when some action occurs” and “Hi, you know what? The action you’re waiting for just occurred!” We are used to this idea from jQuery DOM events such as listening for click and submit, e.g.

$('form').bind('submit', function() {
  alert('User submitted form'); 
});

The Backbone.js documentation describes Backbone.Events as follows: “Events is a module that can be mixed in to any object, giving the object the ability to bind and trigger custom named events.” The docs also shows us how we can use Underscore.js to create an event dispatcher, i.e. a component on which we can bind and trigger events:

var events = _.clone(Backbone.Events);

With this little bit of functionality we can let the success callback trigger an event instead of calling methods. We can also declare in the constructor which methods we want to call when the event is triggered:

+var events = _.clone(Backbone.Events);
+
 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

+    events.on('status:add', this.appendStatus, this);
+    events.on('status:add', this.clearInput, this);
+
     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    var that = this;
-
     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
-            that.appendStatus(data.text);
-            that.clearInput();
+            events.trigger('status:add', data.text);
         }
     });
 };
 NewStatusView.prototype.appendStatus = function(text) {
     $('#statuses ul').append('<li>' + text + '</li>');
 };
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Now we can declare in the constructor what we want to happen when a status is added, instead of addStatus being responsible for handling success. The only responsibility addStatus should have is backend communication, not updating the DOM.

As we no longer deal with the view in the success callback we can move the triggering of the event into the add method onStatuses:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
-Statuses.prototype.add = function(options) {
+Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
-        data: { text: options.text },
-        success: options.success
+        data: { text: text },
+        success: function(data) {
+            events.trigger('status:add', data.text);
+        }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

     events.on('status:add', this.appendStatus, this);
     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add({
-        text: $('#new-status textarea').val(),
-        success: function(data) {
-            events.trigger('status:add', data.text);
-        }
-    });
+    this.statuses.add($('#new-status textarea').val());
 };
 NewStatusView.prototype.appendStatus = function(text) {
     $('#statuses ul').append('<li>' + text + '</li>');
 };
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

A view’s responsibilities

Looking at appendStatus and clearInput in NewStatusView, we see that these methods focus on two different DOM elements,#statuses and #new-status, respectively. I’ve given them different colors in the app, so you can see the difference. Working on two elements in a view does not adhere to the principles I outline in my blog post on a view’s responsibilities. Let’s pull aStatusesView out of NewStatusView, and let it be responsible for #statuses. Separating these responsibilities is especially simple now that we use events — with a regular success callback this would be far more difficult.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

-    events.on('status:add', this.appendStatus, this);
     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add($('#new-status textarea').val());
 };
-NewStatusView.prototype.appendStatus = function(text) {
-    $('#statuses ul').append('<li>' + text + '</li>');
-};
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

+var StatusesView = function() {
+    events.on('status:add', this.appendStatus, this);
+};
+StatusesView.prototype.appendStatus = function(text) {
+    $('#statuses ul').append('<li>' + text + '</li>');
+};
+
 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
+    new StatusesView();
 });

As each view is now responsible for only one HTML element, we can specify them when instantiating the views:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
+    this.el = $('#new-status');

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
-    $('#new-status form').submit(add);
+    this.el.find('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add($('#new-status textarea').val());
+    this.statuses.add(this.el.find('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
-    $('#new-status textarea').val('');
+    this.el.find('textarea').val('');
 };

 var StatusesView = function() {
+    this.el = $('#statuses');
+
     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
-    $('#statuses ul').append('<li>' + text + '</li>');
+    this.el.find('ul').append('<li>' + text + '</li>');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
     new StatusesView();
 });

Our views, NewStatusView and StatusesView are still difficult to test because they depend on having the HTML present, e.g. in order to find $('#statuses'). To remedy this we can pass in its DOM dependencies when we instantiate a view.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
-    this.el = $('#new-status');
+    this.el = options.el;

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     this.el.find('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add(this.el.find('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
     this.el.find('textarea').val('');
 };

-var StatusesView = function() {
-    this.el = $('#statuses');
+var StatusesView = function(options) {
+    this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
     this.el.find('ul').append('<li>' + text + '</li>');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
-    new NewStatusView({ statuses: statuses });
-    new StatusesView();
+    new NewStatusView({ el: $('#new-status'), statuses: statuses });
+    new StatusesView({ el: $('#statuses') });
 });

Now, this is easy to test! With this change we can use a jQuery trick to test our views. Instead of initiating our views by passing in for example $('#new-status'), we can pass in the necessary HTML wrapped in jQuery, e.g. $('<div><form>…</form></div>'). jQuery will then create the needed DOM elements on the fly. This ensures blazingly fast tests — on my current project our nearly 200 tests run in less than one second.

Our next step is introducing a helper to clean up our views a little bit. Instead of writing this.el.find we can create a simple helper so we can write this.$ instead. With this little change it feels like we are saying, I want to use jQuery to look for something locally on this view instead of globally in the entire HTML. And it’s so easy to add:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
     this.el = options.el;

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
-    this.el.find('form').submit(add);
+    this.$('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add(this.el.find('textarea').val());
+    this.statuses.add(this.$('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
-    this.el.find('textarea').val('');
+    this.$('textarea').val('');
 };
+NewStatusView.prototype.$ = function(selector) {
+    return this.el.find(selector);
+};

 var StatusesView = function(options) {
     this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
-    this.el.find('ul').append('<li>' + text + '</li>');
+    this.$('ul').append('<li>' + text + '</li>');
 };
+StatusesView.prototype.$ = function(selector) {
+    return this.el.find(selector);
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

However, adding this functionality for every view is a pain. That’s one of the reasons to use Backbone.js views — reusing functionality across views.

Getting started with views in Backbone

With the current state of our code, it’s just a couple of lines of change needed to add Backbone.js views:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

-var NewStatusView = function(options) {
-    this.statuses = options.statuses;
-    this.el = options.el;
-
-    events.on('status:add', this.clearInput, this);
-
-    var add = $.proxy(this.addStatus, this);
-    this.$('form').submit(add);
-};
+var NewStatusView = Backbone.View.extend({
+    initialize: function(options) {
+        this.statuses = options.statuses;
+        this.el = options.el;
+
+        events.on('status:add', this.clearInput, this);
+
+        var add = $.proxy(this.addStatus, this);
+        this.$('form').submit(add);
+    }
+});
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add(this.$('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
     this.$('textarea').val('');
 };
 NewStatusView.prototype.$ = function(selector) {
     return this.el.find(selector);
 };

 var StatusesView = function(options) {
     this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
     this.$('ul').append('<li>' + text + '</li>');
 };
 StatusesView.prototype.$ = function(selector) {
     return this.el.find(selector);
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

As you can see from the code, we use Backbone.View.extend to create a new view class in Backbone. Within extend we can specify instance methods such as initialize, which is the name of the constructor.

Now that we have started the move over to Backbone.js views, let’s go on and move both views fully over:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;
         this.el = options.el;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
-    }
+    },
+
+    addStatus: function(e) {
+        e.preventDefault();
+
+        this.statuses.add(this.$('textarea').val());
+    },
+
+    clearInput: function() {
+        this.$('textarea').val('');
+    },
+
+    $: function(selector) {
+        return this.el.find(selector);
+    }
 });
-NewStatusView.prototype.addStatus = function(e) {
-    e.preventDefault();
-
-    this.statuses.add(this.$('textarea').val());
-};
-NewStatusView.prototype.clearInput = function() {
-    this.$('textarea').val('');
-};
-NewStatusView.prototype.$ = function(selector) {
-    return this.el.find(selector);
-};

-var StatusesView = function(options) {
-    this.el = options.el;
-
-    events.on('status:add', this.appendStatus, this);
-};
-StatusesView.prototype.appendStatus = function(text) {
-    this.$('ul').append('<li>' + text + '</li>');
-};
-StatusesView.prototype.$ = function(selector) {
-    return this.el.find(selector);
-};
+var StatusesView = Backbone.View.extend({
+    initialize: function(options) {
+        this.el = options.el;
+
+        events.on('status:add', this.appendStatus, this);
+    },
+
+    appendStatus: function(text) {
+        this.$('ul').append('<li>' + text + '</li>');
+    },
+
+    $: function(selector) {
+        return this.el.find(selector);
+    }
+});

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Now that we use Backbone.js views we can remove the this.$ helper, as it already exists in Backbone. We also no longer need to set this.el ourselves, as Backbone.js does it automatically when a view is instantiated with an HTML element.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;
-        this.el = options.el;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     },
-
-    $: function(selector) {
-        return this.el.find(selector);
-    }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
-        this.el = options.el;
-
         events.on('status:add', this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     },
-
-    $: function(selector) {
-        return this.el.find(selector);
-    }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Let’s use a model

The next step is introducing models, which are responsible for the network traffic, i.e. Ajax requests and responses. As Backbone nicely abstracts Ajax, we don’t need to specify typedataType and data anymore. Now we only need to specify the URL and call saveon the model. The save method accepts the data we want to save as the first parameter, and options, such as the successcallback, as the second parameter.

 var events = _.clone(Backbone.Events);

+var Status = Backbone.Model.extend({
+    url: '/status'
+});
+
 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
-    $.ajax({
-        url: '/status',
-        type: 'POST',
-        dataType: 'json',
-        data: { text: text },
-        success: function(data) {
-            events.trigger('status:add', data.text);
-        }
-    });
+    var status = new Status();
+    status.save({ text: text }, {
+        success: function(model, data) {
+            events.trigger('status:add', data.text);
+        }
+    });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         events.on('status:add', this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Handling several models

Now that we have introduced models, we need a concept for a list of models, such as the list of statuses in our application. In Backbone.js this concept is called a collection.

One really cool thing about collections is that they have scoped events. Basically, this just means that we can bind and trigger events directly on a collection instead of using our events variable — our events will live on statuses instead of events. As we now start firing events directly on statuses there’s no need for “status” in the event name, so we rename it from “status:add” to “add”.

-var events = _.clone(Backbone.Events);
-
 var Status = Backbone.Model.extend({
     url: '/status'
 });

-var Statuses = function() {
-};
-Statuses.prototype.add = function(text) {
-    var status = new Status();
-    status.save({ text: text }, {
-        success: function(model, data) {
-            events.trigger("status:add", data.text);
-        }
-    });
-};
+var Statuses = Backbone.Collection.extend({
+    add: function(text) {
+        var that = this;
+        var status = new Status();
+        status.save({ text: text }, {
+            success: function(model, data) {
+                that.trigger("add", data.text);
+            }
+        });
+    }
+});

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

-        events.on("status:add", this.clearInput, this);
+        this.statuses.on("add", this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
+        this.statuses = options.statuses;
+
-        events.on("status:add", this.appendStatus, this);
+        this.statuses.on("add", this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
-    new StatusesView({ el: $('#statuses') });
+    new StatusesView({ el: $('#statuses'), statuses: statuses });
 });

We can simplify this even more by using Backbone’s create method. It creates a new model instance, adds it to the collection and saves it to the server. Therefore we must specify what type of model the collection should handle. There are two things we need to change to use Backbone collections:

  1. When creating the status we need to pass in an options hash with the attributes we want to save, instead of only passing the text.
  2. The built in create also triggers an “add” event, but rather than passing only the text, as we have done so far, it passes the newly created model. We can get the text from the model by calling model.get("text").
 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
-    add: function(text) {
-        var that = this;
-        var status = new Status();
-        status.save({ text: text }, {
-            success: function(model, data) {
-                that.trigger("add", data.text);
-            }
-        });
-    }
+    model: Status
 });

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         this.statuses.on("add", this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

-        this.statuses.add(this.$('textarea').val());
+        this.statuses.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         this.statuses.on("add", this.appendStatus, this);
     },

-    appendStatus: function(text) {
+    appendStatus: function(status) {
-        this.$('ul').append('<li>' + text + '</li>');
+        this.$('ul').append('<li>' + status.get("text") + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses'), statuses: statuses });
 });

As with el earlier, Backbone.js automatically sets this.collection when collection is passed. Therefore we renamestatuses to collection in our view:

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
-    initialize: function(options) {
+    initialize: function() {
-        this.statuses = options.statuses;
-
-        this.statuses.on('add', this.clearInput, this);
+        this.collection.on('add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

-        this.statuses.add({ text: this.$('textarea').val() });
+        this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
-    initialize: function(options) {
+    initialize: function() {
-        this.statuses = options.statuses;
-
-        this.statuses.on('add', this.appendStatus, this);
+        this.collection.on('add', this.appendStatus, this);
     },

     appendStatus: function(status) {
         this.$('ul').append('<li>' + status.get('text') + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
-    new NewStatusView({ el: $('#new-status'), statuses: statuses });
+    new NewStatusView({ el: $('#new-status'), collection: statuses });
-    new StatusesView({ el: $('#statuses'), statuses: statuses });
+    new StatusesView({ el: $('#statuses'), collection: statuses });
 });

Evented views

Now, let’s get rid of that nasty $.proxy stuff. We can do this by letting Backbone.js delegate our events by specifying them in anevents hash in the view. This hash is on the format {"event selector": "callback"}:

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
+    events: {
+        'submit form': 'addStatus'
+    },
+
     initialize: function() {
         this.collection.on('add', this.clearInput, this);
-
-        var add = $.proxy(this.addStatus, this);
-        this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function() {
         this.collection.on('add', this.appendStatus, this);
     },

     appendStatus: function(status) {
         this.$('ul').append('<li>' + status.get('text') + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), collection: statuses });
     new StatusesView({ el: $('#statuses'), collection: statuses });
 });

Escape it!

As our last step we’re going to prevent XSS-exploits. Instead of using model.get('text') let’s use the built-in escape handling and write model.escape('text') instead. If you’re using HandlebarsMustache or similar templating engines, you might get this functionality out of the box.

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
     events: {
         "submit form": "addStatus"
     },

     initialize: function(options) {
         this.collection.on("add", this.clearInput, this);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         this.collection.on("add", this.appendStatus, this);
     },

     appendStatus: function(status) {
-        this.$('ul').append('<li>' + status.get("text") + '</li>');
+        this.$('ul').append('<li>' + status.escape("text") + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), collection: statuses });
     new StatusesView({ el: $('#statuses'), collection: statuses });
 });

And we’re done!

This is our final code:

var Status = Backbone.Model.extend({
    url: '/status'
});

var Statuses = Backbone.Collection.extend({
    model: Status
});

var NewStatusView = Backbone.View.extend({
    events: {
        'submit form': 'addStatus'
    },

    initialize: function() {
        this.collection.on('add', this.clearInput, this);
    },

    addStatus: function(e) {
        e.preventDefault();

        this.collection.create({ text: this.$('textarea').val() });
    },

    clearInput: function() {
        this.$('textarea').val('');
    }
});

var StatusesView = Backbone.View.extend({
    initialize: function() {
        this.collection.on('add', this.appendStatus, this);
    },

    appendStatus: function(status) {
        this.$('ul').append('<li>' + status.escape('text') + '</li>');
    }
});

$(document).ready(function() {
    var statuses = new Statuses();
    new NewStatusView({ el: $('#new-status'), collection: statuses });
    new StatusesView({ el: $('#statuses'), collection: statuses });
});

And here’s the application running with the refactored code. Yeah, it’s still the exact same application from a user’s point of view.

However, the code has increased from 16 lines to more than 40, so why do I think this is better? Because we are now working on a higher level of abstraction. This code is more maintainable, easier to reuse and extend, and easier to test. What I’ve seen is that Backbone.js helps improve the structure of my JavaScript applications considerably, and in my experience the end result is often less complex and has fewer lines of code than my “regular JavaScript”.

Want to learn more?

By now you should understand far more of Backbone.js than when you did an hour ago. There are some great resources for learning Backbone.js out there, but there’s also a whole lot of crap. Actually, the Backbone.js documentation is superb as soon as you have a better understanding of how the framework works at a higher level. Addy Osmani’s Developing Backbone.js Applications is another great source.

Reading the annotated source of Backbone.js is also a great way to get a better understanding of how it works. I found this to be an enlightening experience when I had used Backbone.js for some weeks. The Backbone.js example on TodoMVC is another great way to learn more.

And, lastly, if you want to get a better understanding of the creation of a framework such as Backbone.js, I recommend the magnificentJavaScript Web Applications by Alex MacCaw.

Step by step from jQuery to Backbone

I’ve seen many struggle when they first meet Backbone.js. In this blog post I will gradually refactor a bit of code from how I used to write JavaScript before, into proper Backbone.js code using models, collections, views and events. Hopefully this process will give you a firm understanding of the core abstractions in Backbone.js.

Let’s start with the code we’re going to work with throughout this blog post:

$(document).ready(function() {
    $('#new-status').submit(function(e) {
        e.preventDefault();

        $.ajax({
            url: '/status',
            type: 'POST',
            dataType: 'json',
            data: { text: $(this).find('textarea').val() },
            success: function(data) {
                $('#statuses').append('<li>' + data.text + '</li>');
                $(this).find('textarea').val('');
            }
        });
    });
});

And here’s the application up and running: Monologue. This simple application is based on a great JavaScript presentation by Brandon Keepers. However, while his focus is primarily on testing and design patterns — of which he does an amazing job — mine is on explaining the core Backbone.js abstractions one step at a time.

Basically, the app lets you to write some text, click on “Post”, and see what you’ve written show up in the list while the textarea is prepared for new input. Translating this to code, we start by waiting for the DOM to load, set a submit listener, and when the form is submitted we send the input to the server, append the response to the list of statuses and reset the input.

But, what’s the problem? This code does a lot of stuff at the same time. It listens for page events, user events, network events, it does network IO, handles user input, parses the response, and does HTML templating. All in 16 lines of code. It looks like most JavaScript code I wrote a year ago. Throughout this blog post I will gradually work my way to code that follows the single responsibility principle, and which is far easier to test, maintain, reuse and extend.

There are three things we want to achieve:

  • We want to move as much as possible out of $(document).ready, so we can trigger the code ourselves — we only want to bootstrap the application when the DOM is ready. In its current state the code is nearly impossible to test.
  • We want to adhere to the single responsibility principle, and make the code more reusable and easier to test.
  • We want to break the coupling between the DOM and Ajax.

Separating DOM and Ajax

We start with splitting Ajax and DOM from each other, and the first step is to create an addStatus function:

+function addStatus(options) {
+    $.ajax({
+        url: '/status',
+        type: 'POST',
+        dataType: 'json',
+        data: { text: $('#new-status textarea').val() },
+        success: function(data) {
+            $('#statuses ul').append('<li>' + data.text + '</li>');
+            $('#new-status textarea').val('');
+        }
+    });
+}
+
 $(document).ready(function() {
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        $.ajax({
-            url: '/status',
-            type: 'POST',
-            dataType: 'json',
-            data: { text: $('#new-status textarea').val() },
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
+        addStatus();
     });
 });

However, both within data and success we work with the DOM. We can break this coupling by sending these as arguments toaddStatus:

 function addStatus(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
-        data: { text: $('#new-status textarea').val() },
-        success: function(data) {
-            $('#statuses ul').append('<li>' + data.text + '</li>');
-            $('#new-status textarea').val('');
-        }
+        data: { text: options.text },
+        success: options.success
     });
 }

 $(document).ready(function() {
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        addStatus();
+        addStatus({
+            text: $('#new-status textarea').val(),
+            success: function(data) {
+                $('#statuses ul').append('<li>' + data.text + '</li>');
+                $('#new-status textarea').val('');
+            }
+        });
     });
 });

However, I want to wrap these statuses in an object and be able to write statuses.add instead of addStatus. To achieve this we can use the constructor pattern with prototypes to introduce a Statuses “class”:

-function addStatus(options) {
+var Statuses = function() {
+};
+Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
-}
+};

 $(document).ready(function() {
+    var statuses = new Statuses();
+
     $('#new-status form').submit(function(e) {
         e.preventDefault();

-        addStatus({
+        statuses.add({
             text: $('#new-status textarea').val(),
             success: function(data) {
                 $('#statuses ul').append('<li>' + data.text + '</li>');
                 $('#new-status textarea').val('');
             }
         });
     });
 });

Creating a view

Our submit handler now has one dependency, the statuses variable, and everything else within it is focused on the DOM. Let’s move the submit handler and everything inside it into its own class, NewStatusView:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

+var NewStatusView = function(options) {
+    var statuses = options.statuses;
+
+    $('#new-status form').submit(function(e) {
+        e.preventDefault();
+
+        statuses.add({
+            text: $('#new-status textarea').val(),
+            success: function(data) {
+                $('#statuses ul').append('<li>' + data.text + '</li>');
+                $('#new-status textarea').val('');
+            }
+        });
+    });
+};
+
 $(document).ready(function() {
     var statuses = new Statuses();

-    $('#new-status form').submit(function(e) {
-        e.preventDefault();
-
-        statuses.add({
-            text: $('#new-status textarea').val(),
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
-    });
+    new NewStatusView({ statuses: statuses });
 });

Now we only bootstrap our application when the DOM is loaded, and everything else is moved out of $(document).ready. The steps we have taken so far has given us two components which are easier to test and have more well-defined responsibilities. However, there is still much to clean up. Let’s start by splitting the submit handler in NewStatusView into its own addStatus method:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
-    var statuses = options.statuses;
+    this.statuses = options.statuses;

-    $('#new-status form').submit(function(e) {
-        e.preventDefault();
-        statuses.add({
-            text: $('#new-status textarea').val(),
-            success: function(data) {
-                $('#statuses ul').append('<li>' + data.text + '</li>');
-                $('#new-status textarea').val('');
-            }
-        });
-    });
+    $('#new-status form').submit(this.addStatus);
 };
+NewStatusView.prototype.addStatus = function(e) {
+    e.preventDefault();
+
+    this.statuses.add({
+        text: $('#new-status textarea').val(),
+        success: function(data) {
+            $('#statuses ul').append('<li>' + data.text + '</li>');
+            $('#new-status textarea').val('');
+        }
+    });
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Running this in Chrome we get this error:

Uncaught TypeError: Cannot call method 'add' of undefined

We get this error as this means different things in the constructor and the addStatus method, as it is jQuery that actually calls the latter when the user submits the form. (If you don’t fully grasp how this works, I recommend reading Understanding JavaScript Function Invocation and “this”.) To solve this problem we can use $.proxy, which creates a function where this is always the same — the context you specify as the second argument.

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

-    $('#new-status form').submit(this.addStatus);
+    var add = $.proxy(this.addStatus, this);
+    $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
             $('#statuses ul').append('<li>' + data.text + '</li>');
             $('#new-status textarea').val('');
         }
     });
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Let’s make the success callback call methods instead of working directly on the DOM, which makes the callback easier to read and work with:

 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

+    var that = this;
+
     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
-            $('#statuses ul').append('<li>' + data.text + '</li>');
-            $('#new-status textarea').val('');
+            that.appendStatus(data.text);
+            that.clearInput();
         }
     });
 };
+NewStatusView.prototype.appendStatus = function(text) {
+    $('#statuses ul').append('<li>' + text + '</li>');
+};
+NewStatusView.prototype.clearInput = function() {
+    $('#new-status textarea').val('');
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

This is much easier to test and to work with as our code grows. We are also far on our way to understand Backbone.js.

Adding events

For the next step we need to introduce our first bit of Backbone: events. Events are basically just a way to say: “Hi, I want to know when some action occurs” and “Hi, you know what? The action you’re waiting for just occurred!” We are used to this idea from jQuery DOM events such as listening for click and submit, e.g.

$('form').bind('submit', function() {
  alert('User submitted form'); 
});

The Backbone.js documentation describes Backbone.Events as follows: “Events is a module that can be mixed in to any object, giving the object the ability to bind and trigger custom named events.” The docs also shows us how we can use Underscore.js to create an event dispatcher, i.e. a component on which we can bind and trigger events:

var events = _.clone(Backbone.Events);

With this little bit of functionality we can let the success callback trigger an event instead of calling methods. We can also declare in the constructor which methods we want to call when the event is triggered:

+var events = _.clone(Backbone.Events);
+
 var Statuses = function() {
 };
 Statuses.prototype.add = function(options) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

+    events.on('status:add', this.appendStatus, this);
+    events.on('status:add', this.clearInput, this);
+
     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    var that = this;
-
     this.statuses.add({
         text: $('#new-status textarea').val(),
         success: function(data) {
-            that.appendStatus(data.text);
-            that.clearInput();
+            events.trigger('status:add', data.text);
         }
     });
 };
 NewStatusView.prototype.appendStatus = function(text) {
     $('#statuses ul').append('<li>' + text + '</li>');
 };
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

Now we can declare in the constructor what we want to happen when a status is added, instead of addStatus being responsible for handling success. The only responsibility addStatus should have is backend communication, not updating the DOM.

As we no longer deal with the view in the success callback we can move the triggering of the event into the add method onStatuses:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
-Statuses.prototype.add = function(options) {
+Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
-        data: { text: options.text },
-        success: options.success
+        data: { text: text },
+        success: function(data) {
+            events.trigger('status:add', data.text);
+        }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

     events.on('status:add', this.appendStatus, this);
     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add({
-        text: $('#new-status textarea').val(),
-        success: function(data) {
-            events.trigger('status:add', data.text);
-        }
-    });
+    this.statuses.add($('#new-status textarea').val());
 };
 NewStatusView.prototype.appendStatus = function(text) {
     $('#statuses ul').append('<li>' + text + '</li>');
 };
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
 });

A view’s responsibilities

Looking at appendStatus and clearInput in NewStatusView, we see that these methods focus on two different DOM elements,#statuses and #new-status, respectively. I’ve given them different colors in the app, so you can see the difference. Working on two elements in a view does not adhere to the principles I outline in my blog post on a view’s responsibilities. Let’s pull aStatusesView out of NewStatusView, and let it be responsible for #statuses. Separating these responsibilities is especially simple now that we use events — with a regular success callback this would be far more difficult.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;

-    events.on('status:add', this.appendStatus, this);
     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     $('#new-status form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add($('#new-status textarea').val());
 };
-NewStatusView.prototype.appendStatus = function(text) {
-    $('#statuses ul').append('<li>' + text + '</li>');
-};
 NewStatusView.prototype.clearInput = function() {
     $('#new-status textarea').val('');
 };

+var StatusesView = function() {
+    events.on('status:add', this.appendStatus, this);
+};
+StatusesView.prototype.appendStatus = function(text) {
+    $('#statuses ul').append('<li>' + text + '</li>');
+};
+
 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
+    new StatusesView();
 });

As each view is now responsible for only one HTML element, we can specify them when instantiating the views:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
+    this.el = $('#new-status');

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
-    $('#new-status form').submit(add);
+    this.el.find('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add($('#new-status textarea').val());
+    this.statuses.add(this.el.find('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
-    $('#new-status textarea').val('');
+    this.el.find('textarea').val('');
 };

 var StatusesView = function() {
+    this.el = $('#statuses');
+
     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
-    $('#statuses ul').append('<li>' + text + '</li>');
+    this.el.find('ul').append('<li>' + text + '</li>');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ statuses: statuses });
     new StatusesView();
 });

Our views, NewStatusView and StatusesView are still difficult to test because they depend on having the HTML present, e.g. in order to find $('#statuses'). To remedy this we can pass in its DOM dependencies when we instantiate a view.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
-    this.el = $('#new-status');
+    this.el = options.el;

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
     this.el.find('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add(this.el.find('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
     this.el.find('textarea').val('');
 };

-var StatusesView = function() {
-    this.el = $('#statuses');
+var StatusesView = function(options) {
+    this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
     this.el.find('ul').append('<li>' + text + '</li>');
 };

 $(document).ready(function() {
     var statuses = new Statuses();
-    new NewStatusView({ statuses: statuses });
-    new StatusesView();
+    new NewStatusView({ el: $('#new-status'), statuses: statuses });
+    new StatusesView({ el: $('#statuses') });
 });

Now, this is easy to test! With this change we can use a jQuery trick to test our views. Instead of initiating our views by passing in for example $('#new-status'), we can pass in the necessary HTML wrapped in jQuery, e.g. $('<div><form>…</form></div>'). jQuery will then create the needed DOM elements on the fly. This ensures blazingly fast tests — on my current project our nearly 200 tests run in less than one second.

Our next step is introducing a helper to clean up our views a little bit. Instead of writing this.el.find we can create a simple helper so we can write this.$ instead. With this little change it feels like we are saying, I want to use jQuery to look for something locally on this view instead of globally in the entire HTML. And it’s so easy to add:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = function(options) {
     this.statuses = options.statuses;
     this.el = options.el;

     events.on('status:add', this.clearInput, this);

     var add = $.proxy(this.addStatus, this);
-    this.el.find('form').submit(add);
+    this.$('form').submit(add);
 };
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

-    this.statuses.add(this.el.find('textarea').val());
+    this.statuses.add(this.$('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
-    this.el.find('textarea').val('');
+    this.$('textarea').val('');
 };
+NewStatusView.prototype.$ = function(selector) {
+    return this.el.find(selector);
+};

 var StatusesView = function(options) {
     this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
-    this.el.find('ul').append('<li>' + text + '</li>');
+    this.$('ul').append('<li>' + text + '</li>');
 };
+StatusesView.prototype.$ = function(selector) {
+    return this.el.find(selector);
+};

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

However, adding this functionality for every view is a pain. That’s one of the reasons to use Backbone.js views — reusing functionality across views.

Getting started with views in Backbone

With the current state of our code, it’s just a couple of lines of change needed to add Backbone.js views:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

-var NewStatusView = function(options) {
-    this.statuses = options.statuses;
-    this.el = options.el;
-
-    events.on('status:add', this.clearInput, this);
-
-    var add = $.proxy(this.addStatus, this);
-    this.$('form').submit(add);
-};
+var NewStatusView = Backbone.View.extend({
+    initialize: function(options) {
+        this.statuses = options.statuses;
+        this.el = options.el;
+
+        events.on('status:add', this.clearInput, this);
+
+        var add = $.proxy(this.addStatus, this);
+        this.$('form').submit(add);
+    }
+});
 NewStatusView.prototype.addStatus = function(e) {
     e.preventDefault();

     this.statuses.add(this.$('textarea').val());
 };
 NewStatusView.prototype.clearInput = function() {
     this.$('textarea').val('');
 };
 NewStatusView.prototype.$ = function(selector) {
     return this.el.find(selector);
 };

 var StatusesView = function(options) {
     this.el = options.el;

     events.on('status:add', this.appendStatus, this);
 };
 StatusesView.prototype.appendStatus = function(text) {
     this.$('ul').append('<li>' + text + '</li>');
 };
 StatusesView.prototype.$ = function(selector) {
     return this.el.find(selector);
 };

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

As you can see from the code, we use Backbone.View.extend to create a new view class in Backbone. Within extend we can specify instance methods such as initialize, which is the name of the constructor.

Now that we have started the move over to Backbone.js views, let’s go on and move both views fully over:

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;
         this.el = options.el;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
-    }
+    },
+
+    addStatus: function(e) {
+        e.preventDefault();
+
+        this.statuses.add(this.$('textarea').val());
+    },
+
+    clearInput: function() {
+        this.$('textarea').val('');
+    },
+
+    $: function(selector) {
+        return this.el.find(selector);
+    }
 });
-NewStatusView.prototype.addStatus = function(e) {
-    e.preventDefault();
-
-    this.statuses.add(this.$('textarea').val());
-};
-NewStatusView.prototype.clearInput = function() {
-    this.$('textarea').val('');
-};
-NewStatusView.prototype.$ = function(selector) {
-    return this.el.find(selector);
-};

-var StatusesView = function(options) {
-    this.el = options.el;
-
-    events.on('status:add', this.appendStatus, this);
-};
-StatusesView.prototype.appendStatus = function(text) {
-    this.$('ul').append('<li>' + text + '</li>');
-};
-StatusesView.prototype.$ = function(selector) {
-    return this.el.find(selector);
-};
+var StatusesView = Backbone.View.extend({
+    initialize: function(options) {
+        this.el = options.el;
+
+        events.on('status:add', this.appendStatus, this);
+    },
+
+    appendStatus: function(text) {
+        this.$('ul').append('<li>' + text + '</li>');
+    },
+
+    $: function(selector) {
+        return this.el.find(selector);
+    }
+});

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Now that we use Backbone.js views we can remove the this.$ helper, as it already exists in Backbone. We also no longer need to set this.el ourselves, as Backbone.js does it automatically when a view is instantiated with an HTML element.

 var events = _.clone(Backbone.Events);

 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
     $.ajax({
         url: '/status',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
             events.trigger('status:add', data.text);
         }
     });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;
-        this.el = options.el;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     },
-
-    $: function(selector) {
-        return this.el.find(selector);
-    }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
-        this.el = options.el;
-
         events.on('status:add', this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     },
-
-    $: function(selector) {
-        return this.el.find(selector);
-    }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Let’s use a model

The next step is introducing models, which are responsible for the network traffic, i.e. Ajax requests and responses. As Backbone nicely abstracts Ajax, we don’t need to specify typedataType and data anymore. Now we only need to specify the URL and call saveon the model. The save method accepts the data we want to save as the first parameter, and options, such as the successcallback, as the second parameter.

 var events = _.clone(Backbone.Events);

+var Status = Backbone.Model.extend({
+    url: '/status'
+});
+
 var Statuses = function() {
 };
 Statuses.prototype.add = function(text) {
-    $.ajax({
-        url: '/status',
-        type: 'POST',
-        dataType: 'json',
-        data: { text: text },
-        success: function(data) {
-            events.trigger('status:add', data.text);
-        }
-    });
+    var status = new Status();
+    status.save({ text: text }, {
+        success: function(model, data) {
+            events.trigger('status:add', data.text);
+        }
+    });
 };

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         events.on('status:add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         events.on('status:add', this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses') });
 });

Handling several models

Now that we have introduced models, we need a concept for a list of models, such as the list of statuses in our application. In Backbone.js this concept is called a collection.

One really cool thing about collections is that they have scoped events. Basically, this just means that we can bind and trigger events directly on a collection instead of using our events variable — our events will live on statuses instead of events. As we now start firing events directly on statuses there’s no need for “status” in the event name, so we rename it from “status:add” to “add”.

-var events = _.clone(Backbone.Events);
-
 var Status = Backbone.Model.extend({
     url: '/status'
 });

-var Statuses = function() {
-};
-Statuses.prototype.add = function(text) {
-    var status = new Status();
-    status.save({ text: text }, {
-        success: function(model, data) {
-            events.trigger("status:add", data.text);
-        }
-    });
-};
+var Statuses = Backbone.Collection.extend({
+    add: function(text) {
+        var that = this;
+        var status = new Status();
+        status.save({ text: text }, {
+            success: function(model, data) {
+                that.trigger("add", data.text);
+            }
+        });
+    }
+});

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

-        events.on("status:add", this.clearInput, this);
+        this.statuses.on("add", this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.statuses.add(this.$('textarea').val());
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
+        this.statuses = options.statuses;
+
-        events.on("status:add", this.appendStatus, this);
+        this.statuses.on("add", this.appendStatus, this);
     },

     appendStatus: function(text) {
         this.$('ul').append('<li>' + text + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
-    new StatusesView({ el: $('#statuses') });
+    new StatusesView({ el: $('#statuses'), statuses: statuses });
 });

We can simplify this even more by using Backbone’s create method. It creates a new model instance, adds it to the collection and saves it to the server. Therefore we must specify what type of model the collection should handle. There are two things we need to change to use Backbone collections:

  1. When creating the status we need to pass in an options hash with the attributes we want to save, instead of only passing the text.
  2. The built in create also triggers an “add” event, but rather than passing only the text, as we have done so far, it passes the newly created model. We can get the text from the model by calling model.get("text").
 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
-    add: function(text) {
-        var that = this;
-        var status = new Status();
-        status.save({ text: text }, {
-            success: function(model, data) {
-                that.trigger("add", data.text);
-            }
-        });
-    }
+    model: Status
 });

 var NewStatusView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         this.statuses.on("add", this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

-        this.statuses.add(this.$('textarea').val());
+        this.statuses.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         this.statuses = options.statuses;

         this.statuses.on("add", this.appendStatus, this);
     },

-    appendStatus: function(text) {
+    appendStatus: function(status) {
-        this.$('ul').append('<li>' + text + '</li>');
+        this.$('ul').append('<li>' + status.get("text") + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), statuses: statuses });
     new StatusesView({ el: $('#statuses'), statuses: statuses });
 });

As with el earlier, Backbone.js automatically sets this.collection when collection is passed. Therefore we renamestatuses to collection in our view:

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
-    initialize: function(options) {
+    initialize: function() {
-        this.statuses = options.statuses;
-
-        this.statuses.on('add', this.clearInput, this);
+        this.collection.on('add', this.clearInput, this);

         var add = $.proxy(this.addStatus, this);
         this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

-        this.statuses.add({ text: this.$('textarea').val() });
+        this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
-    initialize: function(options) {
+    initialize: function() {
-        this.statuses = options.statuses;
-
-        this.statuses.on('add', this.appendStatus, this);
+        this.collection.on('add', this.appendStatus, this);
     },

     appendStatus: function(status) {
         this.$('ul').append('<li>' + status.get('text') + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
-    new NewStatusView({ el: $('#new-status'), statuses: statuses });
+    new NewStatusView({ el: $('#new-status'), collection: statuses });
-    new StatusesView({ el: $('#statuses'), statuses: statuses });
+    new StatusesView({ el: $('#statuses'), collection: statuses });
 });

Evented views

Now, let’s get rid of that nasty $.proxy stuff. We can do this by letting Backbone.js delegate our events by specifying them in anevents hash in the view. This hash is on the format {"event selector": "callback"}:

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
+    events: {
+        'submit form': 'addStatus'
+    },
+
     initialize: function() {
         this.collection.on('add', this.clearInput, this);
-
-        var add = $.proxy(this.addStatus, this);
-        this.$('form').submit(add);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function() {
         this.collection.on('add', this.appendStatus, this);
     },

     appendStatus: function(status) {
         this.$('ul').append('<li>' + status.get('text') + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), collection: statuses });
     new StatusesView({ el: $('#statuses'), collection: statuses });
 });

Escape it!

As our last step we’re going to prevent XSS-exploits. Instead of using model.get('text') let’s use the built-in escape handling and write model.escape('text') instead. If you’re using HandlebarsMustache or similar templating engines, you might get this functionality out of the box.

 var Status = Backbone.Model.extend({
     url: '/status'
 });

 var Statuses = Backbone.Collection.extend({
     model: Status
 });

 var NewStatusView = Backbone.View.extend({
     events: {
         "submit form": "addStatus"
     },

     initialize: function(options) {
         this.collection.on("add", this.clearInput, this);
     },

     addStatus: function(e) {
         e.preventDefault();

         this.collection.create({ text: this.$('textarea').val() });
     },

     clearInput: function() {
         this.$('textarea').val('');
     }
 });

 var StatusesView = Backbone.View.extend({
     initialize: function(options) {
         this.collection.on("add", this.appendStatus, this);
     },

     appendStatus: function(status) {
-        this.$('ul').append('<li>' + status.get("text") + '</li>');
+        this.$('ul').append('<li>' + status.escape("text") + '</li>');
     }
 });

 $(document).ready(function() {
     var statuses = new Statuses();
     new NewStatusView({ el: $('#new-status'), collection: statuses });
     new StatusesView({ el: $('#statuses'), collection: statuses });
 });

And we’re done!

This is our final code:

var Status = Backbone.Model.extend({
    url: '/status'
});

var Statuses = Backbone.Collection.extend({
    model: Status
});

var NewStatusView = Backbone.View.extend({
    events: {
        'submit form': 'addStatus'
    },

    initialize: function() {
        this.collection.on('add', this.clearInput, this);
    },

    addStatus: function(e) {
        e.preventDefault();

        this.collection.create({ text: this.$('textarea').val() });
    },

    clearInput: function() {
        this.$('textarea').val('');
    }
});

var StatusesView = Backbone.View.extend({
    initialize: function() {
        this.collection.on('add', this.appendStatus, this);
    },

    appendStatus: function(status) {
        this.$('ul').append('<li>' + status.escape('text') + '</li>');
    }
});

$(document).ready(function() {
    var statuses = new Statuses();
    new NewStatusView({ el: $('#new-status'), collection: statuses });
    new StatusesView({ el: $('#statuses'), collection: statuses });
});

And here’s the application running with the refactored code. Yeah, it’s still the exact same application from a user’s point of view.

However, the code has increased from 16 lines to more than 40, so why do I think this is better? Because we are now working on a higher level of abstraction. This code is more maintainable, easier to reuse and extend, and easier to test. What I’ve seen is that Backbone.js helps improve the structure of my JavaScript applications considerably, and in my experience the end result is often less complex and has fewer lines of code than my “regular JavaScript”.

Want to learn more?

By now you should understand far more of Backbone.js than when you did an hour ago. There are some great resources for learning Backbone.js out there, but there’s also a whole lot of crap. Actually, the Backbone.js documentation is superb as soon as you have a better understanding of how the framework works at a higher level. Addy Osmani’s Developing Backbone.js Applications is another great source.

Reading the annotated source of Backbone.js is also a great way to get a better understanding of how it works. I found this to be an enlightening experience when I had used Backbone.js for some weeks. The Backbone.js example on TodoMVC is another great way to learn more.

And, lastly, if you want to get a better understanding of the creation of a framework such as Backbone.js, I recommend the magnificentJavaScript Web Applications by Alex MacCaw.

ajax, JavaScript, jquery, Uncategorized

returning value from jquery ajax api


.responseText is used to return the value with ajax api

check_title_exist : function(_title){

return $.ajax({
url: base_url+”themes/theme_title_exist/”,
data:{title:_title},
cache: false,
type:’POST’,
async: false
}).responseText;
}

//php part

function theme_title_exist(){
$title = $this->input->post(‘title’, TRUE);
if($this->themes_model->check_theme_title_exit($title)):
echo ‘true’;
else:
echo ‘false’;
endif;
}

/// java script view part

res = themes_mod.check_title_exist(ni_gui.get_value(_post_data,’title’));

if(res == ‘true’){
alert(1)
}else{
alert(0)
}

JavaScript, jquery

difference between jquery .map and .each


Guys, if performance does matters to your system please read this post. There is a very tiny thing that you should know playing with jquery api

here is the small but pretty nice  tips for you….

source : http://stackoverflow.com/questions/749084/jquery-map-vs-each

 

The each method is meant to be an immutable iterator, where as the map method can be used as an iterator, but is really meant to manipulate the supplied array and return a new array.

Another important thing to note is that the each function returns the original array while the map function returns a new array. If you overuse the return value of the map function you can potentially waste a lot of memory.

For example:

var items = [1,2,3,4];

$.each(items, function() {
  alert('this is ' + this);
});

var newItems = $.map(items, function(i) {
  return i + 1;
});
// newItems is [2,3,4,5]



===================== effective use of map ================

You can also use the map function to remove an item from an array. For example:

var items = [0,1,2,3,4,5,6,7,8,9];

var itemsLessThanEqualFive = $.map(items, function(i) {
  // removes all items > 5
  if (i > 5) 
    return null;
  return i;
});
// itemsLessThanEqualFive = [0,1,2,3,4,5]


You’ll also note that the this is not mapped in the map function. You will have to supply the first parameter in the callback (eg we used i above). Ironically, the callback arguments used in the each method are the reverse of the callback arguments in the map function so be careful.

map(arr, function(elem, index) {});
// versus 
each(arr, function(index, elem) {});



============================================================

1: The arguments to the callback function are reversed.

each()‘s callback function takes the index first, and then the element

function (index, element)

map()‘s callback has the same arguments, but reversed

function (element, index)

2: each() does something special with this

each() calls the function in such a way that this points to the current element. In most cases, you don’t even need the two arguments in the callback function.

function shout() { alert(this + '!') }

result = $.each(['lions', 'tigers', 'bears'], shout)

// result == ['lions', 'tigers', 'bears']

3: map() does something special with the callback’s return value

map() calls the function on each element, and stores the result in a new array, which it returns. You usually only need to use the first argument in the callback function.

function shout(el) { return el + '!' }

result = $.map(['lions', 'tigers', 'bears'], shout)

// result == ['lions!', 'tigers!', 'bears!']

The each function iterates over an array, calling the supplied function once per element, and setting this to the active element. This:
function countdown() {
    alert(this + "..");
}

$([5, 4, 3, 2, 1]).each(countdown);

will alert 5.. then 4.. then 3.. then 2.. then 1..

Map on the other hand takes an array, and returns a new array with each element changed by the function. This:

function squared() {
    return this * this;
}

var s = $([5, 4, 3, 2, 1]).map(squared);

would result in s being [25, 16, 9, 4, 1].

for more information please check jquery api doc