backbone-forms-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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +20 -0
- data/Rakefile +1 -0
- data/backbone-forms-rails.gemspec +24 -0
- data/lib/backbone-forms-rails.rb +9 -0
- data/lib/backbone/forms/rails/engine.rb +9 -0
- data/lib/backbone/forms/rails/version.rb +7 -0
- data/vendor/assets/javascripts/backbone-forms-rails.js +2 -0
- data/vendor/assets/javascripts/backbone-forms-rails/backbone-forms.js +798 -0
- data/vendor/assets/javascripts/backbone-forms-rails/jquery-ui-editors.js +405 -0
- data/vendor/assets/stylesheets/backbone-forms-rails.css +3 -0
- data/vendor/assets/stylesheets/backbone-forms-rails/backbone-forms.css +97 -0
- metadata +67 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
backbone-forms-rails
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
This gem wraps the [backbone-forms library](https://github.com/powmedia/backbone-forms) in the Rails asset pipeline's loving embrace. Rails 3.1 and up.
|
|
5
|
+
|
|
6
|
+
1. Add it to your gemfile:
|
|
7
|
+
|
|
8
|
+
gem 'backbone-forms-rails'
|
|
9
|
+
|
|
10
|
+
2. Include assets:
|
|
11
|
+
|
|
12
|
+
// In application.js
|
|
13
|
+
//= require backbone-forms-rails
|
|
14
|
+
|
|
15
|
+
/* In application.css */
|
|
16
|
+
/*
|
|
17
|
+
*= require backbone-forms-rails
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
3. Rock and roll.
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "backbone/forms/rails/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "backbone-forms-rails"
|
|
7
|
+
s.version = Backbone::Forms::Rails::VERSION
|
|
8
|
+
s.authors = ["Jason Morrison"]
|
|
9
|
+
s.email = ["jmorrison@thoughtbot.com"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Rails asset wrapper for backbone-forms}
|
|
12
|
+
s.description = %q{Use the backbone-forms library https://github.com/powmedia/backbone-forms: a "Form framework for BackboneJS with nested forms, editable lists and validation"}
|
|
13
|
+
|
|
14
|
+
s.rubyforge_project = "backbone-forms-rails"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
# specify any dependencies here; for example:
|
|
22
|
+
# s.add_development_dependency "rspec"
|
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
|
24
|
+
end
|
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
;(function() {
|
|
2
|
+
|
|
3
|
+
var helpers = {};
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This function is used to transform the key from a schema into the title used in a label.
|
|
7
|
+
* (If a specific title is provided it will be used instead).
|
|
8
|
+
*
|
|
9
|
+
* By default this converts a camelCase string into words, i.e. Camel Case
|
|
10
|
+
* If you have a different naming convention for schema keys, replace this function.
|
|
11
|
+
*
|
|
12
|
+
* @param {String} Key
|
|
13
|
+
* @return {String} Title
|
|
14
|
+
*/
|
|
15
|
+
helpers.keyToTitle = function(str) {
|
|
16
|
+
//Add spaces
|
|
17
|
+
var str = str.replace(/([A-Z])/g, ' $1');
|
|
18
|
+
|
|
19
|
+
//Uppercase first character
|
|
20
|
+
str = str.replace(/^./, function(str) { return str.toUpperCase(); });
|
|
21
|
+
|
|
22
|
+
return str;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Helper to create a template with the {{mustache}} style tags. Template settings are reset
|
|
27
|
+
* to user's settings when done to avoid conflicts.
|
|
28
|
+
* @param {String} Template string
|
|
29
|
+
* @return {Template} Compiled template
|
|
30
|
+
*/
|
|
31
|
+
helpers.createTemplate = function(str) {
|
|
32
|
+
//Store user's template options
|
|
33
|
+
var _interpolateBackup = _.templateSettings.interpolate;
|
|
34
|
+
|
|
35
|
+
//Set custom template settings
|
|
36
|
+
_.templateSettings.interpolate = /\{\{(.+?)\}\}/g
|
|
37
|
+
|
|
38
|
+
var template = _.template(str);
|
|
39
|
+
|
|
40
|
+
//Reset to users' template settings
|
|
41
|
+
_.templateSettings.interpolate = _interpolateBackup;
|
|
42
|
+
|
|
43
|
+
return template;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Return the editor constructor for a given schema 'type'.
|
|
49
|
+
* Accepts strings for the default editors, or the reference to the constructor function
|
|
50
|
+
* for custom editors
|
|
51
|
+
*
|
|
52
|
+
* @param {String|Function} The schema type e.g. 'Text', 'Select', or the editor constructor e.g. editors.Date
|
|
53
|
+
* @param {Object} Options to pass to editor, including required 'key', 'schema'
|
|
54
|
+
* @return {Mixed} An instance of the mapped editor
|
|
55
|
+
*/
|
|
56
|
+
helpers.createEditor = function(schemaType, options) {
|
|
57
|
+
var constructorFn;
|
|
58
|
+
|
|
59
|
+
if (_.isString(schemaType))
|
|
60
|
+
constructorFn = editors[schemaType];
|
|
61
|
+
else
|
|
62
|
+
constructorFn = schemaType;
|
|
63
|
+
|
|
64
|
+
return new constructorFn(options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Triggers an event that can be cancelled. Requires the user to invoke a callback. If false
|
|
69
|
+
* is passed to the callback, the action does not run.
|
|
70
|
+
*
|
|
71
|
+
* @param {Mixed} Instance of Backbone model, view, collection to trigger event on
|
|
72
|
+
* @param {String} Event name
|
|
73
|
+
* @param {Array} Arguments to pass to the event handlers
|
|
74
|
+
* @param {Function} Callback to run after the event handler has run.
|
|
75
|
+
* If any of them passed false or error, this callback won't run
|
|
76
|
+
*/
|
|
77
|
+
helpers.triggerCancellableEvent = function(subject, event, arguments, callback) {
|
|
78
|
+
var eventHandlers = subject._callbacks[event] || [];
|
|
79
|
+
|
|
80
|
+
if (!eventHandlers.length) return callback();
|
|
81
|
+
|
|
82
|
+
var fn = eventHandlers[0][0],
|
|
83
|
+
context = eventHandlers[0][1] || this;
|
|
84
|
+
|
|
85
|
+
//Add the callback that will be used when done
|
|
86
|
+
arguments.push(callback);
|
|
87
|
+
|
|
88
|
+
fn.apply(context, arguments);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
var Form = Backbone.View.extend({
|
|
95
|
+
|
|
96
|
+
//Field views
|
|
97
|
+
fields: null,
|
|
98
|
+
|
|
99
|
+
tagName: 'ul',
|
|
100
|
+
|
|
101
|
+
className: 'bbf-form',
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {Object} Options
|
|
105
|
+
* Required:
|
|
106
|
+
* schema {Array}
|
|
107
|
+
* Optional:
|
|
108
|
+
* model {Backbone.Model} : Use instead of data, and use commit().
|
|
109
|
+
* data {Array} : Pass this when not using a model. Use getValue() to get out value
|
|
110
|
+
* fields {Array} : Keys of fields to include in the form, in display order (default: all fields)
|
|
111
|
+
*/
|
|
112
|
+
initialize: function(options) {
|
|
113
|
+
this.schema = options.schema || (options.model ? options.model.schema : {}),
|
|
114
|
+
this.model = options.model;
|
|
115
|
+
this.data = options.data;
|
|
116
|
+
this.fieldsToRender = options.fields || _.keys(this.schema);
|
|
117
|
+
this.idPrefix = options.idPrefix || '';
|
|
118
|
+
|
|
119
|
+
//Stores all Field views
|
|
120
|
+
this.fields = {};
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Renders the form and all fields
|
|
125
|
+
*/
|
|
126
|
+
render: function() {
|
|
127
|
+
var schema = this.schema,
|
|
128
|
+
model = this.model,
|
|
129
|
+
data = this.data,
|
|
130
|
+
fieldsToRender = this.fieldsToRender,
|
|
131
|
+
fields = this.fields,
|
|
132
|
+
el = $(this.el),
|
|
133
|
+
self = this;
|
|
134
|
+
|
|
135
|
+
//Create form fields
|
|
136
|
+
_.each(fieldsToRender, function(key) {
|
|
137
|
+
var itemSchema = schema[key];
|
|
138
|
+
|
|
139
|
+
if (!itemSchema) throw "Field '"+key+"' not found in schema";
|
|
140
|
+
|
|
141
|
+
var options = {
|
|
142
|
+
key: key,
|
|
143
|
+
schema: itemSchema,
|
|
144
|
+
idPrefix: self.idPrefix
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (model) {
|
|
148
|
+
options.model = model;
|
|
149
|
+
} else if (data) {
|
|
150
|
+
options.value = data[key];
|
|
151
|
+
} else {
|
|
152
|
+
options.value = null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var field = new Field(options);
|
|
156
|
+
|
|
157
|
+
//Render the fields with editors, apart from Hidden fields
|
|
158
|
+
if (itemSchema.type == 'Hidden') {
|
|
159
|
+
field.editor = helpers.createEditor('Hidden', options);
|
|
160
|
+
} else {
|
|
161
|
+
el.append(field.render().el);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fields[key] = field;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return this;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Update the model with all latest values.
|
|
172
|
+
*
|
|
173
|
+
* @return {Object} Validation errors
|
|
174
|
+
*/
|
|
175
|
+
commit: function() {
|
|
176
|
+
var fields = this.fields,
|
|
177
|
+
errors = {};
|
|
178
|
+
|
|
179
|
+
_.each(fields, function(field) {
|
|
180
|
+
var error = field.commit();
|
|
181
|
+
if (error) errors[field.key] = error;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return _.isEmpty(errors) ? null : errors;
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get all the field values as an object.
|
|
189
|
+
* Use this method when passing data instead of objects
|
|
190
|
+
*
|
|
191
|
+
* @param {String} To get a specific field value pass the key name
|
|
192
|
+
*/
|
|
193
|
+
getValue: function(key) {
|
|
194
|
+
if (key) {
|
|
195
|
+
//Return given key only
|
|
196
|
+
return this.fields[key].getValue();
|
|
197
|
+
} else {
|
|
198
|
+
//Return entire form data
|
|
199
|
+
var schema = this.schema,
|
|
200
|
+
fields = this.fields
|
|
201
|
+
obj = {};
|
|
202
|
+
|
|
203
|
+
_.each(fields, function(field) {
|
|
204
|
+
obj[field.key] = field.getValue();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return obj;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Update field values, referenced by key
|
|
213
|
+
* @param {Object} New values to set
|
|
214
|
+
*/
|
|
215
|
+
setValue: function(data) {
|
|
216
|
+
for (var key in data) {
|
|
217
|
+
this.fields[key].setValue(data[key]);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Override default remove function in order to remove embedded views
|
|
223
|
+
*/
|
|
224
|
+
remove: function() {
|
|
225
|
+
var fields = this.fields;
|
|
226
|
+
|
|
227
|
+
for (var key in fields) {
|
|
228
|
+
fields[key].remove();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Backbone.View.prototype.remove.call(this);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
var Field = Backbone.View.extend({
|
|
238
|
+
|
|
239
|
+
tagName: 'li',
|
|
240
|
+
|
|
241
|
+
className: 'bbf-field',
|
|
242
|
+
|
|
243
|
+
events: {
|
|
244
|
+
'click label': 'logValue'
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
template: helpers.createTemplate('\
|
|
248
|
+
<label for="{{id}}">{{title}}</label>\
|
|
249
|
+
<div class="bbf-editor"></div>\
|
|
250
|
+
'),
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {Object} Options
|
|
254
|
+
* Required:
|
|
255
|
+
* key {String} : The model attribute key
|
|
256
|
+
* Optional:
|
|
257
|
+
* schema {Object} : Schema for the field
|
|
258
|
+
* value {Mixed} : Pass value when not using a model. Use getValue() to get out value
|
|
259
|
+
* model {Backbone.Model} : Use instead of value, and use commit().
|
|
260
|
+
* idPrefix {String} : Prefix to add to the editor DOM element's ID
|
|
261
|
+
*/
|
|
262
|
+
initialize: function(options) {
|
|
263
|
+
this.key = options.key;
|
|
264
|
+
this.schema = options.schema || {};
|
|
265
|
+
this.value = options.value;
|
|
266
|
+
this.model = options.model;
|
|
267
|
+
this.idPrefix = options.idPrefix || '';
|
|
268
|
+
|
|
269
|
+
//Set schema defaults
|
|
270
|
+
var schema = this.schema;
|
|
271
|
+
if (!schema.type) schema.type = 'Text';
|
|
272
|
+
if (!schema.title) schema.title = helpers.keyToTitle(this.key);
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
render: function() {
|
|
276
|
+
var schema = this.schema,
|
|
277
|
+
el = $(this.el);
|
|
278
|
+
|
|
279
|
+
//Standard options that will go to all editors
|
|
280
|
+
var options = {
|
|
281
|
+
key: this.key,
|
|
282
|
+
schema: schema,
|
|
283
|
+
idPrefix: this.idPrefix,
|
|
284
|
+
id: this.idPrefix + this.key
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
//Decide on data delivery type to pass to editors
|
|
288
|
+
if (this.model)
|
|
289
|
+
options.model = this.model;
|
|
290
|
+
else
|
|
291
|
+
options.value = this.value;
|
|
292
|
+
|
|
293
|
+
//Decide on the editor to use
|
|
294
|
+
var editor = helpers.createEditor(schema.type, options);
|
|
295
|
+
|
|
296
|
+
el.html(this.template({
|
|
297
|
+
key: this.key,
|
|
298
|
+
title: schema.title,
|
|
299
|
+
id: editor.id
|
|
300
|
+
}));
|
|
301
|
+
|
|
302
|
+
//Add the editor
|
|
303
|
+
$('.bbf-editor', el).html(editor.render().el);
|
|
304
|
+
|
|
305
|
+
this.editor = editor;
|
|
306
|
+
|
|
307
|
+
return this;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Update the model with the new value from the editor
|
|
312
|
+
*/
|
|
313
|
+
commit: function() {
|
|
314
|
+
return this.editor.commit();
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get the value from the editor
|
|
319
|
+
* @return {Mixed}
|
|
320
|
+
*/
|
|
321
|
+
getValue: function() {
|
|
322
|
+
return this.editor.getValue();
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Set/change the value of the editor
|
|
327
|
+
*/
|
|
328
|
+
setValue: function(value) {
|
|
329
|
+
this.editor.setValue(value);
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
logValue: function() {
|
|
333
|
+
console.log(this.getValue());
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
remove: function() {
|
|
337
|
+
this.editor.remove();
|
|
338
|
+
|
|
339
|
+
Backbone.View.prototype.remove.call(this);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
//========================================================================
|
|
348
|
+
//EDITORS
|
|
349
|
+
//========================================================================
|
|
350
|
+
|
|
351
|
+
var editors = {};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Base editor (interface). To be extended, not used directly
|
|
355
|
+
|
|
356
|
+
* @param {Object} Options
|
|
357
|
+
* Optional:
|
|
358
|
+
* model {Backbone.Model} : Use instead of value, and use commit().
|
|
359
|
+
* key {String} : The model attribute key. Required when using 'model'
|
|
360
|
+
* value {String} : When not using a model. If neither provided, defaultValue will be used.
|
|
361
|
+
* schema {Object} : May be required by some editors
|
|
362
|
+
*/
|
|
363
|
+
editors.Base = Backbone.View.extend({
|
|
364
|
+
|
|
365
|
+
defaultValue: null,
|
|
366
|
+
|
|
367
|
+
initialize: function(options) {
|
|
368
|
+
var options = options || {};
|
|
369
|
+
|
|
370
|
+
if (options.model) {
|
|
371
|
+
if (!options.key) throw "Missing option: 'key'";
|
|
372
|
+
|
|
373
|
+
this.model = options.model;
|
|
374
|
+
this.key = options.key;
|
|
375
|
+
|
|
376
|
+
this.value = this.model.get(this.key);
|
|
377
|
+
}
|
|
378
|
+
else if (options.value)
|
|
379
|
+
this.value = options.value;
|
|
380
|
+
|
|
381
|
+
if (this.value === undefined) this.value = this.defaultValue;
|
|
382
|
+
|
|
383
|
+
this.schema = options.schema;
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
getValue: function() {
|
|
387
|
+
throw 'Not implemented. Extend and override this method.';
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
setValue: function() {
|
|
391
|
+
throw 'Not implemented. Extend and override this method.';
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Update the model with the new value from the editor
|
|
396
|
+
*
|
|
397
|
+
* @return {Error|null} Validation error or null
|
|
398
|
+
*/
|
|
399
|
+
commit: function() {
|
|
400
|
+
var el = $(this.el),
|
|
401
|
+
change = {};
|
|
402
|
+
|
|
403
|
+
change[this.key] = this.getValue();
|
|
404
|
+
|
|
405
|
+
var error = null
|
|
406
|
+
this.model.set(change, {
|
|
407
|
+
error: function(model, e) {
|
|
408
|
+
error = e;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
if (error)
|
|
413
|
+
el.addClass('bbf-error');
|
|
414
|
+
else
|
|
415
|
+
el.removeClass('bbf-error');
|
|
416
|
+
|
|
417
|
+
return error;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
editors.Text = editors.Base.extend({
|
|
423
|
+
|
|
424
|
+
tagName: 'input',
|
|
425
|
+
|
|
426
|
+
defaultValue: '',
|
|
427
|
+
|
|
428
|
+
initialize: function(options) {
|
|
429
|
+
editors.Base.prototype.initialize.call(this, options);
|
|
430
|
+
|
|
431
|
+
$(this.el).attr('type', 'text');
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Adds the editor to the DOM
|
|
436
|
+
*/
|
|
437
|
+
render: function() {
|
|
438
|
+
this.setValue(this.value);
|
|
439
|
+
|
|
440
|
+
return this;
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Returns the current editor value
|
|
445
|
+
* @return {String}
|
|
446
|
+
*/
|
|
447
|
+
getValue: function() {
|
|
448
|
+
return $(this.el).val();
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Sets the value of the form element
|
|
453
|
+
* @param {String}
|
|
454
|
+
*/
|
|
455
|
+
setValue: function(value) {
|
|
456
|
+
$(this.el).val(value);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Normal text input that only allows a number. Letters etc. are not entered
|
|
464
|
+
*/
|
|
465
|
+
editors.Number = editors.Text.extend({
|
|
466
|
+
|
|
467
|
+
defaultValue: 0,
|
|
468
|
+
|
|
469
|
+
events: {
|
|
470
|
+
'keypress': 'onKeyPress'
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Check value is numeric
|
|
475
|
+
*/
|
|
476
|
+
onKeyPress: function(event) {
|
|
477
|
+
var newVal = $(this.el).val() + String.fromCharCode(event.keyCode);
|
|
478
|
+
|
|
479
|
+
var numeric = /^[0-9]*\.?[0-9]*?$/.test(newVal);
|
|
480
|
+
|
|
481
|
+
if (!numeric) event.preventDefault();
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
getValue: function() {
|
|
485
|
+
var value = $(this.el).val();
|
|
486
|
+
|
|
487
|
+
return value === "" ? null : parseFloat(value, 10);
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
setValue: function(value) {
|
|
491
|
+
value = value === null ? null : parseFloat(value, 10);
|
|
492
|
+
|
|
493
|
+
editors.Text.prototype.setValue.call(this, value);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
editors.Password = editors.Text.extend({
|
|
500
|
+
|
|
501
|
+
initialize: function(options) {
|
|
502
|
+
editors.Text.prototype.initialize.call(this, options);
|
|
503
|
+
|
|
504
|
+
$(this.el).attr('type', 'password');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
editors.TextArea = editors.Text.extend({
|
|
511
|
+
|
|
512
|
+
tagName: 'textarea',
|
|
513
|
+
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
editors.Hidden = editors.Base.extend({
|
|
518
|
+
|
|
519
|
+
defaultValue: '',
|
|
520
|
+
|
|
521
|
+
initialize: function(options) {
|
|
522
|
+
editors.Text.prototype.initialize.call(this, options);
|
|
523
|
+
|
|
524
|
+
$(this.el).attr('type', 'hidden');
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
getValue: function() {
|
|
528
|
+
return this.value;
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
setValue: function(value) {
|
|
532
|
+
this.value = value;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Renders a <select> with given options
|
|
540
|
+
*
|
|
541
|
+
* Requires an 'options' value on the schema.
|
|
542
|
+
* Can be an array of options, a function that calls back with the array of options, a string of HTML
|
|
543
|
+
* or a Backbone collection. If a collection, the models must implement a toString() method
|
|
544
|
+
*/
|
|
545
|
+
editors.Select = editors.Base.extend({
|
|
546
|
+
|
|
547
|
+
tagName: 'select',
|
|
548
|
+
|
|
549
|
+
initialize: function(options) {
|
|
550
|
+
editors.Base.prototype.initialize.call(this, options);
|
|
551
|
+
|
|
552
|
+
if (!this.schema || !this.schema.options)
|
|
553
|
+
throw "Missing required 'schema.options'";
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
render: function() {
|
|
557
|
+
var options = this.schema.options,
|
|
558
|
+
self = this;
|
|
559
|
+
|
|
560
|
+
//If a collection was passed, check if it needs fetching
|
|
561
|
+
if (options instanceof Backbone.Collection) {
|
|
562
|
+
var collection = options;
|
|
563
|
+
|
|
564
|
+
//Don't do the fetch if it's already populated
|
|
565
|
+
if (collection.length > 0) {
|
|
566
|
+
self.renderOptions(options);
|
|
567
|
+
} else {
|
|
568
|
+
collection.fetch({
|
|
569
|
+
success: function(collection) {
|
|
570
|
+
self.renderOptions(options);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
//If a function was passed, run it to get the options
|
|
577
|
+
else if (_.isFunction(options)) {
|
|
578
|
+
options(function(result) {
|
|
579
|
+
self.renderOptions(result);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
//Otherwise, ready to go straight to renderOptions
|
|
584
|
+
else {
|
|
585
|
+
self.renderOptions(options);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return this;
|
|
589
|
+
},
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Adds the <option> html to the DOM
|
|
593
|
+
* @param {Mixed} Options as a simple array e.g. ['option1', 'option2']
|
|
594
|
+
* or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}]
|
|
595
|
+
* or as a string of <option> HTML to insert into the <select>
|
|
596
|
+
*/
|
|
597
|
+
renderOptions: function(options) {
|
|
598
|
+
var $select = $(this.el),
|
|
599
|
+
html;
|
|
600
|
+
|
|
601
|
+
//Accept string of HTML
|
|
602
|
+
if (_.isString(options)) {
|
|
603
|
+
html = options;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
//Or array
|
|
607
|
+
else if (_.isArray(options)) {
|
|
608
|
+
html = this._arrayToHtml(options);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
//Or Backbone collection
|
|
612
|
+
else if (options instanceof Backbone.Collection) {
|
|
613
|
+
html = this._collectionToHtml(options)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
//Insert options
|
|
617
|
+
$select.html(html);
|
|
618
|
+
|
|
619
|
+
//Select correct option
|
|
620
|
+
this.setValue(this.value);
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
getValue: function() {
|
|
624
|
+
return $(this.el).val();
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
setValue: function(value) {
|
|
628
|
+
$(this.el).val(value);
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Transforms a collection into HTML ready to use in the renderOptions method
|
|
633
|
+
* @param {Backbone.Collection}
|
|
634
|
+
* @return {String}
|
|
635
|
+
*/
|
|
636
|
+
_collectionToHtml: function(collection) {
|
|
637
|
+
//Convert collection to array first
|
|
638
|
+
var array = [];
|
|
639
|
+
collection.each(function(model) {
|
|
640
|
+
array.push({ val: model.id, label: model.toString() });
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
//Now convert to HTML
|
|
644
|
+
var html = this._arrayToHtml(array);
|
|
645
|
+
|
|
646
|
+
return html;
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Create the <option> HTML
|
|
651
|
+
* @param {Array} Options as a simple array e.g. ['option1', 'option2']
|
|
652
|
+
* or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}]
|
|
653
|
+
* @return {String} HTML
|
|
654
|
+
*/
|
|
655
|
+
_arrayToHtml: function(array) {
|
|
656
|
+
var html = [];
|
|
657
|
+
|
|
658
|
+
//Generate HTML
|
|
659
|
+
_.each(array, function(option) {
|
|
660
|
+
if (_.isObject(option)) {
|
|
661
|
+
var val = option.val ? option.val : '';
|
|
662
|
+
html.push('<option value="'+val+'">'+option.label+'</option>');
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
html.push('<option>'+option+'</option>');
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
return html.join('');
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Creates a child form. For editing Javascript objects
|
|
679
|
+
*
|
|
680
|
+
* Special options:
|
|
681
|
+
* schema.subSchema: Subschema for object.
|
|
682
|
+
* idPrefix,
|
|
683
|
+
*/
|
|
684
|
+
editors.Object = editors.Base.extend({
|
|
685
|
+
|
|
686
|
+
className: 'bbf-object',
|
|
687
|
+
|
|
688
|
+
defaultValue: {},
|
|
689
|
+
|
|
690
|
+
initialize: function(options) {
|
|
691
|
+
editors.Base.prototype.initialize.call(this, options);
|
|
692
|
+
|
|
693
|
+
if (!this.schema.subSchema)
|
|
694
|
+
throw "Missing required 'schema.subSchema' option for Object editor";
|
|
695
|
+
|
|
696
|
+
this.idPrefix = options.idPrefix || '';
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
render: function() {
|
|
700
|
+
var el = $(this.el),
|
|
701
|
+
data = this.value || {},
|
|
702
|
+
key = this.key,
|
|
703
|
+
schema = this.schema,
|
|
704
|
+
objSchema = schema.subSchema;
|
|
705
|
+
|
|
706
|
+
this.form = new Form({
|
|
707
|
+
schema: objSchema,
|
|
708
|
+
data: data,
|
|
709
|
+
idPrefix: this.idPrefix + this.key + '_'
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
//Render form
|
|
713
|
+
el.html(this.form.render().el);
|
|
714
|
+
|
|
715
|
+
return this;
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
getValue: function() {
|
|
719
|
+
return this.form.getValue();
|
|
720
|
+
},
|
|
721
|
+
|
|
722
|
+
setValue: function(value) {
|
|
723
|
+
this.value = value;
|
|
724
|
+
|
|
725
|
+
this.render();
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
remove: function() {
|
|
729
|
+
this.form.remove();
|
|
730
|
+
|
|
731
|
+
Backbone.View.prototype.remove.call(this);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Creates a child form. For editing nested Backbone models
|
|
739
|
+
*
|
|
740
|
+
* Special options:
|
|
741
|
+
* schema.model: Embedded model constructor
|
|
742
|
+
*/
|
|
743
|
+
editors.NestedModel = editors.Object.extend({
|
|
744
|
+
|
|
745
|
+
initialize: function(options) {
|
|
746
|
+
editors.Base.prototype.initialize.call(this, options);
|
|
747
|
+
|
|
748
|
+
if (!options.schema.model)
|
|
749
|
+
throw 'Missing required "schema.model" option for NestedModel editor';
|
|
750
|
+
|
|
751
|
+
this.idPrefix = options.idPrefix || '';
|
|
752
|
+
},
|
|
753
|
+
|
|
754
|
+
render: function() {
|
|
755
|
+
var el = $(this.el),
|
|
756
|
+
data = this.value || {},
|
|
757
|
+
key = this.key,
|
|
758
|
+
nestedModel = this.schema.model,
|
|
759
|
+
nestedModelSchema = (nestedModel).prototype.schema;
|
|
760
|
+
|
|
761
|
+
this.form = new Form({
|
|
762
|
+
schema: nestedModelSchema,
|
|
763
|
+
model: new nestedModel(data),
|
|
764
|
+
idPrefix: this.idPrefix + this.key + '_'
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
//Render form
|
|
768
|
+
el.html(this.form.render().el);
|
|
769
|
+
|
|
770
|
+
return this;
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Update the embedded model, checking for nested validation errors and pass them up
|
|
775
|
+
* Then update the main model if all OK
|
|
776
|
+
*
|
|
777
|
+
* @return {Error|null} Validation error or null
|
|
778
|
+
*/
|
|
779
|
+
commit: function() {
|
|
780
|
+
var error = this.form.commit();
|
|
781
|
+
if (error) {
|
|
782
|
+
$(this.el).addClass('error');
|
|
783
|
+
return error;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return editors.Object.prototype.commit.call(this);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
//Exports
|
|
793
|
+
Form.helpers = helpers;
|
|
794
|
+
Form.Field = Field;
|
|
795
|
+
Form.editors = editors;
|
|
796
|
+
Backbone.Form = Form;
|
|
797
|
+
|
|
798
|
+
})();
|