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 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
+ })();