pbw 0.0.10 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}));
|