backbone_model_binder-rails 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/backbone_model_binder-rails.gemspec +23 -0
- data/lib/backbone_model_binder-rails.rb +8 -0
- data/lib/backbone_model_binder-rails/version.rb +5 -0
- data/vendor/assets/javascripts/Backbone.CollectionBinder.js +281 -0
- data/vendor/assets/javascripts/Backbone.CollectionBinder.min.js +5 -0
- data/vendor/assets/javascripts/Backbone.ModelBinder.js +576 -0
- data/vendor/assets/javascripts/Backbone.ModelBinder.min.js +4 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 8c7ab63a707ada43aaf91e8f026d3c80b8b54f3c
|
|
4
|
+
data.tar.gz: 504606265014a3dffe2fa6af234b31a2030b688c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 15ac126a2bf79baf03a30999b211c7d2a6b0728ed276a304c23b5ed4d33ab1eec965f260049a0a1b91f25c215784344dc09ec4b0ec62178948317fd9c017e7f5
|
|
7
|
+
data.tar.gz: 0d9b96ad97f30085d67e4135e30d315ff3ace1cc2a4d670fd8bf86f606c1a074c2a5b022db43346d5c59085d47548a017d94258de042d4d71ba393152065de7b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Anton Taraev
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# BackboneModelBinder::Rails
|
|
2
|
+
|
|
3
|
+
TODO: Write a gem description
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'backbone_model_binder-rails'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install backbone_model_binder-rails
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'backbone_model_binder-rails/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "backbone_model_binder-rails"
|
|
8
|
+
spec.version = BackboneModelBinder::Rails::VERSION
|
|
9
|
+
spec.authors = ["Anton Taraev"]
|
|
10
|
+
spec.email = ["anti191@gmail.com"]
|
|
11
|
+
spec.description = %q{Backbone.ModelBinding for Rails}
|
|
12
|
+
spec.summary = %q{Vendors Backbone.ModelBinding for use with the asset pipeline.}
|
|
13
|
+
spec.homepage = ""
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
|
22
|
+
spec.add_development_dependency "rake"
|
|
23
|
+
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Backbone.CollectionBinder v1.0.2
|
|
2
|
+
// (c) 2013 Bart Wood
|
|
3
|
+
// Distributed Under MIT License
|
|
4
|
+
|
|
5
|
+
(function(){
|
|
6
|
+
|
|
7
|
+
if(!Backbone){
|
|
8
|
+
throw 'Please include Backbone.js before Backbone.ModelBinder.js';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if(!Backbone.ModelBinder){
|
|
12
|
+
throw 'Please include Backbone.ModelBinder.js before Backbone.CollectionBinder.js';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Backbone.CollectionBinder = function(elManagerFactory, options){
|
|
16
|
+
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
|
17
|
+
this._elManagers = {};
|
|
18
|
+
|
|
19
|
+
this._elManagerFactory = elManagerFactory;
|
|
20
|
+
if(!this._elManagerFactory) throw 'elManagerFactory must be defined.';
|
|
21
|
+
|
|
22
|
+
// Let the factory just use the trigger function on the view binder
|
|
23
|
+
this._elManagerFactory.trigger = this.trigger;
|
|
24
|
+
|
|
25
|
+
this._options = options || {};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Backbone.CollectionBinder.VERSION = '1.0.1';
|
|
29
|
+
|
|
30
|
+
_.extend(Backbone.CollectionBinder.prototype, Backbone.Events, {
|
|
31
|
+
bind: function(collection, parentEl){
|
|
32
|
+
this.unbind();
|
|
33
|
+
|
|
34
|
+
if(!collection) throw 'collection must be defined';
|
|
35
|
+
if(!parentEl) throw 'parentEl must be defined';
|
|
36
|
+
|
|
37
|
+
this._collection = collection;
|
|
38
|
+
this._elManagerFactory.setParentEl(parentEl);
|
|
39
|
+
|
|
40
|
+
this._onCollectionReset();
|
|
41
|
+
|
|
42
|
+
this._collection.on('add', this._onCollectionAdd, this);
|
|
43
|
+
this._collection.on('remove', this._onCollectionRemove, this);
|
|
44
|
+
this._collection.on('reset', this._onCollectionReset, this);
|
|
45
|
+
this._collection.on('sort', this._onCollectionSort, this);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
unbind: function(){
|
|
49
|
+
if(this._collection !== undefined){
|
|
50
|
+
this._collection.off('add', this._onCollectionAdd);
|
|
51
|
+
this._collection.off('remove', this._onCollectionRemove);
|
|
52
|
+
this._collection.off('reset', this._onCollectionReset);
|
|
53
|
+
this._collection.off('sort', this._onCollectionSort);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this._removeAllElManagers();
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
getManagerForEl: function(el){
|
|
60
|
+
var i, elManager, elManagers = _.values(this._elManagers);
|
|
61
|
+
|
|
62
|
+
for(i = 0; i < elManagers.length; i++){
|
|
63
|
+
elManager = elManagers[i];
|
|
64
|
+
|
|
65
|
+
if(elManager.isElContained(el)){
|
|
66
|
+
return elManager;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
getManagerForModel: function(model){
|
|
74
|
+
return this._elManagers[_.isObject(model)? model.cid : model];
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
_onCollectionAdd: function(model){
|
|
78
|
+
this._elManagers[model.cid] = this._elManagerFactory.makeElManager(model);
|
|
79
|
+
this._elManagers[model.cid].createEl();
|
|
80
|
+
|
|
81
|
+
if(this._options['autoSort']){
|
|
82
|
+
this.sortRootEls();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
_onCollectionRemove: function(model){
|
|
87
|
+
this._removeElManager(model);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
_onCollectionReset: function(){
|
|
91
|
+
this._removeAllElManagers();
|
|
92
|
+
|
|
93
|
+
this._collection.each(function(model){
|
|
94
|
+
this._onCollectionAdd(model);
|
|
95
|
+
}, this);
|
|
96
|
+
|
|
97
|
+
this.trigger('elsReset', this._collection);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
_onCollectionSort: function() {
|
|
101
|
+
if(this._options['autoSort']){
|
|
102
|
+
this.sortRootEls();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
_removeAllElManagers: function(){
|
|
107
|
+
_.each(this._elManagers, function(elManager){
|
|
108
|
+
elManager.removeEl();
|
|
109
|
+
delete this._elManagers[elManager._model.cid];
|
|
110
|
+
}, this);
|
|
111
|
+
|
|
112
|
+
delete this._elManagers;
|
|
113
|
+
this._elManagers = {};
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
_removeElManager: function(model){
|
|
117
|
+
if(this._elManagers[model.cid] !== undefined){
|
|
118
|
+
this._elManagers[model.cid].removeEl();
|
|
119
|
+
delete this._elManagers[model.cid];
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
sortRootEls: function(){
|
|
124
|
+
this._collection.each(function(model, modelIndex){
|
|
125
|
+
var modelElManager = this.getManagerForModel(model);
|
|
126
|
+
if(modelElManager){
|
|
127
|
+
var modelEl = modelElManager.getEl();
|
|
128
|
+
var currentRootEls = $(this._elManagerFactory.getParentEl()).children();
|
|
129
|
+
|
|
130
|
+
if(currentRootEls[modelIndex] !== modelEl[0]){
|
|
131
|
+
modelEl.detach();
|
|
132
|
+
modelEl.insertBefore(currentRootEls[modelIndex]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, this);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// The ElManagerFactory is used for els that are just html templates
|
|
140
|
+
// elHtml - how the model's html will be rendered. Must have a single root element (div,span).
|
|
141
|
+
// bindings (optional) - either a string which is the binding attribute (name, id, data-name, etc.) or a normal bindings hash
|
|
142
|
+
Backbone.CollectionBinder.ElManagerFactory = function(elHtml, bindings){
|
|
143
|
+
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
|
144
|
+
|
|
145
|
+
this._elHtml = elHtml;
|
|
146
|
+
this._bindings = bindings;
|
|
147
|
+
|
|
148
|
+
if(! _.isString(this._elHtml)) throw 'elHtml must be a valid html string';
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
_.extend(Backbone.CollectionBinder.ElManagerFactory.prototype, {
|
|
152
|
+
setParentEl: function(parentEl){
|
|
153
|
+
this._parentEl = parentEl;
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
getParentEl: function(){
|
|
157
|
+
return this._parentEl;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
makeElManager: function(model){
|
|
161
|
+
|
|
162
|
+
var elManager = {
|
|
163
|
+
_model: model,
|
|
164
|
+
|
|
165
|
+
createEl: function(){
|
|
166
|
+
|
|
167
|
+
this._el = $(this._elHtml);
|
|
168
|
+
$(this._parentEl).append(this._el);
|
|
169
|
+
|
|
170
|
+
if(this._bindings){
|
|
171
|
+
if(_.isString(this._bindings)){
|
|
172
|
+
this._modelBinder = new Backbone.ModelBinder();
|
|
173
|
+
this._modelBinder.bind(this._model, this._el, Backbone.ModelBinder.createDefaultBindings(this._el, this._bindings));
|
|
174
|
+
}
|
|
175
|
+
else if(_.isObject(this._bindings)){
|
|
176
|
+
this._modelBinder = new Backbone.ModelBinder();
|
|
177
|
+
this._modelBinder.bind(this._model, this._el, this._bindings);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
throw 'Unsupported bindings type, please use a boolean or a bindings hash';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.trigger('elCreated', this._model, this._el);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
removeEl: function(){
|
|
188
|
+
if(this._modelBinder !== undefined){
|
|
189
|
+
this._modelBinder.unbind();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this._el.remove();
|
|
193
|
+
this.trigger('elRemoved', this._model, this._el);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
isElContained: function(findEl){
|
|
197
|
+
return this._el === findEl || $(this._el).has(findEl).length > 0;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
getModel: function(){
|
|
201
|
+
return this._model;
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
getEl: function(){
|
|
205
|
+
return this._el;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
_.extend(elManager, this);
|
|
210
|
+
return elManager;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
// The ViewManagerFactory is used for els that are created and owned by backbone views.
|
|
216
|
+
// There is no bindings option because the view made by the viewCreator should take care of any binding
|
|
217
|
+
// viewCreator - a callback that will create backbone view instances for a model passed to the callback
|
|
218
|
+
Backbone.CollectionBinder.ViewManagerFactory = function(viewCreator){
|
|
219
|
+
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
|
220
|
+
this._viewCreator = viewCreator;
|
|
221
|
+
|
|
222
|
+
if(!_.isFunction(this._viewCreator)) throw 'viewCreator must be a valid function that accepts a model and returns a backbone view';
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
_.extend(Backbone.CollectionBinder.ViewManagerFactory.prototype, {
|
|
226
|
+
setParentEl: function(parentEl){
|
|
227
|
+
this._parentEl = parentEl;
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
getParentEl: function(){
|
|
231
|
+
return this._parentEl;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
makeElManager: function(model){
|
|
235
|
+
var elManager = {
|
|
236
|
+
|
|
237
|
+
_model: model,
|
|
238
|
+
|
|
239
|
+
createEl: function(){
|
|
240
|
+
this._view = this._viewCreator(model);
|
|
241
|
+
$(this._parentEl).append(this._view.render(this._model).el);
|
|
242
|
+
|
|
243
|
+
this.trigger('elCreated', this._model, this._view);
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
removeEl: function(){
|
|
247
|
+
if(this._view.close !== undefined){
|
|
248
|
+
this._view.close();
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this._view.$el.remove();
|
|
252
|
+
console.log('warning, you should implement a close() function for your view, you might end up with zombies');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.trigger('elRemoved', this._model, this._view);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
isElContained: function(findEl){
|
|
259
|
+
return this._view.el === findEl || this._view.$el.has(findEl).length > 0;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
getModel: function(){
|
|
263
|
+
return this._model;
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
getView: function(){
|
|
267
|
+
return this._view;
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
getEl: function(){
|
|
271
|
+
return this._view.$el;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
_.extend(elManager, this);
|
|
276
|
+
|
|
277
|
+
return elManager;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
}).call(this);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Backbone.CollectionBinder v1.0.2
|
|
2
|
+
// (c) 2013 Bart Wood
|
|
3
|
+
// Distributed Under MIT License
|
|
4
|
+
|
|
5
|
+
(function(){if(!Backbone){throw"Please include Backbone.js before Backbone.ModelBinder.js"}if(!Backbone.ModelBinder){throw"Please include Backbone.ModelBinder.js before Backbone.CollectionBinder.js"}Backbone.CollectionBinder=function(e,t){_.bindAll.apply(_,[this].concat(_.functions(this)));this._elManagers={};this._elManagerFactory=e;if(!this._elManagerFactory)throw"elManagerFactory must be defined.";this._elManagerFactory.trigger=this.trigger;this._options=t||{}};Backbone.CollectionBinder.VERSION="1.0.1";_.extend(Backbone.CollectionBinder.prototype,Backbone.Events,{bind:function(e,t){this.unbind();if(!e)throw"collection must be defined";if(!t)throw"parentEl must be defined";this._collection=e;this._elManagerFactory.setParentEl(t);this._onCollectionReset();this._collection.on("add",this._onCollectionAdd,this);this._collection.on("remove",this._onCollectionRemove,this);this._collection.on("reset",this._onCollectionReset,this);this._collection.on("sort",this._onCollectionSort,this)},unbind:function(){if(this._collection!==undefined){this._collection.off("add",this._onCollectionAdd);this._collection.off("remove",this._onCollectionRemove);this._collection.off("reset",this._onCollectionReset);this._collection.off("sort",this._onCollectionSort)}this._removeAllElManagers()},getManagerForEl:function(e){var t,n,r=_.values(this._elManagers);for(t=0;t<r.length;t++){n=r[t];if(n.isElContained(e)){return n}}return undefined},getManagerForModel:function(e){return this._elManagers[_.isObject(e)?e.cid:e]},_onCollectionAdd:function(e){this._elManagers[e.cid]=this._elManagerFactory.makeElManager(e);this._elManagers[e.cid].createEl();if(this._options["autoSort"]){this.sortRootEls()}},_onCollectionRemove:function(e){this._removeElManager(e)},_onCollectionReset:function(){this._removeAllElManagers();this._collection.each(function(e){this._onCollectionAdd(e)},this);this.trigger("elsReset",this._collection)},_onCollectionSort:function(){if(this._options["autoSort"]){this.sortRootEls()}},_removeAllElManagers:function(){_.each(this._elManagers,function(e){e.removeEl();delete this._elManagers[e._model.cid]},this);delete this._elManagers;this._elManagers={}},_removeElManager:function(e){if(this._elManagers[e.cid]!==undefined){this._elManagers[e.cid].removeEl();delete this._elManagers[e.cid]}},sortRootEls:function(){this._collection.each(function(e,t){var n=this.getManagerForModel(e);if(n){var r=n.getEl();var i=$(this._elManagerFactory.getParentEl()).children();if(i[t]!==r[0]){r.detach();r.insertBefore(i[t])}}},this)}});Backbone.CollectionBinder.ElManagerFactory=function(e,t){_.bindAll.apply(_,[this].concat(_.functions(this)));this._elHtml=e;this._bindings=t;if(!_.isString(this._elHtml))throw"elHtml must be a valid html string"};_.extend(Backbone.CollectionBinder.ElManagerFactory.prototype,{setParentEl:function(e){this._parentEl=e},getParentEl:function(){return this._parentEl},makeElManager:function(e){var t={_model:e,createEl:function(){this._el=$(this._elHtml);$(this._parentEl).append(this._el);if(this._bindings){if(_.isString(this._bindings)){this._modelBinder=new Backbone.ModelBinder;this._modelBinder.bind(this._model,this._el,Backbone.ModelBinder.createDefaultBindings(this._el,this._bindings))}else if(_.isObject(this._bindings)){this._modelBinder=new Backbone.ModelBinder;this._modelBinder.bind(this._model,this._el,this._bindings)}else{throw"Unsupported bindings type, please use a boolean or a bindings hash"}}this.trigger("elCreated",this._model,this._el)},removeEl:function(){if(this._modelBinder!==undefined){this._modelBinder.unbind()}this._el.remove();this.trigger("elRemoved",this._model,this._el)},isElContained:function(e){return this._el===e||$(this._el).has(e).length>0},getModel:function(){return this._model},getEl:function(){return this._el}};_.extend(t,this);return t}});Backbone.CollectionBinder.ViewManagerFactory=function(e){_.bindAll.apply(_,[this].concat(_.functions(this)));this._viewCreator=e;if(!_.isFunction(this._viewCreator))throw"viewCreator must be a valid function that accepts a model and returns a backbone view"};_.extend(Backbone.CollectionBinder.ViewManagerFactory.prototype,{setParentEl:function(e){this._parentEl=e},getParentEl:function(){return this._parentEl},makeElManager:function(e){var t={_model:e,createEl:function(){this._view=this._viewCreator(e);$(this._parentEl).append(this._view.render(this._model).el);this.trigger("elCreated",this._model,this._view)},removeEl:function(){if(this._view.close!==undefined){this._view.close()}else{this._view.$el.remove();console.log("warning, you should implement a close() function for your view, you might end up with zombies")}this.trigger("elRemoved",this._model,this._view)},isElContained:function(e){return this._view.el===e||this._view.$el.has(e).length>0},getModel:function(){return this._model},getView:function(){return this._view},getEl:function(){return this._view.$el}};_.extend(t,this);return t}})}).call(this)
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
// Backbone.ModelBinder v1.0.2
|
|
2
|
+
// (c) 2013 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(){
|
|
20
|
+
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Static setter for class level options
|
|
24
|
+
Backbone.ModelBinder.SetOptions = function(options){
|
|
25
|
+
Backbone.ModelBinder.options = options;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Current version of the library.
|
|
29
|
+
Backbone.ModelBinder.VERSION = '1.0.2';
|
|
30
|
+
Backbone.ModelBinder.Constants = {};
|
|
31
|
+
Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
|
|
32
|
+
Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
|
|
33
|
+
|
|
34
|
+
_.extend(Backbone.ModelBinder.prototype, {
|
|
35
|
+
|
|
36
|
+
bind:function (model, rootEl, attributeBindings, options) {
|
|
37
|
+
this.unbind();
|
|
38
|
+
|
|
39
|
+
this._model = model;
|
|
40
|
+
this._rootEl = rootEl;
|
|
41
|
+
this._setOptions(options);
|
|
42
|
+
|
|
43
|
+
if (!this._model) this._throwException('model must be specified');
|
|
44
|
+
if (!this._rootEl) this._throwException('rootEl must be specified');
|
|
45
|
+
|
|
46
|
+
if(attributeBindings){
|
|
47
|
+
// Create a deep clone of the attribute bindings
|
|
48
|
+
this._attributeBindings = $.extend(true, {}, attributeBindings);
|
|
49
|
+
|
|
50
|
+
this._initializeAttributeBindings();
|
|
51
|
+
this._initializeElBindings();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this._initializeDefaultBindings();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this._bindModelToView();
|
|
58
|
+
this._bindViewToModel();
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) {
|
|
62
|
+
this._triggers = triggers;
|
|
63
|
+
this.bind(model, rootEl, attributeBindings, modelSetOptions)
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
unbind:function () {
|
|
67
|
+
this._unbindModelToView();
|
|
68
|
+
this._unbindViewToModel();
|
|
69
|
+
|
|
70
|
+
if(this._attributeBindings){
|
|
71
|
+
delete this._attributeBindings;
|
|
72
|
+
this._attributeBindings = undefined;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
_setOptions: function(options){
|
|
77
|
+
this._options = _.extend({
|
|
78
|
+
boundAttribute: 'name'
|
|
79
|
+
}, Backbone.ModelBinder.options, options);
|
|
80
|
+
|
|
81
|
+
// initialize default options
|
|
82
|
+
if(!this._options['modelSetOptions']){
|
|
83
|
+
this._options['modelSetOptions'] = {};
|
|
84
|
+
}
|
|
85
|
+
this._options['modelSetOptions'].changeSource = 'ModelBinder';
|
|
86
|
+
|
|
87
|
+
if(!this._options['changeTriggers']){
|
|
88
|
+
this._options['changeTriggers'] = {'': 'change', '[contenteditable]': 'blur'};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if(!this._options['initialCopyDirection']){
|
|
92
|
+
this._options['initialCopyDirection'] = Backbone.ModelBinder.Constants.ModelToView;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Converts the input bindings, which might just be empty or strings, to binding objects
|
|
97
|
+
_initializeAttributeBindings:function () {
|
|
98
|
+
var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding;
|
|
99
|
+
|
|
100
|
+
for (attributeBindingKey in this._attributeBindings) {
|
|
101
|
+
inputBinding = this._attributeBindings[attributeBindingKey];
|
|
102
|
+
|
|
103
|
+
if (_.isString(inputBinding)) {
|
|
104
|
+
attributeBinding = {elementBindings: [{selector: inputBinding}]};
|
|
105
|
+
}
|
|
106
|
+
else if (_.isArray(inputBinding)) {
|
|
107
|
+
attributeBinding = {elementBindings: inputBinding};
|
|
108
|
+
}
|
|
109
|
+
else if(_.isObject(inputBinding)){
|
|
110
|
+
attributeBinding = {elementBindings: [inputBinding]};
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this._throwException('Unsupported type passed to Model Binder ' + attributeBinding);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add a linkage from the element binding back to the attribute binding
|
|
117
|
+
for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){
|
|
118
|
+
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
|
119
|
+
elementBinding.attributeBinding = attributeBinding;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
attributeBinding.attributeName = attributeBindingKey;
|
|
123
|
+
this._attributeBindings[attributeBindingKey] = attributeBinding;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// If the bindings are not specified, the default binding is performed on the specified attribute, name by default
|
|
128
|
+
_initializeDefaultBindings: function(){
|
|
129
|
+
var elCount, elsWithAttribute, matchedEl, name, attributeBinding;
|
|
130
|
+
|
|
131
|
+
this._attributeBindings = {};
|
|
132
|
+
elsWithAttribute = $('[' + this._options['boundAttribute'] + ']', this._rootEl);
|
|
133
|
+
|
|
134
|
+
for(elCount = 0; elCount < elsWithAttribute.length; elCount++){
|
|
135
|
+
matchedEl = elsWithAttribute[elCount];
|
|
136
|
+
name = $(matchedEl).attr(this._options['boundAttribute']);
|
|
137
|
+
|
|
138
|
+
// For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings
|
|
139
|
+
if(!this._attributeBindings[name]){
|
|
140
|
+
attributeBinding = {attributeName: name};
|
|
141
|
+
attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [matchedEl]}];
|
|
142
|
+
this._attributeBindings[name] = attributeBinding;
|
|
143
|
+
}
|
|
144
|
+
else{
|
|
145
|
+
this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [matchedEl]});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
_initializeElBindings:function () {
|
|
151
|
+
var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el;
|
|
152
|
+
for (bindingKey in this._attributeBindings) {
|
|
153
|
+
attributeBinding = this._attributeBindings[bindingKey];
|
|
154
|
+
|
|
155
|
+
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
|
156
|
+
elementBinding = attributeBinding.elementBindings[bindingCount];
|
|
157
|
+
if (elementBinding.selector === '') {
|
|
158
|
+
foundEls = $(this._rootEl);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
foundEls = $(elementBinding.selector, this._rootEl);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (foundEls.length === 0) {
|
|
165
|
+
this._throwException('Bad binding found. No elements returned for binding selector ' + elementBinding.selector);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
elementBinding.boundEls = [];
|
|
169
|
+
for (elCount = 0; elCount < foundEls.length; elCount++) {
|
|
170
|
+
el = foundEls[elCount];
|
|
171
|
+
elementBinding.boundEls.push(el);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
_bindModelToView: function () {
|
|
179
|
+
this._model.on('change', this._onModelChange, this);
|
|
180
|
+
|
|
181
|
+
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ModelToView){
|
|
182
|
+
this.copyModelAttributesToView();
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// attributesToCopy is an optional parameter - if empty, all attributes
|
|
187
|
+
// that are bound will be copied. Otherwise, only attributeBindings specified
|
|
188
|
+
// in the attributesToCopy are copied.
|
|
189
|
+
copyModelAttributesToView: function(attributesToCopy){
|
|
190
|
+
var attributeName, attributeBinding;
|
|
191
|
+
|
|
192
|
+
for (attributeName in this._attributeBindings) {
|
|
193
|
+
if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){
|
|
194
|
+
attributeBinding = this._attributeBindings[attributeName];
|
|
195
|
+
this._copyModelToView(attributeBinding);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
copyViewValuesToModel: function(){
|
|
201
|
+
var bindingKey, attributeBinding, bindingCount, elementBinding, elCount, el;
|
|
202
|
+
for (bindingKey in this._attributeBindings) {
|
|
203
|
+
attributeBinding = this._attributeBindings[bindingKey];
|
|
204
|
+
|
|
205
|
+
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
|
206
|
+
elementBinding = attributeBinding.elementBindings[bindingCount];
|
|
207
|
+
|
|
208
|
+
if(this._isBindingUserEditable(elementBinding)){
|
|
209
|
+
if(this._isBindingRadioGroup(elementBinding)){
|
|
210
|
+
el = this._getRadioButtonGroupCheckedEl(elementBinding);
|
|
211
|
+
if(el){
|
|
212
|
+
this._copyViewToModel(elementBinding, el);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
for(elCount = 0; elCount < elementBinding.boundEls.length; elCount++){
|
|
217
|
+
el = $(elementBinding.boundEls[elCount]);
|
|
218
|
+
if(this._isElUserEditable(el)){
|
|
219
|
+
this._copyViewToModel(elementBinding, el);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
_unbindModelToView: function(){
|
|
229
|
+
if(this._model){
|
|
230
|
+
this._model.off('change', this._onModelChange);
|
|
231
|
+
this._model = undefined;
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
_bindViewToModel: function () {
|
|
236
|
+
_.each(this._options['changeTriggers'], function (event, selector) {
|
|
237
|
+
$(this._rootEl).delegate(selector, event, this._onElChanged);
|
|
238
|
+
}, this);
|
|
239
|
+
|
|
240
|
+
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ViewToModel){
|
|
241
|
+
this.copyViewValuesToModel();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
_unbindViewToModel: function () {
|
|
246
|
+
if(this._options && this._options['changeTriggers']){
|
|
247
|
+
_.each(this._options['changeTriggers'], function (event, selector) {
|
|
248
|
+
$(this._rootEl).undelegate(selector, event, this._onElChanged);
|
|
249
|
+
}, this);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
_onElChanged:function (event) {
|
|
254
|
+
var el, elBindings, elBindingCount, elBinding;
|
|
255
|
+
|
|
256
|
+
el = $(event.target)[0];
|
|
257
|
+
elBindings = this._getElBindings(el);
|
|
258
|
+
|
|
259
|
+
for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){
|
|
260
|
+
elBinding = elBindings[elBindingCount];
|
|
261
|
+
if (this._isBindingUserEditable(elBinding)) {
|
|
262
|
+
this._copyViewToModel(elBinding, el);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
_isBindingUserEditable: function(elBinding){
|
|
268
|
+
return elBinding.elAttribute === undefined ||
|
|
269
|
+
elBinding.elAttribute === 'text' ||
|
|
270
|
+
elBinding.elAttribute === 'html';
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
_isElUserEditable: function(el){
|
|
274
|
+
var isContentEditable = el.attr('contenteditable');
|
|
275
|
+
return isContentEditable || el.is('input') || el.is('select') || el.is('textarea');
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
_isBindingRadioGroup: function(elBinding){
|
|
279
|
+
var elCount, el;
|
|
280
|
+
var isAllRadioButtons = elBinding.boundEls.length > 0;
|
|
281
|
+
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
|
282
|
+
el = $(elBinding.boundEls[elCount]);
|
|
283
|
+
if(el.attr('type') !== 'radio'){
|
|
284
|
+
isAllRadioButtons = false;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return isAllRadioButtons;
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
_getRadioButtonGroupCheckedEl: function(elBinding){
|
|
293
|
+
var elCount, el;
|
|
294
|
+
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
|
295
|
+
el = $(elBinding.boundEls[elCount]);
|
|
296
|
+
if(el.attr('type') === 'radio' && el.attr('checked')){
|
|
297
|
+
return el;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return undefined;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
_getElBindings:function (findEl) {
|
|
305
|
+
var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl;
|
|
306
|
+
var elBindings = [];
|
|
307
|
+
|
|
308
|
+
for (attributeName in this._attributeBindings) {
|
|
309
|
+
attributeBinding = this._attributeBindings[attributeName];
|
|
310
|
+
|
|
311
|
+
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
|
312
|
+
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
|
313
|
+
|
|
314
|
+
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
|
315
|
+
boundEl = elementBinding.boundEls[boundElCount];
|
|
316
|
+
|
|
317
|
+
if (boundEl === findEl) {
|
|
318
|
+
elBindings.push(elementBinding);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return elBindings;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
_onModelChange:function () {
|
|
328
|
+
var changedAttribute, attributeBinding;
|
|
329
|
+
|
|
330
|
+
for (changedAttribute in this._model.changedAttributes()) {
|
|
331
|
+
attributeBinding = this._attributeBindings[changedAttribute];
|
|
332
|
+
|
|
333
|
+
if (attributeBinding) {
|
|
334
|
+
this._copyModelToView(attributeBinding);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
_copyModelToView:function (attributeBinding) {
|
|
340
|
+
var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue;
|
|
341
|
+
|
|
342
|
+
value = this._model.get(attributeBinding.attributeName);
|
|
343
|
+
|
|
344
|
+
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
|
345
|
+
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
|
346
|
+
|
|
347
|
+
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
|
348
|
+
boundEl = elementBinding.boundEls[boundElCount];
|
|
349
|
+
|
|
350
|
+
if(!boundEl._isSetting){
|
|
351
|
+
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
|
352
|
+
this._setEl($(boundEl), elementBinding, convertedValue);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
_setEl: function (el, elementBinding, convertedValue) {
|
|
359
|
+
if (elementBinding.elAttribute) {
|
|
360
|
+
this._setElAttribute(el, elementBinding, convertedValue);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
this._setElValue(el, convertedValue);
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
_setElAttribute:function (el, elementBinding, convertedValue) {
|
|
368
|
+
switch (elementBinding.elAttribute) {
|
|
369
|
+
case 'html':
|
|
370
|
+
el.html(convertedValue);
|
|
371
|
+
break;
|
|
372
|
+
case 'text':
|
|
373
|
+
el.text(convertedValue);
|
|
374
|
+
break;
|
|
375
|
+
case 'enabled':
|
|
376
|
+
el.prop('disabled', !convertedValue);
|
|
377
|
+
break;
|
|
378
|
+
case 'displayed':
|
|
379
|
+
el[convertedValue ? 'show' : 'hide']();
|
|
380
|
+
break;
|
|
381
|
+
case 'hidden':
|
|
382
|
+
el[convertedValue ? 'hide' : 'show']();
|
|
383
|
+
break;
|
|
384
|
+
case 'css':
|
|
385
|
+
el.css(elementBinding.cssAttribute, convertedValue);
|
|
386
|
+
break;
|
|
387
|
+
case 'class':
|
|
388
|
+
var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName);
|
|
389
|
+
var currentValue = this._model.get(elementBinding.attributeBinding.attributeName);
|
|
390
|
+
// is current value is now defined then remove the class the may have been set for the undefined value
|
|
391
|
+
if(!_.isUndefined(previousValue) || !_.isUndefined(currentValue)){
|
|
392
|
+
previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue);
|
|
393
|
+
el.removeClass(previousValue);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if(convertedValue){
|
|
397
|
+
el.addClass(convertedValue);
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
default:
|
|
401
|
+
el.attr(elementBinding.elAttribute, convertedValue);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
_setElValue:function (el, convertedValue) {
|
|
406
|
+
if(el.attr('type')){
|
|
407
|
+
switch (el.attr('type')) {
|
|
408
|
+
case 'radio':
|
|
409
|
+
if (el.val() === convertedValue) {
|
|
410
|
+
// must defer the change trigger or the change will actually fire with the old value
|
|
411
|
+
el.prop('checked') || _.defer(function() { el.trigger('change'); });
|
|
412
|
+
el.prop('checked', true);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// must defer the change trigger or the change will actually fire with the old value
|
|
416
|
+
el.prop('checked', false);
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
case 'checkbox':
|
|
420
|
+
// must defer the change trigger or the change will actually fire with the old value
|
|
421
|
+
el.prop('checked') === !!convertedValue || _.defer(function() { el.trigger('change') });
|
|
422
|
+
el.prop('checked', !!convertedValue);
|
|
423
|
+
break;
|
|
424
|
+
case 'file':
|
|
425
|
+
break;
|
|
426
|
+
default:
|
|
427
|
+
el.val(convertedValue);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else if(el.is('input') || el.is('select') || el.is('textarea')){
|
|
431
|
+
el.val(convertedValue || (convertedValue === 0 ? '0' : ''));
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
el.text(convertedValue || (convertedValue === 0 ? '0' : ''));
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
_copyViewToModel: function (elementBinding, el) {
|
|
439
|
+
var result, value, convertedValue;
|
|
440
|
+
|
|
441
|
+
if (!el._isSetting) {
|
|
442
|
+
|
|
443
|
+
el._isSetting = true;
|
|
444
|
+
result = this._setModel(elementBinding, $(el));
|
|
445
|
+
el._isSetting = false;
|
|
446
|
+
|
|
447
|
+
if(result && elementBinding.converter){
|
|
448
|
+
value = this._model.get(elementBinding.attributeBinding.attributeName);
|
|
449
|
+
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
|
450
|
+
this._setEl($(el), elementBinding, convertedValue);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
_getElValue: function(elementBinding, el){
|
|
456
|
+
switch (el.attr('type')) {
|
|
457
|
+
case 'checkbox':
|
|
458
|
+
return el.prop('checked') ? true : false;
|
|
459
|
+
default:
|
|
460
|
+
if(el.attr('contenteditable') !== undefined){
|
|
461
|
+
return el.html();
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
return el.val();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
_setModel: function (elementBinding, el) {
|
|
470
|
+
var data = {};
|
|
471
|
+
var elVal = this._getElValue(elementBinding, el);
|
|
472
|
+
elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal);
|
|
473
|
+
data[elementBinding.attributeBinding.attributeName] = elVal;
|
|
474
|
+
return this._model.set(data, this._options['modelSetOptions']);
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
_getConvertedValue: function (direction, elementBinding, value) {
|
|
478
|
+
if (elementBinding.converter) {
|
|
479
|
+
value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return value;
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
_throwException: function(message){
|
|
486
|
+
if(this._options.suppressThrows){
|
|
487
|
+
if(console && console.error){
|
|
488
|
+
console.error(message);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
throw message;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
Backbone.ModelBinder.CollectionConverter = function(collection){
|
|
498
|
+
this._collection = collection;
|
|
499
|
+
|
|
500
|
+
if(!this._collection){
|
|
501
|
+
throw 'Collection must be defined';
|
|
502
|
+
}
|
|
503
|
+
_.bindAll(this, 'convert');
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
_.extend(Backbone.ModelBinder.CollectionConverter.prototype, {
|
|
507
|
+
convert: function(direction, value){
|
|
508
|
+
if (direction === Backbone.ModelBinder.Constants.ModelToView) {
|
|
509
|
+
return value ? value.id : undefined;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
return this._collection.get(value);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// A static helper function to create a default set of bindings that you can customize before calling the bind() function
|
|
518
|
+
// rootEl - where to find all of the bound elements
|
|
519
|
+
// attributeType - probably 'name' or 'id' in most cases
|
|
520
|
+
// converter(optional) - the default converter you want applied to all your bindings
|
|
521
|
+
// elAttribute(optional) - the default elAttribute you want applied to all your bindings
|
|
522
|
+
Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){
|
|
523
|
+
var foundEls, elCount, foundEl, attributeName;
|
|
524
|
+
var bindings = {};
|
|
525
|
+
|
|
526
|
+
foundEls = $('[' + attributeType + ']', rootEl);
|
|
527
|
+
|
|
528
|
+
for(elCount = 0; elCount < foundEls.length; elCount++){
|
|
529
|
+
foundEl = foundEls[elCount];
|
|
530
|
+
attributeName = $(foundEl).attr(attributeType);
|
|
531
|
+
|
|
532
|
+
if(!bindings[attributeName]){
|
|
533
|
+
var attributeBinding = {selector: '[' + attributeType + '="' + attributeName + '"]'};
|
|
534
|
+
bindings[attributeName] = attributeBinding;
|
|
535
|
+
|
|
536
|
+
if(converter){
|
|
537
|
+
bindings[attributeName].converter = converter;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if(elAttribute){
|
|
541
|
+
bindings[attributeName].elAttribute = elAttribute;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return bindings;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// Helps you to combine 2 sets of bindings
|
|
550
|
+
Backbone.ModelBinder.combineBindings = function(destination, source){
|
|
551
|
+
_.each(source, function(value, key){
|
|
552
|
+
var elementBinding = {selector: value.selector};
|
|
553
|
+
|
|
554
|
+
if(value.converter){
|
|
555
|
+
elementBinding.converter = value.converter;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if(value.elAttribute){
|
|
559
|
+
elementBinding.elAttribute = value.elAttribute;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if(!destination[key]){
|
|
563
|
+
destination[key] = elementBinding;
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
destination[key] = [destination[key], elementBinding];
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
return destination;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
return Backbone.ModelBinder;
|
|
575
|
+
|
|
576
|
+
}));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Backbone.ModelBinder v1.0.2
|
|
2
|
+
// (c) 2013 Bart Wood
|
|
3
|
+
// Distributed Under MIT License
|
|
4
|
+
(function(e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","backbone"],e)}else{e(_,$,Backbone)}})(function(e,t,n){if(!n){throw"Please include Backbone.js before Backbone.ModelBinder.js"}n.ModelBinder=function(){e.bindAll.apply(e,[this].concat(e.functions(this)))};n.ModelBinder.SetOptions=function(e){n.ModelBinder.options=e};n.ModelBinder.VERSION="1.0.2";n.ModelBinder.Constants={};n.ModelBinder.Constants.ModelToView="ModelToView";n.ModelBinder.Constants.ViewToModel="ViewToModel";e.extend(n.ModelBinder.prototype,{bind:function(e,n,r,i){this.unbind();this._model=e;this._rootEl=n;this._setOptions(i);if(!this._model)this._throwException("model must be specified");if(!this._rootEl)this._throwException("rootEl must be specified");if(r){this._attributeBindings=t.extend(true,{},r);this._initializeAttributeBindings();this._initializeElBindings()}else{this._initializeDefaultBindings()}this._bindModelToView();this._bindViewToModel()},bindCustomTriggers:function(e,t,n,r,i){this._triggers=n;this.bind(e,t,r,i)},unbind:function(){this._unbindModelToView();this._unbindViewToModel();if(this._attributeBindings){delete this._attributeBindings;this._attributeBindings=undefined}},_setOptions:function(t){this._options=e.extend({boundAttribute:"name"},n.ModelBinder.options,t);if(!this._options["modelSetOptions"]){this._options["modelSetOptions"]={}}this._options["modelSetOptions"].changeSource="ModelBinder";if(!this._options["changeTriggers"]){this._options["changeTriggers"]={"":"change","[contenteditable]":"blur"}}if(!this._options["initialCopyDirection"]){this._options["initialCopyDirection"]=n.ModelBinder.Constants.ModelToView}},_initializeAttributeBindings:function(){var t,n,r,i,s;for(t in this._attributeBindings){n=this._attributeBindings[t];if(e.isString(n)){r={elementBindings:[{selector:n}]}}else if(e.isArray(n)){r={elementBindings:n}}else if(e.isObject(n)){r={elementBindings:[n]}}else{this._throwException("Unsupported type passed to Model Binder "+r)}for(i=0;i<r.elementBindings.length;i++){s=r.elementBindings[i];s.attributeBinding=r}r.attributeName=t;this._attributeBindings[t]=r}},_initializeDefaultBindings:function(){var e,n,r,i,s;this._attributeBindings={};n=t("["+this._options["boundAttribute"]+"]",this._rootEl);for(e=0;e<n.length;e++){r=n[e];i=t(r).attr(this._options["boundAttribute"]);if(!this._attributeBindings[i]){s={attributeName:i};s.elementBindings=[{attributeBinding:s,boundEls:[r]}];this._attributeBindings[i]=s}else{this._attributeBindings[i].elementBindings.push({attributeBinding:this._attributeBindings[i],boundEls:[r]})}}},_initializeElBindings:function(){var e,n,r,i,s,o,u;for(e in this._attributeBindings){n=this._attributeBindings[e];for(r=0;r<n.elementBindings.length;r++){i=n.elementBindings[r];if(i.selector===""){s=t(this._rootEl)}else{s=t(i.selector,this._rootEl)}if(s.length===0){this._throwException("Bad binding found. No elements returned for binding selector "+i.selector)}else{i.boundEls=[];for(o=0;o<s.length;o++){u=s[o];i.boundEls.push(u)}}}}},_bindModelToView:function(){this._model.on("change",this._onModelChange,this);if(this._options["initialCopyDirection"]===n.ModelBinder.Constants.ModelToView){this.copyModelAttributesToView()}},copyModelAttributesToView:function(t){var n,r;for(n in this._attributeBindings){if(t===undefined||e.indexOf(t,n)!==-1){r=this._attributeBindings[n];this._copyModelToView(r)}}},copyViewValuesToModel:function(){var e,n,r,i,s,o;for(e in this._attributeBindings){n=this._attributeBindings[e];for(r=0;r<n.elementBindings.length;r++){i=n.elementBindings[r];if(this._isBindingUserEditable(i)){if(this._isBindingRadioGroup(i)){o=this._getRadioButtonGroupCheckedEl(i);if(o){this._copyViewToModel(i,o)}}else{for(s=0;s<i.boundEls.length;s++){o=t(i.boundEls[s]);if(this._isElUserEditable(o)){this._copyViewToModel(i,o)}}}}}}},_unbindModelToView:function(){if(this._model){this._model.off("change",this._onModelChange);this._model=undefined}},_bindViewToModel:function(){e.each(this._options["changeTriggers"],function(e,n){t(this._rootEl).delegate(n,e,this._onElChanged)},this);if(this._options["initialCopyDirection"]===n.ModelBinder.Constants.ViewToModel){this.copyViewValuesToModel()}},_unbindViewToModel:function(){if(this._options&&this._options["changeTriggers"]){e.each(this._options["changeTriggers"],function(e,n){t(this._rootEl).undelegate(n,e,this._onElChanged)},this)}},_onElChanged:function(e){var n,r,i,s;n=t(e.target)[0];r=this._getElBindings(n);for(i=0;i<r.length;i++){s=r[i];if(this._isBindingUserEditable(s)){this._copyViewToModel(s,n)}}},_isBindingUserEditable:function(e){return e.elAttribute===undefined||e.elAttribute==="text"||e.elAttribute==="html"},_isElUserEditable:function(e){var t=e.attr("contenteditable");return t||e.is("input")||e.is("select")||e.is("textarea")},_isBindingRadioGroup:function(e){var n,r;var i=e.boundEls.length>0;for(n=0;n<e.boundEls.length;n++){r=t(e.boundEls[n]);if(r.attr("type")!=="radio"){i=false;break}}return i},_getRadioButtonGroupCheckedEl:function(e){var n,r;for(n=0;n<e.boundEls.length;n++){r=t(e.boundEls[n]);if(r.attr("type")==="radio"&&r.attr("checked")){return r}}return undefined},_getElBindings:function(e){var t,n,r,i,s,o;var u=[];for(t in this._attributeBindings){n=this._attributeBindings[t];for(r=0;r<n.elementBindings.length;r++){i=n.elementBindings[r];for(s=0;s<i.boundEls.length;s++){o=i.boundEls[s];if(o===e){u.push(i)}}}}return u},_onModelChange:function(){var e,t;for(e in this._model.changedAttributes()){t=this._attributeBindings[e];if(t){this._copyModelToView(t)}}},_copyModelToView:function(e){var r,i,s,o,u,a;u=this._model.get(e.attributeName);for(r=0;r<e.elementBindings.length;r++){i=e.elementBindings[r];for(s=0;s<i.boundEls.length;s++){o=i.boundEls[s];if(!o._isSetting){a=this._getConvertedValue(n.ModelBinder.Constants.ModelToView,i,u);this._setEl(t(o),i,a)}}}},_setEl:function(e,t,n){if(t.elAttribute){this._setElAttribute(e,t,n)}else{this._setElValue(e,n)}},_setElAttribute:function(t,r,i){switch(r.elAttribute){case"html":t.html(i);break;case"text":t.text(i);break;case"enabled":t.prop("disabled",!i);break;case"displayed":t[i?"show":"hide"]();break;case"hidden":t[i?"hide":"show"]();break;case"css":t.css(r.cssAttribute,i);break;case"class":var s=this._model.previous(r.attributeBinding.attributeName);var o=this._model.get(r.attributeBinding.attributeName);if(!e.isUndefined(s)||!e.isUndefined(o)){s=this._getConvertedValue(n.ModelBinder.Constants.ModelToView,r,s);t.removeClass(s)}if(i){t.addClass(i)}break;default:t.attr(r.elAttribute,i)}},_setElValue:function(t,n){if(t.attr("type")){switch(t.attr("type")){case"radio":if(t.val()===n){t.prop("checked")||e.defer(function(){t.trigger("change")});t.prop("checked",true)}else{t.prop("checked",false)}break;case"checkbox":t.prop("checked")===!!n||e.defer(function(){t.trigger("change")});t.prop("checked",!!n);break;case"file":break;default:t.val(n)}}else if(t.is("input")||t.is("select")||t.is("textarea")){t.val(n||(n===0?"0":""))}else{t.text(n||(n===0?"0":""))}},_copyViewToModel:function(e,r){var i,s,o;if(!r._isSetting){r._isSetting=true;i=this._setModel(e,t(r));r._isSetting=false;if(i&&e.converter){s=this._model.get(e.attributeBinding.attributeName);o=this._getConvertedValue(n.ModelBinder.Constants.ModelToView,e,s);this._setEl(t(r),e,o)}}},_getElValue:function(e,t){switch(t.attr("type")){case"checkbox":return t.prop("checked")?true:false;default:if(t.attr("contenteditable")!==undefined){return t.html()}else{return t.val()}}},_setModel:function(e,t){var r={};var i=this._getElValue(e,t);i=this._getConvertedValue(n.ModelBinder.Constants.ViewToModel,e,i);r[e.attributeBinding.attributeName]=i;return this._model.set(r,this._options["modelSetOptions"])},_getConvertedValue:function(e,t,n){if(t.converter){n=t.converter(e,n,t.attributeBinding.attributeName,this._model,t.boundEls)}return n},_throwException:function(e){if(this._options.suppressThrows){if(console&&console.error){console.error(e)}}else{throw e}}});n.ModelBinder.CollectionConverter=function(t){this._collection=t;if(!this._collection){throw"Collection must be defined"}e.bindAll(this,"convert")};e.extend(n.ModelBinder.CollectionConverter.prototype,{convert:function(e,t){if(e===n.ModelBinder.Constants.ModelToView){return t?t.id:undefined}else{return this._collection.get(t)}}});n.ModelBinder.createDefaultBindings=function(e,n,r,i){var s,o,u,a;var f={};s=t("["+n+"]",e);for(o=0;o<s.length;o++){u=s[o];a=t(u).attr(n);if(!f[a]){var l={selector:"["+n+'="'+a+'"]'};f[a]=l;if(r){f[a].converter=r}if(i){f[a].elAttribute=i}}}return f};n.ModelBinder.combineBindings=function(t,n){e.each(n,function(e,n){var r={selector:e.selector};if(e.converter){r.converter=e.converter}if(e.elAttribute){r.elAttribute=e.elAttribute}if(!t[n]){t[n]=r}else{t[n]=[t[n],r]}});return t};return n.ModelBinder})
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: backbone_model_binder-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Anton Taraev
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-07-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description: Backbone.ModelBinding for Rails
|
|
42
|
+
email:
|
|
43
|
+
- anti191@gmail.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- .gitignore
|
|
49
|
+
- Gemfile
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- Rakefile
|
|
53
|
+
- backbone_model_binder-rails.gemspec
|
|
54
|
+
- lib/backbone_model_binder-rails.rb
|
|
55
|
+
- lib/backbone_model_binder-rails/version.rb
|
|
56
|
+
- vendor/assets/javascripts/Backbone.CollectionBinder.js
|
|
57
|
+
- vendor/assets/javascripts/Backbone.CollectionBinder.min.js
|
|
58
|
+
- vendor/assets/javascripts/Backbone.ModelBinder.js
|
|
59
|
+
- vendor/assets/javascripts/Backbone.ModelBinder.min.js
|
|
60
|
+
homepage: ''
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata: {}
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - '>='
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubyforge_project:
|
|
80
|
+
rubygems_version: 2.0.3
|
|
81
|
+
signing_key:
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: Vendors Backbone.ModelBinding for use with the asset pipeline.
|
|
84
|
+
test_files: []
|