pbw 0.0.10 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Rakefile +1 -10
- data/app/controllers/pbw/{application_controller.rb → base_controller.rb} +1 -3
- data/app/controllers/pbw/base_models_controller.rb +13 -19
- data/app/controllers/pbw/item_containers_controller.rb +36 -0
- data/app/controllers/pbw/passwords_controller.rb +2 -2
- data/app/controllers/pbw/registrations_controller.rb +5 -3
- data/app/controllers/pbw/sessions_controller.rb +6 -3
- data/app/controllers/pbw/tokens_controller.rb +4 -0
- data/app/models/pbw/ability.rb +3 -3
- data/app/models/pbw/attached_process.rb +2 -0
- data/app/models/pbw/capability.rb +1 -1
- data/app/models/pbw/constraint.rb +1 -1
- data/app/models/pbw/trigger.rb +1 -1
- data/app/models/pbw/user.rb +3 -3
- data/config/initializers/mongoid_accessible_attributes.rb +14 -0
- data/config/routes.rb +15 -6
- data/lib/generators/pbw/area/area_generator.rb +4 -0
- data/lib/generators/pbw/install/install_generator.rb +1 -1
- data/lib/generators/pbw/item/item_generator.rb +4 -0
- data/lib/generators/pbw/resource_helpers.rb +4 -0
- data/lib/generators/pbw/rules/command/command_generator.rb +10 -0
- data/lib/generators/pbw/scaffold_generator.rb +15 -12
- data/lib/generators/pbw/templates/index.erb +1 -0
- data/lib/generators/pbw/templates/model.coffee +5 -5
- data/lib/generators/pbw/templates/pbw.coffee +18 -9
- data/lib/generators/pbw/templates/router.coffee +9 -0
- data/lib/generators/pbw/templates/templates/edit.jst +19 -4
- data/lib/generators/pbw/templates/templates/home.jst +2 -1
- data/lib/generators/pbw/templates/templates/index.jst +21 -10
- data/lib/generators/pbw/templates/templates/model.jst +15 -2
- data/lib/generators/pbw/templates/templates/new.jst +20 -4
- data/lib/generators/pbw/templates/templates/show.jst +12 -3
- data/lib/generators/pbw/templates/user_recovery.coffee +0 -1
- data/lib/generators/pbw/templates/user_registration.coffee +0 -1
- data/lib/generators/pbw/templates/user_session.coffee +0 -1
- data/lib/generators/pbw/templates/views/edit_view.coffee +19 -8
- data/lib/generators/pbw/templates/views/index_view.coffee +10 -2
- data/lib/generators/pbw/templates/views/login_view.coffee +20 -8
- data/lib/generators/pbw/templates/views/new_view.coffee +13 -5
- data/lib/generators/pbw/templates/views/recovery_view.coffee +8 -3
- data/lib/generators/pbw/templates/views/show_view.coffee +11 -1
- data/lib/generators/pbw/templates/views/signup_view.coffee +19 -5
- data/lib/generators/pbw/token/token_generator.rb +4 -0
- data/lib/pbw/engine.rb +1 -0
- data/lib/pbw/version.rb +1 -1
- data/vendor/assets/javascripts/Backbone.CollectionBinder.js +281 -0
- data/vendor/assets/javascripts/Backbone.ModelBinder.js +576 -0
- metadata +6 -6
- data/app/controllers/pbw/item_conversions_controller.rb +0 -7
- data/vendor/assets/javascripts/backbone_datalink.js +0 -21
- data/vendor/assets/javascripts/backbone_rails_sync.js +0 -81
@@ -9,15 +9,20 @@ class <%= user_view_namespace %>.RecoveryView extends Backbone.View
|
|
9
9
|
constructor: (options) ->
|
10
10
|
super(options)
|
11
11
|
@model = new <%= js_user_model_namespace %>Recovery
|
12
|
-
|
12
|
+
|
13
13
|
@model.bind("change:errors", () =>
|
14
14
|
this.render()
|
15
15
|
)
|
16
16
|
|
17
17
|
@model.bind("error", (model, xhr, options) =>
|
18
|
-
|
18
|
+
display_errors 'There was a problem recovering your password', xhr
|
19
19
|
)
|
20
20
|
|
21
|
+
initialize: ->
|
22
|
+
@_modelBinder = new Backbone.ModelBinder
|
23
|
+
@bindings =
|
24
|
+
email: '[name=email]'
|
25
|
+
|
21
26
|
save: (e) ->
|
22
27
|
e.preventDefault()
|
23
28
|
e.stopPropagation()
|
@@ -33,6 +38,6 @@ class <%= user_view_namespace %>.RecoveryView extends Backbone.View
|
|
33
38
|
render: ->
|
34
39
|
@$el.html(@template(@model.toJSON() ))
|
35
40
|
|
36
|
-
|
41
|
+
@_modelBinder.bind(@model, @el, @bindings)
|
37
42
|
|
38
43
|
return this
|
@@ -3,6 +3,16 @@
|
|
3
3
|
class <%= view_namespace %>.ShowView extends Backbone.View
|
4
4
|
template: JST["<%= jst 'show' %>"]
|
5
5
|
|
6
|
+
initialize: () ->
|
7
|
+
@model = @options.model
|
8
|
+
@model.bind("error", (model, xhr, options) =>
|
9
|
+
display_errors 'There was a problem displaying <%= singular_name %>', xhr, "/<%=plural_model_name%>/#{@model.id}"
|
10
|
+
)
|
11
|
+
|
6
12
|
render: ->
|
7
|
-
|
13
|
+
@model.fetch
|
14
|
+
success: (model) =>
|
15
|
+
@$el.html(@template(model.toJSON() ))
|
16
|
+
error: (model, response) ->
|
17
|
+
debug response
|
8
18
|
return this
|
@@ -9,15 +9,27 @@ class <%= user_view_namespace %>.SignupView extends Backbone.View
|
|
9
9
|
constructor: (options) ->
|
10
10
|
super(options)
|
11
11
|
@model = new <%= js_user_model_namespace %>Registration
|
12
|
-
|
12
|
+
|
13
13
|
@model.bind("change:errors", () =>
|
14
14
|
this.render()
|
15
15
|
)
|
16
16
|
|
17
17
|
@model.bind("error", (model, xhr, options) =>
|
18
|
-
|
18
|
+
display_errors 'There was a problem signing up', xhr
|
19
|
+
)
|
20
|
+
|
21
|
+
@model.bind("sync", (model, xhr, options) =>
|
22
|
+
window.<%=js_app_name%>.User = xhr
|
19
23
|
)
|
20
24
|
|
25
|
+
initialize: ->
|
26
|
+
@_modelBinder = new Backbone.ModelBinder
|
27
|
+
@bindings =
|
28
|
+
name: '[name=name]'
|
29
|
+
email: '[name=email]'
|
30
|
+
password: '[name=password]'
|
31
|
+
password_confirmation: '[name=password_confirmation]'
|
32
|
+
|
21
33
|
save: (e) ->
|
22
34
|
e.preventDefault()
|
23
35
|
e.stopPropagation()
|
@@ -27,13 +39,15 @@ class <%= user_view_namespace %>.SignupView extends Backbone.View
|
|
27
39
|
@model.save(@model.attributes,
|
28
40
|
success: (user, response, options) =>
|
29
41
|
@model = user
|
30
|
-
window.<%=
|
31
|
-
|
42
|
+
if window.<%=js_app_name%>.backlink
|
43
|
+
window.location.hash = window.<%=js_app_name%>.backlink
|
44
|
+
else
|
45
|
+
window.location.hash = "/"
|
32
46
|
)
|
33
47
|
|
34
48
|
render: ->
|
35
49
|
@$el.html(@template(@model.toJSON() ))
|
36
50
|
|
37
|
-
|
51
|
+
@_modelBinder.bind(@model, @el, @bindings)
|
38
52
|
|
39
53
|
return this
|
data/lib/pbw/engine.rb
CHANGED
data/lib/pbw/version.rb
CHANGED
@@ -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,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
|
+
}));
|