using the Backbone.View.Elements library
It’s not yet another post about slick and sexy code architecture you can achieve using React, Angular or what’s now in vogue? The article is about situation when you have pile of jQuery spaghetti wrapped to Backbone views. Sounds familiar?The problem #1: poorly expressive selectors
All of us know spaghetti in JavaScript is reflection of malformed HTML templates. Therefore, most likely such the code contains some tricky ambiguous DOM transformations and manipulations. It’s very hard to comprehend it because you need to keep in mind a lot of indistinct names of elements while you are trying to find out what’s going on in general. Let’s give expression to our code:_selectors: function () { return { elemName: '.block__elem-name' }; }Move selectors to one place and give understandable names to elements which will be retrieved by them. Now we can search elements of the view like this
this._elem('elemName');instead of
this.$('.block__elem-name');Not bad? Let’s go ahead!
In this particular case you can say the code became more expressive a bit, but do not forget we talking about a project with very cool selectors beyond the semantic like
div > tr.row[data-active="true"] a.red-Buttonfor “Buy” buttons.
Yet another advantage using Backbone.View.Elements is that you have to change the selector in the only one place in JavaScript if your HTML template is changed. Code duplication reduced. Cool.
The problem #2: elements storage
Sometimes you can see$('div > tr.row[data-active="true"] a.red-Button').blahBlah();and in 10 lines
$('div > tr.row[data-active="true"] a.red-Button').anotherBlahBlah();
You think “facepaaalm” and move the expression to a variable:
var $buyButton = $('div > tr.row[data-active="true"] a.red-Button');no, you are using Backbone, you move it to a property
this._$buyButton = this.$('div > tr.row[data-active="true"] a.red-Button');or you have already included Backbone.View.Elements?
this._$buyButton = this._elem('buyButton');
Actually you don’t need to keep it anywhere because _elem caches results. So just use
this._elem('buyButton');
It caches.. and what about invalidation?
Yeah, we also heard about the two hard problems. Thereforethis._findElem('elemName');searches elements without using the cache
this._dropElemCache('elemName');clears the cache for the particular element
this._dropElemCache();
drops all your cache when you realize the time is now. For example, after rendering.Global things
Specially for you we wrapped to jQuery most often used elements and placed them to properties:this._$window; this._$body; this._$document;
The problem #3: imperative styles
There is CSS, the whole language to describe styles, but we are talking about another surprising world, we are talking about spaghetti where styles are stuck in JavaScript creating one more barrier for code reading.$('div > tr.row[data-active="true"] a.red-Button').css({color: 'magenta'});
Let’s declare styles inside stylesheets
.button_active { color: magenta; }and Backbone.View.Elements will care about classes manipulations. First, move all CSS classes to one method
_classes: function () { return { activeButton: 'button_active' }; }then you will be able to add classes
this._addClass('activeButton', 'buyButton');remove classes
this._removeClass('activeButton', 'buyButton');and toggle classes
var condition = !!Math.round(Math.random()); this._toggleClass('activeButton', 'buyButton', condition);
You can also generate a selector by class name
this._selector('activeButton'); // returns '.button_active'or search elements by this selector
this._elem('activeButton');But do not forget about cache since the active button probably changes in time
this._findElem('activeButton');
The problem #4: when everything becomes difficult
From time to time we need to generate classes and selectors dynamicallyvar id = 5, state = 'highlighted'; $('.item[data-id="' + id + '"]').addClass('item_state_' + state);and understanding becomes complicated dramatically. Welcome complex selectors!
_classes: function () { return { itemInState: 'item_state_%s' }; }, _selectors: function () { return { itemById: '.item[data-id=%s]' }; }Hardly have you described them like this when the code below becomes true
this._class('itemInState', 'highlighted') === 'item_state_highlighted'; this._selector('itemInState', 'highlighted') === '.item_state_highlighted'; this._selector('itemById', 5) === '.item[data-id=5]';And the manipulation above turns to
var id = 5, state = 'highlighted'; this._addClass(['itemInState', state], ['itemById', id]);The class "item_state_highlighted" will be added to jQuery collection found by selector ".item[data-id=5]"
Extreme selectors complexity
You can use named placeholders for selectors and classes_classes: function () { return { item: 'item_%(mod)s_%(value)s' }; }And then
this._elem('item', { mod: 'state', value: 'focused' });will find jQuery collection by ".item_state_focused"
Problem #5: retrieving data
We’ve added some sugar for data attributes. All of them for the root element can be found inside the "_data" property. For example, if you have your view initialized on a div<div data-some-ids="[5,6,7]"></div>then
this._data['someIds']; // returns [5,6,7]
And if you have data for particular element of the view you can rely to
this._getElemData('elemName', 'someIds');or for retriving whole data
this._getElemData('elemName'); // returns {someIds: [5,6,7]}
About inclusion and installation..
..and more documented API please read Readme.md on GitHubYou might also enjoy git diff of todomvc with Backbone.View.Elements and without it
Thanks for reading! Have a nice code :)
No comments:
Post a Comment