chiropractor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chiropractor.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Andrew Eberbach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ # Chiropractor
2
+
3
+ I miss ActiveSupport. I really do. The javascript standard library is lacking. So this is a collection of some useful Backbone and Underscore components. Saves me from having to keep downloading them into every project. This seems like a reasonable stack that gives you pretty powerful framework for building backbone apps.
4
+
5
+ Included:
6
+
7
+ * Underscore.Inflector: https://github.com/jeremyruppel/underscore.inflection
8
+ * Underscore.String: https://github.com/epeli/underscore.string
9
+ * Backbone.Marionette: https://github.com/marionettejs/backbone.marionette
10
+ * Backbone.Relational: https://github.com/PaulUithol/Backbone-relational
11
+ * Backbone.ModelBinder: https://github.com/theironcook/Backbone.ModelBinder
12
+ * AccountingJS: http://josscrowcroft.github.com/accounting.js/
13
+ * MomentJS: https://github.com/timrwood/moment/
14
+
15
+ Also have a couple extensions:
16
+
17
+ * Backbone.LayoutRegion: Combines Layouts and Regions from Marionette
18
+ * Backbone.JST: Adds JST support to Marionette
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'chiropractor'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install chiropractor
33
+
34
+ ## Usage
35
+
36
+ ```javascript
37
+ //= require chiropractor
38
+ ```
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ scripts = {
4
+ "https://github.com/jeremyruppel/underscore.inflection" => "https://raw.github.com/jeremyruppel/underscore.inflection/master/src/underscore.inflection.js",
5
+ "https://github.com/epeli/underscore.string" => "https://raw.github.com/epeli/underscore.string/master/lib/underscore.string.js",
6
+ "https://github.com/timrwood/moment/" => "https://raw.github.com/timrwood/moment/master/moment.js",
7
+ "http://josscrowcroft.github.com/accounting.js/" => "https://raw.github.com/josscrowcroft/accounting.js/master/accounting.js",
8
+ "https://github.com/PaulUithol/Backbone-relational" => "https://raw.github.com/PaulUithol/Backbone-relational/master/backbone-relational.js",
9
+ "https://github.com/theironcook/Backbone.ModelBinder" => "https://raw.github.com/theironcook/Backbone.ModelBinder/master/Backbone.ModelBinder.js"
10
+ }
11
+
12
+ require 'open-uri'
13
+ desc "Update scripts"
14
+ task :update_scripts do
15
+ dir = File.expand_path("../vendor/assets/javascripts/chiropractor", __FILE__)
16
+ FileUtils.mkdir_p(dir)
17
+ scripts.each do |repo_url, file_url|
18
+ file = File.basename(URI.parse(file_url).path)
19
+ output = File.join(dir,file)
20
+ File.open(output, "w+"){|f| f.write(open(file_url).read)}
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chiropractor/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "chiropractor"
8
+ gem.version = Chiropractor::VERSION
9
+ gem.authors = ["Andrew Eberbach"]
10
+ gem.email = ["andrew@ebertech.ca"]
11
+ gem.description = %q{A helpful collection of tools to help your Backbone}
12
+ gem.summary = %q{A helpful collection of tools to help your Backbone}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency "backbone-on-rails"
21
+ gem.add_runtime_dependency "marionette-rails"
22
+ gem.add_runtime_dependency "coffee-rails"
23
+ gem.add_runtime_dependency "jquery-rails"
24
+ end
@@ -0,0 +1,11 @@
1
+ require "backbone-on-rails"
2
+ require "marionette-rails"
3
+ require "coffee-rails"
4
+ require "jquery-rails"
5
+
6
+ module Chiropractor
7
+ autoload :Engine, 'chiropractor/engine'
8
+ autoload :VERSION, 'chiropractor/version'
9
+ end
10
+
11
+ Chiropractor::Engine
@@ -0,0 +1,6 @@
1
+ module Chiropractor
2
+ class Engine < Rails::Engine
3
+
4
+ end
5
+ end
6
+
@@ -0,0 +1,3 @@
1
+ module Chiropractor
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,482 @@
1
+ // Backbone.ModelBinder v0.1.6
2
+ // (c) 2012 Bart Wood
3
+ // Distributed Under MIT License
4
+
5
+ (function (factory) {
6
+ if (typeof define === 'function' && define.amd) {
7
+ // AMD. Register as an anonymous module.
8
+ define(['underscore', 'jquery', 'backbone'], factory);
9
+ } else {
10
+ // Browser globals
11
+ factory(_, $, Backbone);
12
+ }
13
+ }(function(_, $, Backbone){
14
+
15
+ if(!Backbone){
16
+ throw 'Please include Backbone.js before Backbone.ModelBinder.js';
17
+ }
18
+
19
+ Backbone.ModelBinder = function(modelSetOptions){
20
+ _.bindAll(this);
21
+ this._modelSetOptions = modelSetOptions || {};
22
+ };
23
+
24
+ // Current version of the library.
25
+ Backbone.ModelBinder.VERSION = '0.1.6';
26
+ Backbone.ModelBinder.Constants = {};
27
+ Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
28
+ Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
29
+
30
+ _.extend(Backbone.ModelBinder.prototype, {
31
+
32
+ bind:function (model, rootEl, attributeBindings, modelSetOptions) {
33
+ this.unbind();
34
+
35
+ this._model = model;
36
+ this._rootEl = rootEl;
37
+ this._modelSetOptions = _.extend({}, this._modelSetOptions, modelSetOptions);
38
+
39
+ if (!this._model) throw 'model must be specified';
40
+ if (!this._rootEl) throw 'rootEl must be specified';
41
+
42
+ if(attributeBindings){
43
+ // Create a deep clone of the attribute bindings
44
+ this._attributeBindings = $.extend(true, {}, attributeBindings);
45
+
46
+ this._initializeAttributeBindings();
47
+ this._initializeElBindings();
48
+ }
49
+ else {
50
+ this._initializeDefaultBindings();
51
+ }
52
+
53
+ this._bindModelToView();
54
+ this._bindViewToModel();
55
+ },
56
+
57
+ bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) {
58
+ this._triggers = triggers;
59
+ this.bind(model, rootEl, attributeBindings, modelSetOptions)
60
+ },
61
+
62
+ unbind:function () {
63
+ this._unbindModelToView();
64
+ this._unbindViewToModel();
65
+
66
+ if(this._attributeBindings){
67
+ delete this._attributeBindings;
68
+ this._attributeBindings = undefined;
69
+ }
70
+ },
71
+
72
+ // Converts the input bindings, which might just be empty or strings, to binding objects
73
+ _initializeAttributeBindings:function () {
74
+ var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding;
75
+
76
+ for (attributeBindingKey in this._attributeBindings) {
77
+ inputBinding = this._attributeBindings[attributeBindingKey];
78
+
79
+ if (_.isString(inputBinding)) {
80
+ attributeBinding = {elementBindings: [{selector: inputBinding}]};
81
+ }
82
+ else if (_.isArray(inputBinding)) {
83
+ attributeBinding = {elementBindings: inputBinding};
84
+ }
85
+ else if(_.isObject(inputBinding)){
86
+ attributeBinding = {elementBindings: [inputBinding]};
87
+ }
88
+ else {
89
+ throw 'Unsupported type passed to Model Binder ' + attributeBinding;
90
+ }
91
+
92
+ // Add a linkage from the element binding back to the attribute binding
93
+ for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){
94
+ elementBinding = attributeBinding.elementBindings[elementBindingCount];
95
+ elementBinding.attributeBinding = attributeBinding;
96
+ }
97
+
98
+ attributeBinding.attributeName = attributeBindingKey;
99
+ this._attributeBindings[attributeBindingKey] = attributeBinding;
100
+ }
101
+ },
102
+
103
+ // If the bindings are not specified, the default binding is performed on the name attribute
104
+ _initializeDefaultBindings: function(){
105
+ var elCount, namedEls, namedEl, name, attributeBinding;
106
+ this._attributeBindings = {};
107
+ namedEls = $('[name]', this._rootEl);
108
+
109
+ for(elCount = 0; elCount < namedEls.length; elCount++){
110
+ namedEl = namedEls[elCount];
111
+ name = $(namedEl).attr('name');
112
+
113
+ // For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings
114
+ if(!this._attributeBindings[name]){
115
+ attributeBinding = {attributeName: name};
116
+ attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [namedEl]}];
117
+ this._attributeBindings[name] = attributeBinding;
118
+ }
119
+ else{
120
+ this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [namedEl]});
121
+ }
122
+ }
123
+ },
124
+
125
+ _initializeElBindings:function () {
126
+ var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el;
127
+ for (bindingKey in this._attributeBindings) {
128
+ attributeBinding = this._attributeBindings[bindingKey];
129
+
130
+ for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
131
+ elementBinding = attributeBinding.elementBindings[bindingCount];
132
+ if (elementBinding.selector === '') {
133
+ foundEls = $(this._rootEl);
134
+ }
135
+ else {
136
+ foundEls = $(elementBinding.selector, this._rootEl);
137
+ }
138
+
139
+ if (foundEls.length === 0) {
140
+ throw 'Bad binding found. No elements returned for binding selector ' + elementBinding.selector;
141
+ }
142
+ else {
143
+ elementBinding.boundEls = [];
144
+ for (elCount = 0; elCount < foundEls.length; elCount++) {
145
+ el = foundEls[elCount];
146
+ elementBinding.boundEls.push(el);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ },
152
+
153
+ _bindModelToView: function () {
154
+ this._model.on('change', this._onModelChange, this);
155
+
156
+ this.copyModelAttributesToView();
157
+ },
158
+
159
+ // attributesToCopy is an optional parameter - if empty, all attributes
160
+ // that are bound will be copied. Otherwise, only attributeBindings specified
161
+ // in the attributesToCopy are copied.
162
+ copyModelAttributesToView: function(attributesToCopy){
163
+ var attributeName, attributeBinding;
164
+
165
+ for (attributeName in this._attributeBindings) {
166
+ if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){
167
+ attributeBinding = this._attributeBindings[attributeName];
168
+ this._copyModelToView(attributeBinding);
169
+ }
170
+ }
171
+ },
172
+
173
+ _unbindModelToView: function(){
174
+ if(this._model){
175
+ this._model.off('change', this._onModelChange);
176
+ this._model = undefined;
177
+ }
178
+ },
179
+
180
+ _bindViewToModel: function () {
181
+ if (this._triggers) {
182
+ _.each(this._triggers, function (event, selector) {
183
+ $(this._rootEl).delegate(selector, event, this._onElChanged);
184
+ }, this);
185
+ }
186
+ else {
187
+ $(this._rootEl).delegate('', 'change', this._onElChanged);
188
+ // The change event doesn't work properly for contenteditable elements - but blur does
189
+ $(this._rootEl).delegate('[contenteditable]', 'blur', this._onElChanged);
190
+ }
191
+ },
192
+
193
+ _unbindViewToModel: function () {
194
+ if (this._rootEl) {
195
+ if (this._triggers) {
196
+ _.each(this._triggers, function (event, selector) {
197
+ $(this._rootEl).undelegate(selector, event, this._onElChanged);
198
+ }, this);
199
+ }
200
+ else {
201
+ $(this._rootEl).undelegate('', 'change', this._onElChanged);
202
+ $(this._rootEl).undelegate('[contenteditable]', 'blur', this._onElChanged);
203
+ }
204
+ }
205
+ },
206
+
207
+ _onElChanged:function (event) {
208
+ var el, elBindings, elBindingCount, elBinding;
209
+
210
+ el = $(event.target)[0];
211
+ elBindings = this._getElBindings(el);
212
+
213
+ for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){
214
+ elBinding = elBindings[elBindingCount];
215
+ if (this._isBindingUserEditable(elBinding)) {
216
+ this._copyViewToModel(elBinding, el);
217
+ }
218
+ }
219
+ },
220
+
221
+ _isBindingUserEditable: function(elBinding){
222
+ return elBinding.elAttribute === undefined ||
223
+ elBinding.elAttribute === 'text' ||
224
+ elBinding.elAttribute === 'html';
225
+ },
226
+
227
+ _getElBindings:function (findEl) {
228
+ var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl;
229
+ var elBindings = [];
230
+
231
+ for (attributeName in this._attributeBindings) {
232
+ attributeBinding = this._attributeBindings[attributeName];
233
+
234
+ for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
235
+ elementBinding = attributeBinding.elementBindings[elementBindingCount];
236
+
237
+ for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
238
+ boundEl = elementBinding.boundEls[boundElCount];
239
+
240
+ if (boundEl === findEl) {
241
+ elBindings.push(elementBinding);
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ return elBindings;
248
+ },
249
+
250
+ _onModelChange:function () {
251
+ var changedAttribute, attributeBinding;
252
+
253
+ for (changedAttribute in this._model.changedAttributes()) {
254
+ attributeBinding = this._attributeBindings[changedAttribute];
255
+
256
+ if (attributeBinding) {
257
+ this._copyModelToView(attributeBinding);
258
+ }
259
+ }
260
+ },
261
+
262
+ _copyModelToView:function (attributeBinding) {
263
+ var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue;
264
+
265
+ value = this._model.get(attributeBinding.attributeName);
266
+
267
+ for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
268
+ elementBinding = attributeBinding.elementBindings[elementBindingCount];
269
+
270
+ for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
271
+ boundEl = elementBinding.boundEls[boundElCount];
272
+
273
+ if(!boundEl._isSetting){
274
+ convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
275
+ this._setEl($(boundEl), elementBinding, convertedValue);
276
+ }
277
+ }
278
+ }
279
+ },
280
+
281
+ _setEl: function (el, elementBinding, convertedValue) {
282
+ if (elementBinding.elAttribute) {
283
+ this._setElAttribute(el, elementBinding, convertedValue);
284
+ }
285
+ else {
286
+ this._setElValue(el, convertedValue);
287
+ }
288
+ },
289
+
290
+ _setElAttribute:function (el, elementBinding, convertedValue) {
291
+ switch (elementBinding.elAttribute) {
292
+ case 'html':
293
+ el.html(convertedValue);
294
+ break;
295
+ case 'text':
296
+ el.text(convertedValue);
297
+ break;
298
+ case 'enabled':
299
+ el.attr('disabled', !convertedValue);
300
+ break;
301
+ case 'displayed':
302
+ el[convertedValue ? 'show' : 'hide']();
303
+ break;
304
+ case 'hidden':
305
+ el[convertedValue ? 'hide' : 'show']();
306
+ break;
307
+ case 'css':
308
+ el.css(elementBinding.cssAttribute, convertedValue);
309
+ break;
310
+ case 'class':
311
+ var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName);
312
+ if(!_.isUndefined(previousValue)){
313
+ previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue);
314
+ el.removeClass(previousValue);
315
+ }
316
+
317
+ if(convertedValue){
318
+ el.addClass(convertedValue);
319
+ }
320
+ break;
321
+ default:
322
+ el.attr(elementBinding.elAttribute, convertedValue);
323
+ }
324
+ },
325
+
326
+ _setElValue:function (el, convertedValue) {
327
+ if(el.attr('type')){
328
+ switch (el.attr('type')) {
329
+ case 'radio':
330
+ if (el.val() === convertedValue) {
331
+ el.attr('checked', 'checked');
332
+ }
333
+ break;
334
+ case 'checkbox':
335
+ if (convertedValue) {
336
+ el.attr('checked', 'checked');
337
+ }
338
+ else {
339
+ el.removeAttr('checked');
340
+ }
341
+ break;
342
+ default:
343
+ el.val(convertedValue);
344
+ }
345
+ }
346
+ else if(el.is('input') || el.is('select') || el.is('textarea')){
347
+ el.val(convertedValue);
348
+ }
349
+ else {
350
+ el.text(convertedValue);
351
+ }
352
+ },
353
+
354
+ _copyViewToModel: function (elementBinding, el) {
355
+ var value, convertedValue;
356
+
357
+ if (!el._isSetting) {
358
+
359
+ el._isSetting = true;
360
+ this._setModel(elementBinding, $(el));
361
+ el._isSetting = false;
362
+
363
+ if(elementBinding.converter){
364
+ value = this._model.get(elementBinding.attributeBinding.attributeName);
365
+ convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
366
+ this._setEl($(el), elementBinding, convertedValue);
367
+ }
368
+ }
369
+ },
370
+
371
+ _getElValue: function(elementBinding, el){
372
+ switch (el.attr('type')) {
373
+ case 'checkbox':
374
+ return el.prop('checked') ? true : false;
375
+ default:
376
+ if(el.attr('contenteditable') !== undefined){
377
+ return el.html();
378
+ }
379
+ else {
380
+ return el.val();
381
+ }
382
+ }
383
+ },
384
+
385
+ _setModel: function (elementBinding, el) {
386
+ var data = {};
387
+ var elVal = this._getElValue(elementBinding, el);
388
+ elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal);
389
+ data[elementBinding.attributeBinding.attributeName] = elVal;
390
+ var opts = _.extend({}, this._modelSetOptions, {changeSource: 'ModelBinder'});
391
+ this._model.set(data, opts);
392
+ },
393
+
394
+ _getConvertedValue: function (direction, elementBinding, value) {
395
+ if (elementBinding.converter) {
396
+ value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model);
397
+ }
398
+
399
+ return value;
400
+ }
401
+ });
402
+
403
+ Backbone.ModelBinder.CollectionConverter = function(collection){
404
+ this._collection = collection;
405
+
406
+ if(!this._collection){
407
+ throw 'Collection must be defined';
408
+ }
409
+ _.bindAll(this, 'convert');
410
+ };
411
+
412
+ _.extend(Backbone.ModelBinder.CollectionConverter.prototype, {
413
+ convert: function(direction, value){
414
+ if (direction === Backbone.ModelBinder.Constants.ModelToView) {
415
+ return value ? value.id : undefined;
416
+ }
417
+ else {
418
+ return this._collection.get(value);
419
+ }
420
+ }
421
+ });
422
+
423
+ // A static helper function to create a default set of bindings that you can customize before calling the bind() function
424
+ // rootEl - where to find all of the bound elements
425
+ // attributeType - probably 'name' or 'id' in most cases
426
+ // converter(optional) - the default converter you want applied to all your bindings
427
+ // elAttribute(optional) - the default elAttribute you want applied to all your bindings
428
+ Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){
429
+ var foundEls, elCount, foundEl, attributeName;
430
+ var bindings = {};
431
+
432
+ foundEls = $('[' + attributeType + ']', rootEl);
433
+
434
+ for(elCount = 0; elCount < foundEls.length; elCount++){
435
+ foundEl = foundEls[elCount];
436
+ attributeName = $(foundEl).attr(attributeType);
437
+
438
+ if(!bindings[attributeName]){
439
+ var attributeBinding = {selector: '[' + attributeType + '="' + attributeName + '"]'};
440
+ bindings[attributeName] = attributeBinding;
441
+
442
+ if(converter){
443
+ bindings[attributeName].converter = converter;
444
+ }
445
+
446
+ if(elAttribute){
447
+ bindings[attributeName].elAttribute = elAttribute;
448
+ }
449
+ }
450
+ }
451
+
452
+ return bindings;
453
+ };
454
+
455
+ // Helps you to combine 2 sets of bindings
456
+ Backbone.ModelBinder.combineBindings = function(destination, source){
457
+ _.each(source, function(value, key){
458
+ var elementBinding = {selector: value.selector};
459
+
460
+ if(value.converter){
461
+ elementBinding.converter = value.converter;
462
+ }
463
+
464
+ if(value.elAttribute){
465
+ elementBinding.elAttribute = value.elAttribute;
466
+ }
467
+
468
+ if(!destination[key]){
469
+ destination[key] = elementBinding;
470
+ }
471
+ else {
472
+ destination[key] = [destination[key], elementBinding];
473
+ }
474
+ });
475
+
476
+ return destination;
477
+ };
478
+
479
+
480
+ return Backbone.ModelBinder;
481
+
482
+ }));