📜 ⬆️ ⬇️

JavaScript frameworks How to study them quickly

Hello colleagues.

Today we wanted to touch on such a multifaceted and controversial topic as JavaScript frameworks. Over the past few months, publishers repeatedly discussed the prospects for publishing books both on Angular.js and Knockout.js, and the book on Backbone.js was published last year. The following material is intended to help understand the strengths and weaknesses of various JavaScript frameworks. Perhaps, after studying the article, it will be easier for the reader to answer questions about whether these frameworks are similar, and whether he wants to further explore some of the technologies mentioned in this review. We will ask you to share your thoughts on whether new books are needed on these frameworks, if yes - on which ones (we express in the comments, feel free to give references to books).

To quickly learn JavaScript frameworks from the MV * category, it is important to be able to present any framework as a series of possibilities. The basic functionality of an MV * application is routing, data binding, patterns / views, models, and data storage. In this publication, I am going to describe these features and show the code for implementing each of these features in two or three frameworks. To begin with, we will specifically examine the solution of which specific tasks are optimized by each of the frameworks, after which, I hope, you will notice more similarities in all these frameworks than differences. It turns out that most frameworks actively borrow features that have proven themselves in other frameworks.

I prefer to see similarities in people, not differences - Isabel Allende

Do not bother to parse each line of code. For now, just try to assess what these frameworks are similar to, and what problems they allow to solve on a project.
')
Routing

At a minimum, routing maps your URLs to actions, but sometimes it comes to implementing the full state machine pattern to manage state transitions within a view.
If you have ever used a router in a server-side MVC framework, for example, in Rails, CodeIgniter, CakePHP, ASP.NET MVC, Spring MVC, JSF, STRUTS, Grails, etc., you can consider JavaScript MV * routers to be similar entities, but running on a client with javascript and paving routes to server code that can be written in PHP, Ruby, Java, C #, etc.

The question may arise: how will all this work in older browsers? The mechanism works by default, using as a route all the information that is located in the URL after the hashtag. True, if HTML support for push-states is configured (in most frameworks this is done with a single line of code), URLs without hashes that match the routes will be intercepted on the client and also execute JavaScript code.
The push-state is NOT SUPPORTED by default, since, despite the fact that the URL requested directly through a bookmark or sent in an e-mail, will work on the end-user computer, search engines are not well adapted to working with JavaScript. That is, the robot may not execute JavaScript code, therefore, it will not be able to view the true contents of the page.

The solution usually offered in such cases is to run PhantomJS or another lightweight browser on the server, make it load pages and JavaScript, and then return the generated markup along with JavaScript. To configure this mechanism, you need to work hard, so it is set by default for URLs with hashtags that are supported in all browsers.

Quite an explanation, go to the code.

Backbone Example

Here is a simple routing example in Backbone.js

var HomeView = Backbone.View.extend({ template: '<h1>Home</h1>', initialize: function () { this.render(); }, render: function () { this.$el.html(this.template); } }); var AboutView = Backbone.View.extend({ template: '<h1>About</h1>', initialize: function () { this.render(); }, render: function () { this.$el.html(this.template); } }); var AppRouter = Backbone.Router.extend({ routes: { '': 'homeRoute', 'home': 'homeRoute', 'about': 'aboutRoute', }, homeRoute: function () { var homeView = new HomeView(); $("#content").html(homeView.el); }, aboutRoute: function () { var aboutView = new AboutView(); $("#content").html(aboutView.el); } }); var appRouter = new AppRouter(); Backbone.history.start(); 


In particular, pay attention to the AppRouter object. Routes are displayed on function. Functions simply create the view object that manages the DOM fragment, and add it to the page as soon as the URL changes. The Backbone.history.start () function tells Backbone to start listening to URL changes.

AngularJS example

Here is a simple example of routing in AngularJS.

 var routingExample = angular.module('FunnyAnt.Examples.Routing', []); routingExample.controller('HomeController', function ($scope) {}); routingExample.controller('AboutController', function ($scope) {}); routingExample.config(function ($routeProvider) { $routeProvider. when('/home', { templateUrl: 'embedded.home.html', controller: 'HomeController' }). when('/about', { templateUrl: 'embedded.about.html', controller: 'AboutController' }). otherwise({ redirectTo: '/home' }); }); 


