Who developed backbone js

Golo Roden1

Backbone.js is by far the most widely used MVC framework for developing single-page applications - but is it also the best? In order to answer this question, one must first understand and classify the concepts of Backbone.js.

Unlike Knockout.js, Backbone.js does not use the modern design pattern Model View ViewModel (MVVM), but the much older and therefore more widespread Model View Controller (MVC). However, it adapts some aspects of MVC to its individual requirements, i.e. does not implement MVC in its pure form.

The original idea of ​​the MVC design pattern was to add a component to an application that mediates between the representation of the data and the business domain. In this way, inputs can be processed in a bundle and then all relevant views can be updated without having to replicate or distribute the logic required for this.

The core of Backbone.js is the class Backbone.events, from which in turn classes such as Backbone.Model and Backbone.Collection derive. Since it implements the observer pattern, enables Backbone.events a decoupled and at the same time standardized communication within a Backbone.js application.

For inheritance, Backbone.js uses the Underscore.js library, which is standard equipment for many web developers anyway. It is no coincidence that they were chosen: Underscore.js comes from the same author as Backbone.js and emerged from him.

In order to model a domain, Backbone.js knows the two classes already mentioned Backbone.Model and Backbone.Collection: While the former serves to describe individual entities, the latter represents a list of these. The two classes thus form a 1: n relationship.

An entity in Backbone.js can contain not only data but also business functions; the same applies to an entity list. However, Backbone.js does not support references between entities: It is up to the developer to model relationships either by manually managing IDs or using aggregation.

Backbone.js also contains the class Backbone.Viewused to display entities and lists. However, a view only encapsulates access to the web browser; Backbone.js does not actually update the graphical user interface. For this, the developer has to use a library such as jQuery.

implementation

Implement the concepts

In order to use Backbone.js, the appropriate script file must first be downloaded from the library website and integrated. Since Backbone.js uses Underscore.js, among other things, for inheritance, the same applies to them. In addition, the documentation of Backbone.js recommends the use of JSON3 and jQuery:

<!doctype html>
<html>
<head>
<title>Backbone.js-Demo</title>
</head>
<body>
<script type='text/javascript' src='json3.min.js'></script>
<script type='text/javascript' src='jquery-2.0.3.min.js'></script>
<script type='text/javascript' src='underscore-min.js'></script>
<script type='text/javascript' src='backbone.js'></script>
</body>
</html>

Then you can take the two classes Backbone.Model and Backbone.Collection to model the domain using entities and lists. The starting point for this is always the derivation of one of the two classes, whereby the modeling usually starts with an entity:

var Customer = Backbone.Model.extend ({
defaults: {
first name: '',
lastName: ''
}
});

The defaultsProperty defines the default values ​​of a new entity. Customer does not yet represent such, but only their blueprint. To create a concrete entity, you have to Customer call as constructor. You can pass the values ​​of the entity as a parameter object:

var customer = new Customer ({
firstName: 'Golo',
lastName: 'Roden',
age: 34
});

In order to be able to access the values ​​again at a later point in time, Backbone.js provides the two functions get and set to disposal:

console.log (customer.get ('firstName')); // => 'Golo'
customer.set ('age', customer.get ('age') + 1);

If you want to set several values ​​at the same time, the set-Function pass a parameter object, similar to calling the constructor.

Regardless of how the new values ​​are set, the entity notifies the outside world of its change: On the one hand, it solves a general one change-Event, on the other hand a special one change: *-Event for every single changed value. Because Backbone.js each entity has the base class Backbone.events a onFunction makes it easy to react to these events:

customer.on ('change: lastName', function (model, value, options) {
// ...
});

One assigns a value to an entity called id to, Backbone.js treats them in a special way: Each entity has a direct property with that name, which can be accessed without going through the getFunction allows access. Backbone.js also uses this property internally to uniquely identify an entity, for example within a list.

Occasionally you need a pure data object instead of an entity. Since Backbone.js, with the exception of the id encapsulates all values ​​internally, can be checked with the function toJSON convert an entity into such an object:

var pureData = customer.toJSON ();
// => {
// firstName: 'Golo',
// lastName: 'Roden',
// age: 35
// }

Lists of entities work in the same way, you just use the base class Backbone.Collection instead of Backbone.Model. As a convention for the identifier of lists it has become common to give them the suffix Cunning to attach. The entity to be used is to be passed to a list as a parameter:

