backbone-forms-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in backbone-forms-rails.gemspec
4
+ gemspec
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,9 @@
1
+ require "backbone/forms/rails/version"
2
+ require 'backbone/forms/rails/engine'
3
+
4
+ module Backbone
5
+ module Forms
6
+ module Rails
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Backbone
2
+ module Forms
3
+ module Rails
4
+ class Engine < ::Rails::Engine
5
+ # auto wire
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Backbone
2
+ module Forms
3
+ module Rails
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ //= require backbone-forms-rails/jquery-ui-editors.js
2
+ //= require backbone-forms-rails/backbone-forms.js
@@ -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
+ })();