The AngularJS example is very similar to Backbone, with the exception that routes are mapped to the templateUrl classes and controllers (in this context, controllers are similar to Backbone views).
Ember example

Below is a simple example of routing in Ember.

 App.Router.map(function() { //      this.route('home', { path: '/' }); this.route('about'); }); 


Again, the example is very similar to the previous ones, except that in Ember.js, the first parameter of the “resource” object of the router is the name routeName, and the second is the URL. At first, such an order confused me until someone suggested that the path parameter is optional and can often be set by agreement, as is the case with the about page from this example. In addition, to run this simple example of routing templates are needed, but I’ll only focus on them at the end of the article. For now, it’s enough to know that the templates fit into the {{outlet}} element. It should be noted that the Ember router uses relatively complex features, for example, nesting templates, but here I’m just trying to show how it is functionally similar to other libraries.

Just routing

Most frameworks contain a routing solution, but Knockout.js focuses on data binding and offers a best-of-breed solution that uses one of the JavaScript libraries, the only purpose of which is routing. The most common specialized libraries of this kind are SummyJS and HistoryJS. SummyJS works with browsers that support API History for HTML5, as well as URL hashes for older browsers (IE 8 and below). HistoryJS library is smaller, but only supports History API for HTML5.

Data binding

Binding allows you to make changes to the data model that needs to be updated in the view or and / or allows you to automatically update the model with changes made to the view, and no additional code is required for this. Unidirectional data binding usually means that changes made to the model are propagated to the view. With bidirectional data binding, there is an additional opportunity to promptly reflect in the model all changes made to the view. Data binding will save you from the mass of stereotypical code that developers usually have to write. Accordingly, the programmer can focus on solving unique problems peculiar to this application.

AngularJS example

Below is a simple example of bidirectional data binding in AngularJS. When typing in the input field, it will be displayed on the screen immediately after the greeting message.

 <h1>Simple Data Binding with AngularJS</h1> <br /> <div ng-app> Name: <input type="text" ng-model="name" /> <br /><br /> Welcome to AngularJS {{name}} </div> 


KnockoutJS Example

The KnockoutJS framework really focuses solely on data binding and is used as part of the best solution along with other frameworks and libraries, in particular, with DurandalJS for display management and History.js or Sammy.js for routing processing. Here is an example of data binding in KnockoutJS.

 //      var ViewModel = function(name) { this.name = ko.observable(name); }; ko.applyBindings(new ViewModel("")); //    Knockout 


Backbone Example

There is no automatic data binding in the backbone, but you can do it manually. In my experience, unidirectional data binding, in which the view is updated in response to changes made to the model, is extremely useful. Real cases of unidirectional data binding from a view to a model are less common (for example, you want to implement a live preview of the text entered by the user in a text editor). Indeed, linking from a view to a model can be useful, but often the cause of changes to the view is the information entered by the user, and you want to have time to implement other logic before these changes are made to the model. For example, we can talk about validating the input or filtering a list, in which you also need to remember the current filter. Below is a simple example of code that implements both binding options.

 var MessageView = Backbone.View.extend({ template: _.template($('#message-template').html()), events: { 'keyup #name': 'updateModel' }, updateModel: function(event) { this.model.set({name:$("#name").val()}); }, initialize: function() { this.listenTo(this.model, "change", this.render); this.render(); }, render: function() { this.$('#message').html(this.template(this.model.toJSON())); return this; } }); var person = new Backbone.Model({name:''}); messageView = new MessageView({el: $('#message-container') ,model: person}); 


So, you listen to change events occurring in the model, and call the property of the view to render, so that the model updates the view. Similarly, you listen to “keyup” events as you enter and modify a model, retrieving the desired value from the input with jQuery and setting it in the model — so that the view updates the model. This example helps to make an impression about how much code it takes to make a data binding. It is also necessary to mention the existence of numerous plug-ins that support data binding in Backbone.

Ember example

Data binding in Ember looks like this:

 App = Ember.Application.create({}); 


The Ember framework uses the familiar handlebar objects for templating, but also includes “input assistants”, which bind the input fields that are found in most forms. In this example, curly braces {{replace angular <as you type, but the “name” property does not have quotes, so the helper knows that it needs to be bound. The Ember framework, like AngularJS, provides bidirectional binding with the minimum amount of code.

Templates / Views

A template can correspond to a whole HTML page, but more often templates are smaller fragments, where instead of future dynamic data there are placeholder expressions with an already established binding. They may not include any logic - there is a philosophy according to which there should be no program logic in the representations, or it can be present in a minimal amount. You can directly embed javascript code into some templates. Templates can be based on the DOM model and use DOM to insert dynamic data on the fly, or for string interpretation of HTML: HTML strings are used to quickly replace dynamic elements.

Template Libraries

Among the libraries that help with templating, should be called Handlebar.js - the most popular of them. Handlebar.js is often used with Backbone.js and is included with Ember.js (other template libraries can be used with Ember.js, but in this case performance drops significantly and it is not recommended to do so). Another popular templating engine is called Mustache.js. UnderscoreJS is a helper library associated with the Backbone.js framework, which includes, in particular, a template library, as well as a wealth of material for functional programming. The rising star in this area can be considered the Dust.js library, which was recently adopted by LinkedIn for programming its applications. In addition, the Handlebar.js and Mustache.js libraries are often used in applications written with jQuery and AJAX alone (without any JavaScript MVC), when the developer understands that he only needs standardization on the client. JQuery has developed its own template library, but at the moment it is already recognized as undesirable, and I do not recommend it.

Underscore origins

It all started with how Jeremy Ashkenas, the creator of BackboneJS, extracted UnderscoreJS from the original versions of Backbone, so that the convenient auxiliary functions could also be used in isolation from BackboneJS.

Consider a few examples.

AngularJS example

Here is a simple example of a template in AngularJS.

 var templatesExample = angular.module('FunnyAnt.Examples.Templates', []); templatesExample.controller('HomeController', function ($scope) { $scope.greeting = "Welcome to the application."; }); templatesExample.controller('AboutController', function ($scope) { $scope.content = "As a software developer, I've always loved to build things..."; }); templatesExample.config(function ($routeProvider) { $routeProvider. when('/home', { templateUrl: 'embedded.home.html', controller: 'HomeController' }). when('/about', { templateUrl: 'embedded.about.html', controller: 'AboutController' }). otherwise({ redirectTo: '/home' }); }); 


You probably noticed how this code looks like an earlier routing example. Here you just added data bindings to help you understand how templates can be useful to you in the application. Templates can be included in the script tags of the main HTML file so that the example looks transparent and can work with it in jsfiddle.net, however the templates can be external to any Angular.js view. In the latter case, at the $ routeProvider configuration stage, you simply need to specify the valid file path in the templateUrl property.
It is worth mentioning that the preferred method of processing templates in large-scale applications (where performance is important) is to concatenate AngularJS templates and then register them in Angular $ templateCache during compilation, with the assembly task being formulated like this.

Ember example

Below is an example of using templates in Ember.

 App = Ember.Application.create({}); App.Router.map(function() { //      this.resource('home', { path: '/' }); this.resource('about', {path: '/about'}); }); App.HomeRoute = Ember.Route.extend({ model:function(){ return{ greeting: 'Welcome to the application.' } } }); App.AboutRoute = Ember.Route.extend({ model: function(){ return{ pagecontent: 'As a software developer, I have always loved to build things...' }; } }); 


The Ember route is an object that tells the template which model it should display — namely the model, not the URL. It can be considered the simplest controller for your template and resource (URL), whose main task is to load the model. If this is not enough for you, and you also need to save the state of the application, then you need a full controller.

In Ember and AngularJS frameworks, working with templates is implemented in different ways: the string model and the DOM model, respectively. However, in both cases the same problems are solved. In addition, the interfaces and contracts that are provided to them to the developer are very similar. Going a little deeper into the details, we note that both frameworks use handlebar syntax for data binding and script tags or separate files for storing templates.

Backbone Example

Now let's look at a simple example of working with templates in Backbone.

 var HomeView = Backbone.View.extend({ template: _.template($("#home-template").html()), initialize: function () { this.render(); }, render: function () { this.$el.html(this.template({greeting:"Welcome to Backbone!"})); } }); var AboutView = Backbone.View.extend({ template: _.template($("#about-template").html()), initialize: function () { this.render(); }, render: function () { this.$el.html(this.template({content:"As a software developer, I've always loved to build things..."})); } }); var AppRouter = Backbone.Router.extend({ routes: { '': 'homeRoute', 'home': 'homeRoute', 'about': 'aboutRoute', }, homeRoute: function () { var homeView = new HomeView(); $("#content").html(homeView.el); }, aboutRoute: function () { var aboutView = new AboutView(); $("#content").html(aboutView.el); } }); var appRouter = new AppRouter(); Backbone.history.start(); 


This is a modified routing example. In this case, HTML is not hardcoded into the template property of the view object; the markup is on an HTML page enclosed in a script tag with an id attribute (browsers ignore script tags whose types are unknown to them — in particular, text / template — so the template itself will not be displayed). To get a template (HTML fragment), use the jQuery selector, with which we find the element by the script tag id, select innerHTML from it and assign this HTML property to the template property of the view object (this is just a string).

When applying such an approach, in which templates are treated as normal html lines, it is easy to imagine how you can substitute a completely new library in place of the existing one in Backbone; to do this, you just need to slightly change the implementation of the template property.
For example, to use the Handlebars template library instead of Underscore, we will update the template property with the view like this:

 template: Handlebars.compile( $("#home-template").html() ), 


... and then update the binding syntax used in the templates, for example

 {{greeting}} 


That's all we needed to do in order to replace one template library with another in the application.

Here is a complete example in which the library handlebar.js is used for templating.

 var HomeView = Backbone.View.extend({ template: Handlebars.compile( $("#home-template").html() ), initialize: function () { this.render(); }, render: function () { this.$el.html(this.template({greeting:"Welcome to Backbone!"})); } }); var AboutView = Backbone.View.extend({ template: Handlebars.compile( $("#about-template").html() ), initialize: function () { this.render(); }, render: function () { this.$el.html(this.template({content:"As a software developer, I've always loved to build things..."})); } }); var AppRouter = Backbone.Router.extend({ routes: { '': 'homeRoute', 'home': 'homeRoute', 'about': 'aboutRoute', }, homeRoute: function () { var homeView = new HomeView(); $("#content").html(homeView.el); }, aboutRoute: function () { var aboutView = new AboutView(); $("#content").html(aboutView.el); } }); var appRouter = new AppRouter(); Backbone.history.start(); 


In this case, it is not easy to clearly distinguish one concept from another. Speaking of templates, many experts delve into the performance of template libraries, which is largely related to the data binding discussed above, but also concerns the loading of templates (plus their preliminary compilation in JavaScript, etc.).

On top of all this confusion, templates, as mentioned above, are just HTML, and many server developers dealing with MVC associate HTML with views or partial templates (partials). However, views in Backbone are code, a special JavaScript class that controls the HTML fragment. As a rule, there’s almost no markup in a Backbone view, except for a reference to a template, which is usually located outside the view object.

Models

Models are a client type of so-called business objects, domain objects or entities. By definition, Jeremy Ashkenas, author of Backbone, “models are the core of any JavaScript application, they contain both interactive data and much of the logic that covers them; transformations, validation, computed properties, and access control. ” Ashkenas also prudently notes that between these entities and the models contained on your server in Active Record; rather, these entities are a narrower collection located on the client and possessing additional properties that are useful when working with the user interface — for example, count (account).

In principle, the idea of ​​models in client MV * frameworks is as follows: create a central node where the data related to the application will be located, as well as all sorts of behaviors that can be encapsulated with this data. Such a model can be created on the basis of architectures with server MVC plus jQuery, and the data relating to the model are usually stored in the DOM. The purpose of creating such a model is to bring this data and state out of the DOM and place it in a common place from where it will be easy to reuse.

Backbone Example

In one of the previous examples where data binding was considered, we became acquainted with models. In principle, models contain data and store it outside the DOM, and also generate events, such as “changes,” to which numerous views respond appropriately, updating the user interface wherever it is needed. So, we have a source of truth, and this is not a user interface.

var MessageView = Backbone.View.extend ({
template: _.template ($ ('# message-template'). html ()),
events: {
'click #button': 'updateModel'
},
updateModel: function (event) {
  this.model.set({ name: $("#name").val() }); $("#name").html(''); }, initialize: function () { _.bindAll(this, 'render'); this.listenTo(this.model, "change", this.render); }, render: function () { this.$('#message').html(this.template(this.model.toJSON())); return this; } }); var NameView = Backbone.View.extend({ template: _.template($('#name-template').html()), initialize: function () { _.bindAll(this, 'render'); this.listenTo(this.model, "change", this.render); }, render: function () { this.$el.html(this.template(this.model.toJSON())); return this; } }); var Person = Backbone.Model.extend({ defaults: { name: '' } }); var person = new Person(); var messageView = new MessageView({ el: $('#message-container'), model: person }); var nameView = new NameView({ el: $('#name-container'), model: person }); 


I slightly modified the above data-bound example by adding a new template and a view associated with the same Person model object as the template. Above, I dynamically declared the Person model to not complicate the code, but now I add a call to the Backbone.Model.extend () method to demonstrate how to create a prototype for a model that can be reused - like classes in classical languages.

Note that both views listen to the same object of the Person model (a change event) and are updated. If the data source is only one, then multiple calls to specific DOM elements can be encapsulated into their own neat views, and the model can serve them all.

Notice how the access methods (installers and recipients) are used so that in the example above, change events are triggered ... such an agreement is often forgotten.

AngularJS example

The idea of ​​a single model storing true information about the “state” of your application exists in AngularJS, however AngularJS allows you to use POJOs as models, and then “under the hood” adds observers to any properties attached to the $ scope object and are declaratively attached to the view using the "ng-model" directive. Then these observers automatically notify other application components attached to the same model, and these DOM elements “know” how they should be updated. In general, a lot of “magic” happens here, but the AngularJS development team insists that it is “white magic”.
Here is an updated example on AngularJS with data binding. This shows how the view fragments are updated.

 <h1>Simple Data Binding with AngularJS</h1> <br /> <div ng-app> Name: <input type="text" ng-model="person.name" /> <br /><br /> Welcome to AngularJS {{person.name}} <br/> Person: {{person.name}} </div> 


AngularJS person , ng-model. , JavaScript.
, : , Backbone; , , AngularJS .



AngularJS

AngularJS . -, AJAX , $.ajax jQuery, $http. , RESTful-, AngularJS $resource, RESTful- .

$http

 app.factory('myService', function($http) { return { getFooOldSchool: function(callback) { $http.get('foo.json').success(callback); } } }); app.controller('MainCtrl', function($scope, myService) { myService.getFooOldSchool(function(data) { $scope.foo = data; }); });  $resource var Todo = $resource('/api/1/todo/:id'); //  todo var todo1 = new Todo(); todo1.foo = 'bar'; todo1.something = 123; todo1.$save(); //    todo var todo2 = Todo.get({id: 123}); todo2.foo += '!'; todo2.$save(); //  todo Todo.$delete({id: 123}); 


Backbone

Backbone , RESTful API, Backbone.sync(). , (URL), save.

var UserModel = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
name: '',
email: ''
}
  }); var user = new Usermodel(); //  :     `id` var userDetails = { name: 'Craig', email: 'craigmc@funnyant.com' }; //     `id`,   // POST /user    {name:'Craig', email: 'craigmc@funnyant.com'} //       ,   `id` user.save(userDetails, { success: function (user) { alert(user.toJSON()); } }) 


Ember

Ember Ember Data, , / . , ORM ActiveRecord, JavaScript . Ember Core v1.0, , Ember $.ajax jQuery, AngularJS $http.



MV*- JavaScript, , . , , , . , – , , .

Source: https://habr.com/ru/post/257923/


All Articles