var CustomerList = Backbone.Collection.extend ({
model: Customer
});

In this case, too, the result is not a concrete list, but only a blueprint, which is why here too CustomerList is called as a constructor. A list of entities can be passed to the constructor as a parameter, with which the list is initialized:

var golo = new Customer ({firstName: 'Golo', lastName: 'Roden', age: 34}),
bill = new Customer ({firstName: 'Bill', lastName: 'Gates', age: 57});
var customers = new CustomerList ([golo, bill]);

Analogous to the set-Function of an entity, Backbone.js provides a function of the same name for lists: previously not included entities are added to the list, already included entities are adjusted - or removed if they are the set-Function can no longer be passed.

Backbone.js also offers the functions for targeted access to individual list entries add and remove at. There is no need to call a function to update an existing entity; it is sufficient to adjust its values.

In this case, too, Backbone.js triggers various events, in particular add and remove. They can be accessed in the same way as the events of entities. The documentation for Backbone.js contains a complete list of all events that entities and lists can trigger.

Backbone.js also offers numerous functions for lists to filter or sort their content. These include in particular various functions that come from Underscore.js and are passed through to the outside world by Backbone.js, such as the functions that are extremely helpful for numerous tasks map and reduce. Last but not least, lists also have a toJSON-Function that returns its content as a pure data object.

Backbone.js supports it by default to accommodate your own logic in an entity or list. No special precautions are necessary for this. Instead, you add the desired properties and functions of the entity or the list in the call to the respective extend-Function added:

var Customer = Backbone.Model.extend ({
defaults: {
first name: '',
lastName:''
},
fullName: function () {
return this.get ('firstName') + '' + this.get ('lastName');
}
});

Backbone.js also pays special attention to the validation and sorting of entities and lists. The most important entry point for the validation of entities is the function validate represents that in the case of a valid entity undefined, but returns a corresponding error message in the event of an error. This function is also passed when calling extend:

var Customer = Backbone.Model.extend ({
defaults: {
first name: '',
lastName: ''
},
validate: function (attributes, options) {
if (attributes.age <0) {return 'age must not be negative.'; }
}
});

The function isValid carries out the actual validation and returns as a logical value whether the entity conforms to the validate-Function stored rules is valid or not. In the event of an error, you can react accordingly and report the error that has occurred using the property validationError determine:

if (! customer.isValid ()) {
console.log (customer.validationError);
}

It should be noted that the setFunction does not trigger a validation, unless you also pass a suitable parameter object:

customer.set ('age', customer.get ('age') + 1, {validate: true});

The Backbone.js is used to sort entities comparator-Property of lists. If you set them, Backbone.js automatically takes care of inserting new entities in a list so that you always have a correctly sorted list.

To the value of comparator-There are three different approaches to setting the property:

  • In the simplest case, you assign it the name of the attribute that you want to use as the sorting criterion. This procedure is easy to implement, but has the disadvantage that the list can only be sorted in alphabetical order according to a single static criterion.
  • Alternatively, you can use the comparator-Assign a function to the property that accepts the entity and returns the name of the attribute by which you want to sort.However, the same restrictions apply here as before. The only exception is the added option of dynamically defining the sort-relevant attribute.
  • The third option is the comparator-Assign a function to the property that expects two parameters. Both parameters each represent an entity. In this way you can implement any sort of complex logic, the only important thing is the return value of the function: The value -1 means that the first entity is to be sorted before the second; the value 1 describes the opposite. If, on the other hand, the function returns the value 0, this means that both entities are to be regarded as equivalent.

Taken all of this together, it becomes clear that Backbone.js puts the main focus on modeling the domain of the application. Up to this point, Backbone.js can even be used regardless of the fact that it is actually intended to implement single-page applications.

Infrastructure connection

Connection of infrastructure

Being able to model a domain model is helpful, but not sufficient for a complete application. Usually you want to persist it, usually with a REST web service. Here, too, Backbone.js offers its support, because entities have a built-in Save- and a destroy-Function.

The SaveFunction serializes the entity to be stored and sends it to the web server in the background using an AJAX request. Depending on the return value of the function isNew of the entity, Backbone.js decides whether there is a POST- or one PUT-Query generated - whether it tries to save the entity for the first time or to update an existing version.

The destroyFunction a DELETE-Request to delete the entity from the web server. Backbone.js needs a URL to which it can send the requests. This URL is set with the help of the url-Property on the associated list:

var CustomerList = Backbone.Collection.extend ({
model: Customer,
url: '/ customers'
});

Backbone.js combines the url and the id-Property of the affected entity to determine the actual address for a request. In principle, the same way is used to fill a list with data from the web server. The function is used for this fetchwhich you do not call on an entity, but on the list.

By default, Backbone.js merges the content of the existing list with the data received from the web server. If you want to build the list from scratch, a corresponding parameter object must therefore also be passed:

customers.fetch ({reset: true});

In the background, Backbone.js uses an internal function called Backbone.sync. By default, it addresses a REST-based web service. If necessary, however, it can be replaced by your own implementation in order to provide any synchronization logic.

For this it is enough Backbone.sync assign a new function that corresponds to the following signature:

Backbone.sync = function (method, model, options) {
// ...
};

The parameter method contains one of the values create, read, update or delete, the parameter model however, the entity to be saved or the list to be loaded. options Finally, it includes additional options to consider when saving or loading data.

Alternatively, it is possible to use such a function from a third party. For example, the project Backbone.localstorage a function is available that accesses the local memory of the web browser. The integration is very easy: you have to after loading the file backbone.js just the file backbone.localstorage.js load the function Backbone.sync replaced as desired:

[...]
<script type="text/javascript" src="backbone.js"></script>
<script type="text/javascript" src="backbone.localstorage.js"></script>
</body>
[...]

The option to use the function is also interesting Backbone.ajax Create your own AJAX connection or use the functionsBackbone.emulateHTTP and
Backbone.emulateJSON to be able to improve the compatibility with some web servers.

Routes, views

Manage routes and views

As already mentioned, Backbone.js does not take care of updating the display in the web browser, but leaves this task to the developer and a library of his choice. Nevertheless, Backbone.js is the base class Backbone.View which is used to define views. One view, however, corresponds more to the controller known from MVC, i.e. it takes over the communication between the website and the domain model. On the one hand, a view reacts to inputs in the web browser by calling functions on the domain model. On the other hand, it causes the display in the web browser to be updated if changes occur to the domain model.

To define a view, you have to use it analogously to entities and lists of the class Backbone.View derive and define some properties: At least tagName must be built in because the property defines which HTML element the view is to be rendered as. You can also use the properties className and id to specify a CSS class and ID:

var CustomerView = Backbone.View.extend ({
tagName: 'div',
className: 'customerDetail'
});

It also makes sense to use the initialize-Define the function and the renderFunction. The task of this function is to update the display in the web browser. How it is implemented, however, is entirely up to the developer's imagination:

var CustomerView = Backbone.View.extend ({
tagName: 'div',
className: 'customerDetail',
initialize: function () {
this.listenTo (this.model, 'change', this.render);
},
render: function () {
// ...
}
});

Usually the renderFunction itself does not do the actual updating, but only generates the required HTML code to display the view. That shows it to the property el or their jQuery-encapsulated variant $ el that represents the concrete HTML element that was linked to the view. At this point you usually also include the use of HTML templates, which in the simplest case again refers to the templateFunction of Underscore.js.

So that you can render-Function can call chained at a higher level, the documentation of Backbone.js recommends that the function this returns to the caller:

var CustomerView = Backbone.View.extend ({
template: _.template (...),
tagName: 'div',
className: 'customerDetail',
initialize: function () {
this.listenTo (this.model, 'change', this.render);
},
render: function () {
this. $ el.html (this.template (this.model.attributes));
return this;
}
});

In addition, the events-Property react to events within the web browser, for example to call corresponding functions on the view:

var CustomerView = Backbone.View.extend ({
template: _.template (...),
tagName: 'div',
className: 'customerDetail',
events: {
'click .save': 'saveCustomer'
},
initialize: function () {
this.listenTo (this.model, 'change', this.render);
},
render: function () {
this. $ el.html (this.template (this.model.attributes));
return this;
},
saveCustomer: function () {
// ...
}
});

When creating the concrete view, an existing HTML element can be passed to the constructor as a parameter, to which it is bound. You can also dynamically transfer the entity or list to be displayed to the constructor by assigning the model respectively collection adds:

var customerView = new CustomerView ({
el: document.getElementById (...),
model: customer
});

