📜 ⬆️ ⬇️

Sqimitive.js - Frontend Primitive or “Backbone without wrappers”

For quite a long time most of the sites have ceased to be a set of HTML / PHP / CSS / JS-files, which are simply downloaded to the server. Bower, Grunt, Component.js, AMD, Require.js, CoffeeScript, Clojure, Composer, npm, LESS and another 100,500 tools - all this today is used to build projects, update components, load dependencies, compress code, compile from one JavaScript to another, juggling cards, weeding a vegetable garden and even cooking scrambled eggs .

It inspires many people. Why, there - 95% of my friends say in one voice how to connect just a couple of libraries with a special street magic, you can forget the site on over-9000 Australian green dollars - and in just one evening, with a coffee break and bagels.

And I am a strange man. I do not like mixing languages, technologies, libraries. Angular, Knockout, React - they are all good, but each is complex in its own way. But there are “hybrids” where several worlds converge at once - like Ember and Knockout.Bootstrap . In addition, many are built on jQuery - however, even I have no complaints about it; probably this should be javascript.
')
Be that as it may, reality shamelessly comes in contact with dreams and dot the i's. I also have to write on “new & popular” - and when you write, the soul is languishing and asks to create another bicycle ... and will you refuse it? She's like a little child.

Bike was created. Bicycle without wrappers. As simple as a Kalashnikov machine gun, and as versatile as a Swiss knife, where instead of inheritance are events, instead of models, collections and ideas, one class, with unlimited nesting and complete freedom of action, almost half the size of Backbone.js , using Underscore .js and, optionally, jQuery / Zepto .

Welcome to Sqimitive.

How it all began


I am a freelancer. "Free" in this case means the exact opposite, so on duty I work on many projects, with many technologies and sometimes for a long time. The last two years have been a lot of work with Backbone. During this time, I had a carriage and a small cart of observations and observations about this, in general, good library. As a result, they turned into something new. What I called "Sqimitive" .

For almost the whole of 2014, I was lucky to work at Belstone Capital in New York. This is an excellent place for serious work. The ideal of the soul of a freelancer (although the same soul does not allow to work for a long time in one, even the most ideal, place). Sqimitive was created there.

Warm thanks to colleagues from Belstone , who, at my request to put part of the internal code in open access, answered: “Go ahead”.

Now Sqimitive about 9 months old. It is the basis of two projects of this company, in the amount of 15-20 thousand lines (this is funny, considering its own size of 700 lines without comments). The API has not changed in recent months; no serious errors have been noticed for quite a long time. The code is ready for use in a production environment. It can be found on GitHub , the full 55-page documentation is on squizzle.me and there you can also see an example of a simple To-Do App .

Below in this article I will describe 90% of the possibilities of the library, with lots of examples, theories and lyrics. And we start with the fundamental pits of JavaScript and Backbone. By the way, if you know Backbone - Sqimitive will seem to you very familiar, warm and almost tube.

(Hereinafter, my subjective point of view, which may not coincide with yours, Habr, the president or the Cosmic coalition. Read at your own risk, do not forget to comment and remember your karma!)

Table of contents:

  1. Inheritance as a survival factor
  2. Turn methods into events
  3. Inheritance on the fly or prototyping-2.0
  4. About important public relations
  5. Nesting is our everything
  6. Undergoodness
  7. What options are you my?
  8. Careless parents: trees and lists
  9. Introducing views
  10. About frailty of life without saving data
  11. assignResp and _respToOpt - API Response Card
  12. And is that all? _shareProps, _mergeProps, masker ()


Inheritance as a survival factor


If JavaScript was Haskel, then it would not have this problem ( partly because no one would have written on it ). If JavaScript were C, then it would have other problems - maybe a lot of problems, but definitely not the same.

But JavaScript is not Haskel, and not C. He had a difficult childhood with unstable parents who permanently damaged him this this . So now it is inconsistency programmers cleave.