Backbone.js also includes support for managing routes. The basis for this is the class Backbone.Routerwhich can be derived in the usual way. An object with routes and the functions to be called by them are to be passed as parameters to the extend function:

var AppRouter = Backbone.Router.extend ({
routes: {
'customers /: id': 'loadCustomer',
'customers': 'loadCustomers'
},
loadCustomer: function (id) {
// ...
},
loadCustomers: function () {
// ...
}
});

To create a concrete router, you have to AppRouter call as constructor:

var appRouter = new AppRouter ();

However, the existence of a router instance is not enough for Backbone.js to manage the navigation of the web browser. In addition, you have to have the begin-Function of the object Backbone.history call.

You can transfer this function as a parameter, whether you want to use hashbang-using routes or the history API that is newly introduced in HTML5 and is only compatible with modern web browsers:

Backbone.history.start ({pushState: true});

Since hashbang navigation in Internet Explorer requires the use of a iframe- Assumes elements, it is important that beginFunction can only be called up after the website has fully loaded. For this reason, one is well advised to instantiate the router and call the function in a call to the $-Function of jQuery to encapsulate:

$ (function () {
var appRouter = new AppRouter ();
Backbone.history.start ({pushState: true});
});

Conclusion

Strengths and weaknesses

Backbone.js has undoubtedly numerous strengths: Especially in comparison to Knockout, it is noticeable that Backbone.js not only addresses a partial aspect in the development of single-page applications, but offers a comprehensive approach. Combined with the clearly structured structure that Backbone.js applications usually have, this contributes a lot to the maintainability of large web applications.

The focus on the modeling of the domain and the automatic synchronization with the web server force the developer to plan a certain structure for the application from the start and to think about the technical core. This explains why web applications developed with Backbone.js are usually very well structured.

This strength is at the same time one of the relevant weaknesses of Backbone.js: Since the corresponding structure has to be set up for each application, a lot of code is quickly created, especially for small projects, which initially does not contribute to the actual application, but merely represents infrastructure code. The development is correspondingly complex and tough, especially at the beginning.

The biggest weakness of Backbone.js, however, lies in the lack of a concept for updating the display. This creates two disadvantages for Backbone.js. On the one hand, one is dependent on the integration of a third-party library, which in the long term can always lead to compatibility and maintenance problems. You also give up the clean structure of a Backbone.js application, at least in part, in favor of code developed with another library. This can quickly take its toll, especially when using jQuery.

On the other hand, Backbone.js does not itself manage the views displayed. This means that as a developer you have to take care of their correct disposal yourself. All too quickly, however, hard-to-find memory leaks occur that damage the stability and performance of the application in the long term.

Backbone.js does not face these problems. This can be remedied by extensions such as backbone, puppet or thorax, which, however, are anything but compact and further increase the already existing complexity of the application.

The result is often applications that are neatly structured, but have a complex infrastructure and a relatively large amount of code for little effect.

Conclusion

All in all, it can be said that Backbone.js is in principle a well thought-out framework, but it quickly leads to unnecessarily high complexity. In particular, the renouncement of a separate concept for the management of the display contributes to this, among other things due to the memory management for individual views, which must be implemented by hand.

MVC and MVVM framework for JavaScript in comparison

This article is the second part of a series in which various MV * frameworks that work with JavaScript are presented. In the next episode, the MVVM design pattern already known from the previous part will be taken up again, but not in connection with Knockout.js, but a very young, but extremely promising framework: AngularJS.

If you evaluate Backbone.js in terms of the fact that it opened up the possibility of developing single-page applications in a well-structured and professional manner as early as 2010, it is still a noteworthy framework. However, if you look at it from today's perspective, the complexity and the effort required are extremely negative. If one also compares Backbone.js with Knockout.js, the missing concept for the management of the representation becomes painfully noticeable. Backbone.js is no longer up-to-date.

In principle, Knockout would therefore be the perfect complement to Backbone.js, which, among other things, led to the Knockback project. However, this approach also harbors dangers, since you depend on two large frameworks, their compatibility with each other and a further, third component that connects the two.

Without a doubt, Backbone.js is more suitable as a knockout for complex web applications, but it can only be recommended to a limited extent due to the weaknesses mentioned. (jul)

Golo Roden
is the founder and managing director of "the native web UG", a company specializing in native web technologies. For the development of modern web applications he prefers JavaScript and Node.js and with "Node.js & Co." wrote the first German-language book on the subject.

1 comment