Functions in JavaScript are common values ​​like numbers and strings (the so-called first class citizen ). Functions can be assigned, deleted, copied, even converted to a string. Plus, the function has an implicit parameter - this - which, according to the authors of the language, should indicate the object that triggered this function - and not the object to which this function was attached.

Thus, objects in JavaScript seem to be there, but it is impossible to obtain the context of an object. Objects in this sense are the same associative arrays. Apparently, this is the essence of the concept of Java (OOP) + Script (OP) . Joke.

(Please do not take my sarcasm to heart to people with a bad sense of humor. I love both JavaScript and Haskel, but I am also aware of their ... features and try to clearly identify them in order to find them a good solution. In general, it would be time already ruby ​​take over the world.)

Classic example ( JSFiddle ):

function Obj() { this.property = 'Hello!' this.show = function () { alert(this.property) } } var obj = new Obj setTimeout(obj.show, 100) // alert('undefined') 

The reason is that we considered the value-function, which is written in the show property of the Obj object, and passed it to setTimeout , which simply called it — apart from Obj , in the context of the window . Here obj for us is like a faceless array.

However, the problem with the inconsistent this is somehow or other, but it is solved - and even the ECMAScript comrades eventually gave up and after some 16 years (in 2011 together with 5.1 ) bind() was added to the Function , fixing this in one position.

Another feature of JavaScript is the lack of references to the base class in the language and the concept of the “base class” in general. JavaScript uses prototype inheritance, which generally means the following: each function (it’s the same constructor that you call as new Func ) has a so-called “prototype”. When new Func copies the fields of this prototype into a new instance of the object. “Inheritance” in terms of traditional OOP is absent; instead, the prototype is copied into another prototype, that is, all its fields are copied into another object: variables and function methods — which, as already said, are common values ​​that can be manipulated. Then, on the new prototype, all changes are made that are prescribed by “inheritance” (methods overlap, fields are added, etc.).

In fact, we get two independent prototype classes.

This technique is designed to deal with some of the shortcomings of the classic OOP - a fragile base class, multiple inheritance collisions and other nuances. An important problem in JavaScript was solved, in general, by the correct methods, but floating this , function values ​​(the inability to determine one’s name within the object without directly iterating through all the fields) and the absence of simple staff connections between the prototypes add up to blood and fury.

In traditional OOP, inheritance is when one object copies another (possibly with changes), but the parent-child relationship is maintained between them. Now look at OOP in JavaScript ( JSFiddle ):

 function Base() { //  . } Base.prototype.property = 'Hello!' Base.prototype.show = function () { alert(this.property) } function Child() { //  . } //    "". for (var prop in Base.prototype) { Child.prototype[prop] = Base.prototype[prop] } //            (. ). Child.__super__ = Base.prototype Child.prototype.show = function () { //   ? Child.__super__.show.call(this) } 

As you can see, here the show function, which is tied to Child , does not know its own name (it can be tied under different names, many times, to different prototypes), nor the name of the base class, or even this , if we do something like setTimeout((new Child).show, 100) .

We need only to hardcode these values ​​into the code of the function itself. This is clearly a bad way:


This is not to say that writing Foo.__super__.bar.apply(this, arguments) is at least tedious and unaesthetic. And debugging of forgotten, untranslated links can be compared only with the study of black magic ...

However, this is precisely the principle used in Backbone (in Angular, Knockout, React, you actually write on “semi-OOP”, where you explicitly indicate ancestors when you call that it is not much better). Ember has a good solution, with its automatic this._super :

 Child.reopen({ show: function (msg) { this._super(msg + '123') }, }) 

But Ember is 48,000 lines of pure javascript. Couldn't it be simpler? ..

Can. Sqimitive solves this problem like this ( JSFiddle ):

 var Base = Sqimitive.Sqimitive.extend({ property: 'Hello', show: function (right) { alert(this.property + right) }, }) var Child = Base.extend({ property: 'Bye', events: { '=show': function (sup, right) { sup(this, [' World' + right]) }, }, }) ;(new Base).show('123') // alert('Hello123') ;(new Child).show('123') // alert('Bye World123') 

In addition, you can use the standard version with __super__ - left for those who like to shoot at their feet and to support legacy code ( JSFiddle ):

 var Base = Sqimitive.Sqimitive.extend({ //  . }) var Child = Base.extend({ property: 'Bye', show: function (right) { Child.__super__.show.call(this, ' World' + right) }, }) 


Turn methods into events


The events block in Sqimitive defines new handlers that are called for events of objects of this class. When the event name coincides with the name of an already existing method — this method (in the example, show ) is replaced by firer ('show') - the function that, when invoked, triggers the same-name event. The replaced method (for example, the inherited one) is placed at the beginning of the processing chain, and the replaced one is after it. Thus, the execution logic is preserved. There is no need to change anything when changing the structure of the base or inheriting class.

If there was no method, the new method becomes the only handler. If under this name there is no method, then this property will not be overwritten and the event can be raised only explicitly via fire () .

Thus, any class method is a possible event attachment point, and the events themselves can be excited either explicitly through fire ('event') , or by calling a method on the object itself - if this event, then it will be triggered by firer () , and no - the function will be called directly (in fact, it is the only event handler). Transformation of a method into an event is done transparently and on the fly.

It should be noted that for purists who are fighting for nanoseconds - everything is clean. If the performance of a specific method or class is important to you - just define it as usual, without an event (see the example above with __super__ ) - then it will be called directly, bypassing fire () . Moreover, this can be done both in the base class, and in descendants, and already having overlapped event methods. It is only necessary to ensure that no one subsequently creates an event from this method, otherwise nanoseconds will flow in the wrong direction.

As my experience has shown, a complete replacement of the method, as in the example above, is a rather rare thing. In Sqimitive there are three more types of adding a handler, which are distinguished by a prefix (the equal sign is higher - one of them):


In all cases, the event parameters are passed to each handler.
The first type covers 50% of the reasons for overlapping methods, the second and third - another 40%.

 var Child = Base.extend({ events: { show: function (msg) { //   -   -,  , //    - . this.render() }, '-show': function (msg) { //  -  ,    -   //   ,     . if (msg.length < 3) { throw '  show()     3 .' } }, '+show': function (res) { //  ,        . return '(' + res + ')' }, '=show': function (sup, msg) { //  ,     .    //  . return '(' + sup(this, [msg + ' foo!']) + ')' }, }, }) 

In addition, handlers can be strings - if you just need to call a method with this name with original parameters. This greatly reduces the code and makes it clearer:

 var Child = Base.extend({ events: { //  render()  ,  show.  . show: 'render', }, }) 


Inheritance on the fly or prototyping-2.0


So, we have inheritance through events ... But is it necessary to perform it during class declaration via extend ?

As the name implies - no. Events - they are dynamic, their porridge does not feed, let alone excited. Yes, most importantly, more handlers!

 var Base = Sqimitive.Sqimitive.extend({ property: 'Hello', show: function (right) { alert(this.property + right) }, }) var base = new Base base.on('=show', function (sup) { sup(this, [' - I alert']) }) 

The result ( JSFiddle ) is the same as if we inherited a new class from Base and overlapped the method there. Here we did it on a “living” object, one of a kind. In addition, as we did, we can remove JSFIddle the same way:

 base.off('show') 

But be careful: it will remove all show event handlers, except those that are soldered - fused (inherited, for example, one of these). If we want to remove ours, we use its identifier ( JSFiddle ):

 var handlerID = base.on('=show', function (sup) { sup(this, [' - I alert']) }) base.off(handlerID) 

And what will happen to the method we have blocked - Base.show ? As you can see in JSFiddle , it will be restored as soon as its = show handler is removed. Just like people.

Naturally, other prefixes can be used in the same way as they are used in the events block.

In addition to on and off , we also have once - it is completely similar to on , but cancels the handler after it has been called exactly once.


About important public relations


For the time being, there are few objects, the application is simple, there is a lot of memory and in general a complete world and idyll. But this is not always the case.

For applications of an average hand of a class there are tens and hundreds, and objects for thousands. They constantly replace each other and fight for a place under the sun in the tight sandbox of the DOM. In such a situation, leaving them all to hang in the background is not humane. And at the same time, it is not clear how to manage their connections — exactly when an object is created and “connected” to the matrix, and when is it deleted, and how to disable its handlers when switching from the mortal world to the forefathers?

In Backbone, the listenTo and stopListening methods appeared (they were not there initially), which allow you to memorize related objects and get rid of links with them. However, Backbone itself does not contain the logic of embedding these objects. Models in collections are not considered - the main problem is in the constant circulation of representations (or views, View).

In Sqimitive there is an analogue of listenTo , and nesting of objects. Let's talk about the latter in detail later in the article, but for now a simple example:

 var Bindable = Sqimitive.Sqimitive.extend({ // opt (option)   Sqimitive  attribute  Backbone:     //        -  . _opt: { //     ,      . wasBound: false, }, events: { // postInit   ,    .   //  owned -  ,      . postInit: 'bindAll', // unnest       . '-unnest': 'unbindAll', }, bindAll: function () { // ifSet  true,        . this.ifSet('wasBound', true) && this.bind(this) // sink       , . return this.sink('bindALl') }, unbindAll: function () { if (this._parent && this.ifSet('wasBound', false)) { this.unbind(this) } return this.sink('unbindAll') }, //        -  // ,      .  //   ,     unbind. bind: function () { }, //   bind -  .    , //     bind. unbind: function (self) { // autoOff   -  stopListening.    // ,     autoOff('event') - . . this.autoOff() }, }) 

Now we can inherit Bindable , filling it with our logic. In most cases, it looks like this:

 var MyObject = Bindable.extend({ _opt: { someObject: null, //   Sqimitive,   "". }, events: { bind: function () { this.autoOff(this.get('someObject'), { event1: ..., event2: ..., }) }, }, }) new MyObject({someObject: new X}) 

Here MyObject is created with the option (parameter) someObject , to which two event handlers are then added: event1 and event2 . This is done via autoOff , which is similar to on , but adds this object to the list of dependencies and then, when unbind is called , autoOff () without parameters removes all the handlers of its object ( MyObject ) from all objects for which it was previously called ( someObject ).

Note that this is not the standard Sqimitive behavior, it is already our own code that can be laid into the Sqimitive base class for your application.

The third parameter to autoOff is the optional context, which is initially set to the dependent object (and not the one to which the handler is added). In conjunction with the names of the handler methods instead of closures, this gives a rather compact syntax:

 this.autoOff(someObject, { //  render()  this   change  someObject. change: 'render', nest: 'render', }) //  : someObject.on('change', function () { this.render.apply(this, arguments) }, this) someObject.on('nest', function () { this.nest.apply(this, arguments) }, this) 

There are other features to these methods — see the documentation for details.


Nesting is our everything


In Backbone, in my opinion, very little attention (read, no) is given to putting objects into each other. But this is an extremely important feature of them. Projects like Marionette.js try to compensate for this shortcoming, but this is exactly the case when the library is based on the library, everything is somehow assembled and even works, but it consumes so much cosmic energy that it would be better for everyone to sit at home. And in case of an error, it is not clear who to blame - the authors of the Backbone for the lack of regular means, the authors of Marionette for their logic, themselves for the incompatible world view, or JavaScript - simply because it is “not like everyone else.”

In addition, Marionette is another 4,000 lines of code in addition to existing dependencies. But each line is a potential error, each method is a new article in the documentation ( Marionette , however, simply does not have one).

In Sqimitive, the concept of parent-child is laid on the second level. The library itself is divided into two parts as two classes: Sqimitive.Core and Sqimitive.Sqimitive . Core - event core, everything that I have already described above. Sqimitive is his successor, adding options and nesting.

It is Sqimitive that gives the final functionality that is needed in applications. Core can be inherited if you want to implement only the event (and inheriting) mechanism in your class.

The Sqimitive library has no model, collection, and presentation (MCV) separation. A single class has attributes (inherent in models in Backbone) - they are called “options”, as they are passed to the constructor, and can also contain nested objects of a certain class, which can be filtered (the whole set of Underscore.js methods are available) to redirect their events to the parent and, in general, to interpret as a kind of aggregate, on which one can work as with something single, impersonal, and not with each object separately.

For individual work, _opt is just right, where each element is something special, and each movement (access, replacement) can be traced by blocking ifSet and get , adding normalize_OPT , responding to change_OPT and change - this will be change_OPT below.

In contrast to this Zen simplicity, Marionette , Ember, and others are complex. In Ember there are different types of properties (computer, observers, bindings), in Marionette - different types of representations (layouts, regions, element and collection representations, composite). Of course, this is all useful - for a certain level of applications and commands. But for many others, it's like shooting a gun on the sparrows. There is a lot of smoke and noise, the public is satisfied, but the action itself is not efficient and labor-intensive. In addition, you need to start to learn how guns fly sparrows and kernels.

It is also interesting that even the availability of such ready-made tools does not guarantee that it will not be easier for you to write some of them again specifically for your purpose.

Below is an example of declaring nested classes in Sqimitive:

 var MyItem = Sqimitive.Sqimitive.extend({ // -    . _opt: { complete: false, something: 'bar', }, }) var MyCollection = Sqimitive.Sqimitive.extend({ _childClass: MyItem, _childEvents: ['change', 'foobar'], _opt: { //  ,       !complete. allowIncomplete: false, }, events: { //   -  ,  . change_allowIncomplete: function (newValue) { newValue || this.each(this._checkComplete, this) }, //    -  . '.change': '_checkComplete', //    - ,   . '+nest': '_checkComplete', }, _checkComplete: function (sqim) { if (!this.get('allowIncomplete') && !sqim.get('complete')) { throw 'This collection only allows complete items!' } }, }) 

We declared two classes: MyItem , which has an option (attribute) complete , and MyCollection , which:


Here is an example of use when the collection initially does not allow incomplete objects ( JSFiddle ):

 var col = new MyCollection var item1 = new MyItem col.nest(item1) // exception var item2 = new MyItem({complete: true}) col.nest(item2) // okay item2.set('complete', false) // exception 

And here is when the allowIncomplete flag changes on the fly ( JSFiddle ):

 var col = new MyCollection({allowIncomplete: true}) var item1 = new MyItem col.nest(item1) // okay col.set('allowIncomplete', false) // exception 

Attention: if the check is not passed, the exception will be thrown, but the object will remain part of the collection. In real life, the change must either be blocked (by listening to.-Change and -nest ), or deleted by an objectionable object (with change_allowIncomplete ).


Undergoodness


Sqimitive internally uses Underscore.js , a library with general-purpose functions, largely overlapping the functionality of new versions of ECMAScript. Especially many convenient functions are available for working with data sets - arrays and objects.

Most of these functions (about 40) can be used on the Sqimitive object to work with its nested objects.

Below is an example of using the most useful methods using the example of MyCollection described above. A complete list with descriptions is provided in the documentation .

 var col = new MyCollection col.nest( new MyItem({complete: true}) ) col.nest( new MyItem({something: 'item2'}) ) col.nest( new MyItem({complete: true, something: 'item3'}) ) var completeCounts = col.countBy(function (sqim) { return sqim.get('complete') ? 'done' : 'undone' }) // completeCounts = {done: 2, undone: 1} var isEveryComplete = col.every(function (sqim) { return sqim.get('complete') }) // isEveryComplete = false,       complete == true. var allComplete = col.filter( Sqimitive.Sqimitive.picker('get', 'complete') ) // ,  picker() -  ,  . // allComplete = [MyItem item1, MyItem item3] -    complete == true. var firstComplete = col.find( Sqimitive.Sqimitive.picker('get', 'complete') ) // firstComplete = MyItem item1 ( complete == true).  undefined, var doneUndone = col.partition( Sqimitive.Sqimitive.picker('get', 'complete') ) // doneUndone = [[item1, item3], [item2]] -  ,  //     ,    -  . var firstChild = col.first() var lastChild = col.last() var parentKeys = col.keys() var three = col.length var item2 = col.at(1) var item2_3 = col.slice(1, 1) var somethings = col.invoke('get', 'something') // somethings = ['bar', 'item2', 'item3'] -     //    ,       col. var sorted = col.sortBy( Sqimitive.Sqimitive.picker('get', 'something') ) // sorted = [item1, item2, item3] -   ,  //  ,   . var serialized = col.invoke('get') //  Backbone.Collection.toJSON(),   shallow copy. col.invoke('render') //  render()    .  . var cids = col.map(function (sqim) { return sqim._cid }) // cids = ['p11', 'p12', 'p13'] -   invoke(),   //   . _cid -   . col.each(function (sqim, key) { alert(key + ': ' + sqim._cid) }, col) //   3    col (this). 


?


— , , Backbone. state-based programming , , , - ( _updateSize _checkInput ).

— . , — Angular , Knockout , , React . Sqimitive , ( HTML ), ( ), , .

 var MyView = Sqimitive.Sqimitive.extend({ _opt: { name: '', surname: '', age: 900, }, events: { change: function (opt, value) { this.$('.' + opt).text(value) }, render: function () { this.el.empty() .append( $('<p class=name>').text(this.get('name')) ) .append( $('<p class=surname>').text(this.get('surname')) ) .append( $('<p class=age>').text(this.get('age')) ) }, }, }) 

( JSFiddle ) :


 var MyModel = Sqimitive.Sqimitive.extend({ _opt: { name: '', surname: '', age: 900, }, }) var MyView = Sqimitive.Sqimitive.extend({ _opt: { model: null, }, // ,           //  <script id="MyView" type="text/template">   <template>. //  ,  Sqimitive   ,   , //         . _template: _.template( '<p class="name"><%- name %></p>' + '<p class="surname"><%- surname %></p>' + '<p class="age"><%- age %></p>'), events: { //      , change    // ,   (   Backbone). change_model: function (newModel, oldModel) { //    ,       . oldModel && oldModel.off(this) newModel.on('change', '_modelChanged', this) }, render: function () { // get()    toJSON()  Backbone,  //      (shallow copy). this.el.html( this._template(this.get('model').get()) ) }, }, _modelChanged: function (opt, value) { if (/^(name|surname|age)$/.test(opt)) { this.$('.' + opt).text(value) } }, }) 

( JSFiddle ):

 var view = new MyView({ el: $('#foo'), model: new MyModel, }) //  . ,         //   .    postInit,     . view.render() view.get('model').set('name', '') //  _modelChanged('name', '', '') 

— , , Sqimitive , , ( Ember? Knockout? Backbone? Angular? ), .

, Backbone View < Collection < Model Sqimitive View < Model ( - ), . , Sqimitive.


:


, — . Sqimitive ( owning ), — ( non-owning ). :


, ( ) , — .

( JSFiddle ):

 var MyList = Sqimitive.Sqimitive.extend({ //   . _owning: false, _childClass: MyItem, _childEvents: ['change'], }) var item = new MyItem var list1 = new MyList list1.nest(item) var list2 = new MyList list2.nest(item) alert(list1.length + ' ' + list2.length) // alert('1 1') 

( JSFiddle ):

 var MyList = Sqimitive.Sqimitive.extend({ // true    -    . _owning: true, _childClass: MyItem, _childEvents: ['change'], }) var item = new MyItem var list1 = new MyList list1.nest(item) var list2 = new MyList list2.nest(item) alert(list1.length + ' ' + list2.length) // alert('0 1') alert(item._parent === list2) // alert('TRUE') 



— , , , , - . , .

— Views.

Sqimitive Backbone — , MVC . - — , DOM HTML render DOM , — . (, , - , , .)

( JSFiddle ):

 var MyView = Sqimitive.Sqimitive.extend({ el: {tag: 'aside', className: 'info'}, _opt: { //  - . loaded: false, }, elEvents: { 'click .something': '_somethingClicked', }, events: { //      render.      . change_loaded: function (value) { this.el.toggleClass('loaded', value) }, render: function () { this.el.html('Click <u class="something">here</u>?') }, }, _somethingClicked: function (e) { alert(e.target.tagName + ' clicked!') }, }) var view = new MyView({el: $('body')}) view .render() //   .attach() //    DOM (elEvents) .set('loaded', true) // <aside class="info loaded"> 

— Sqimitive:

 var MyList = Sqimitive.Sqimitive.extend({ // el   ,     <div>. //  ,   DOM   ,   ,  , //      . el: false, //   ,       . _childClass: MyItem, }) var MyViewItem = Sqimitive.Sqimitive.extend({ el: {tag: 'li'}, _opt: { model: null, // MyItem. //   this.el   .  . attachPath: '.', }, events: { change_model: function (newModel, oldModel) { oldModel && oldModel.off(this) // ,        -    //         . MyList   . //      -     unnested. newModel.on({ change: 'render', // remove -  ,       //   el (unnest    ,   el   ). //   on    this (3- ),   remove //    -unnest      MyViewItem. '-unnest': 'remove', }, this) }, unnest: function () { //      -   MyViewItem  //      (  ).    //   ,   ,     . this.get('model') && this.get('model').off(this) }, render: function () { this.el.html(...) }, }, }) var MyViewList = Sqimitive.Sqimitive.extend({ el: {tag: 'ul'}, _childClass: MyViewItem, _opt: { list: null, // MyList. }, events: { change_list: function (newList, oldList) { oldList && oldList.off(this) newList.on('+nest', '_modelAdded', this) //      newList. this.invoke('remove') newList.each(this._modelAdded, this) }, }, _modelAdded: function (sqim) { this.nest( new MyViewItem({model: sqim}) ) .render() }, }) 

MyViewList , () MyListMyViewItem . — / , ( MyViewItem ).

el :


MyViewList , .

( JSFiddle ):

 var list = new MyList list.nest(new MyItem) // list.length == 1 var view = new MyViewList({list: list}) // view.length == 1 list.nest(new MyItem) // list.length == view.length == 2 list.at(1).remove() // list.length == view.length == 1 var list2 = new MyList view.set('list', list2) // list.length == 1 // list2.length == view.length == 0 



, , , . ?

, ( ). , - … , . frontend backend , — - .

-, , - . !

— AJAX — - . « API», , , — .

backend — Backbone jQuery Knockout Ember . Backbone sync . , — parse , fetch , createBackbone.sync , .

Sqimitive . : , 80% sync . , , , -, ( fetch parse ), .

20% — $.ajax() 5 .

. — : Backbone . API ( REST), . , , , sync . API , , parse .

, ?

, , , , , — . Backbone sync — , , . , Rails.

« ...»
, Backbone, 6 000 . fetch() . Backbone - parse .

, , , , parse . , parse . , -, .

Sqimitive . , — , API, cookies, Local Storage , location.hash . : assignChildren assignResp .

assignChildren — Backbone. Collection .parse + set. , - . , — ( ). assignChildren ( _childClass ) assignResp. — , — , — .

assignRespModel .parse + set. — . , normalize_OPT , change_OPT change , set , .

API JavaScript. , «» , .

, — . , assignResp .


assignResp _respToOpt — API


, ( - assignChildren ):

 { 'thisID': 123, 'date': '2014-10-03T10:26:22+03:00', 'parents': '1 2 3', 'junk': 'ONk49Xo3SxEps8uCV9je8dhez', 'caption': '', } 

:

 var MyModel = Sqimitive.Sqimitive.extend({ //   -.  defaults  Backbone. _opt: { id: 0, date: new Date, parents: [], caption: '', }, }) 

:


:

 var MyModel = Sqimitive.Sqimitive.extend({ _opt: { id: 0, date: new Date, parents: [], caption: '', }, //       assignResp   ,  //         . _respToOpt: { //    . thisID: 'id', //   .   ,  //   -   ,   -    . date: function (str) { return ['date', new Date(str)] }, //  . junk: false, parents: function (str) { return ['parents', str.split(/\s+/)] }, //  /  .  caption: 'caption'. caption: true, }, // ,   -  ,      . normalize_id: function (value) { var id = parseInt(value) if (isNaN(id)) { throw 'What kind of ID is that?' } return id }, normalize_parents: function (value) { return _.map([].concat(value), this.normalize_id, this) }, normalize_caption: function (value) { return value.replace(/^\s+|\s+$/g, '') }, }) 

( JSFiddle ):

 var model = new MyModel $.getJSON('api/route', _.bind(model.assignResp, model)) //   POST: $.ajax({ url: 'api/route', type: 'POST', data: {...}, context: model, success: model.assignResp, }) 


, ?


, . , , Sqimitive . — . « Zen Book ».

_shareProps:


Let's not talk about languages that suck . Let's talk about Python.

Python, () — , . For example:

 class SomeObject: list = [] def push(self, value): self.list.append(value) return self print SomeObject().push('123').list #=> ['123'] print SomeObject().push('345').list #=> ['123', '456'] 

JavaScript Python - — , . JSFiddle :

 var SomeObject = Backbone.View.extend({ list: [], push: function (value) { this.list.push(value) return this }, }) console.dir( (new SomeObject).push('123').list ) //=> ['123'] console.dir( (new SomeObject).push('456').list ) //=> ['123', '456'] 

, , : , - , . , , — ( ), extend .

Is it logical Of course. ? , Windows (, , ).

, — .

Sqimitive (deep copy) . , , _shareProps . , — _childClass , . , init .

 var MySqimitive = Sqimitive.Sqimitive.extend({ ... }) MySqimitive._shareProps.push('notToCloneSomething') 


_mergeProps:


Backbone, , . , , __super__ . ( JSFiddle ):

 var MyView = Backbone.View.extend({ events: { 'click .me': function () { alert(' !') }, }, }) var MyOtherView = MyView.extend({ events: { 'keypress .me': function () { alert('   :(') }, }, }) 

MyOtherView MyView events . : events: _.extend(MyView.prototype.events, {...}) , events . ( ), . events , .

_mergeProps — -, , , . base.concat(child) , — _.extend(base, child) ( ). , , null / undefined , .

_mergeProps elEvents , events _opt ( _shareProps ) — Backbone Sqimitive : MyOtherView .

( JSFiddle ):

 var MyBase = Sqimitive.Sqimitive.extend({ _opt: { base: 123, base2: 'kept', complex: {a: 1}, }, }) var MyChild = MyBase.extend({ _opt: { //  123. base: 'replaced', //   {a: 1} -      . complex: {b: 2}, //     _opt. child: 'new', // base2 - . //base2: 'kept', }, }) 


masker():


, Sqimitive. , callback- , . , nest(key, object) ID var list = {11: obj1, 22: obj2, 33: obj3} .

This can be done in several ways:


masker m . , -, ( ), — ( «» ).

: API $.ajax . jQuery success , assignResp , . , — jQuery . :

 $.getJSON('api/route', masker(model.assignResp, '.', model)) 

, — . , assignResp .

masker events , elEvents , on once [] (. expandFunc ). — , ( JSFiddle ):

 var MyView = Sqimitive.Sqimitive.extend({ // / .      //  . toggle: function (state) { arguments.length || (state = !this.$('.contents').is(':visible')) this.$('.contents').toggle(!!state) return this }, elEvents: { //      .    - . //     ,    . 'click .toggle': 'toggle-', // jQuery    event object,  == true. 'click .show': 'toggle', // 9  jQuery   ,    != true. //       -       toggle9. 'click .hide': 'toggle-9', }, }) 

— , - ,

  elEvents: { 'click .toggle': function () { this.toggle() }, 'click .show': function () { this.toggle(true) }, 'click .hide': function () { this.toggle(false) }, }, 


… !


. , «», . ? , , - ?

, . GitHub , — squizzle.me , — . , — .

.

Squizzle  it

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


All Articles