diff --git a/distribution.amd/editors/list.js b/distribution.amd/editors/list.js index fc1474be..a1cf69ab 100644 --- a/distribution.amd/editors/list.js +++ b/distribution.amd/editors/list.js @@ -4,7 +4,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba /** * List editor - * + * * An array editor. Creates a list of other editor items. * * Special options: @@ -50,15 +50,34 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba }, render: function() { - var self = this, - value = this.value || []; - //Create main element var $el = $($.trim(this.template())); //Store a reference to the list (item container) this.$list = $el.is('[data-items]') ? $el : $el.find('[data-items]'); + this.renderItems(); + + this.setElement($el); + this.$el.attr('id', this.id); + this.$el.attr('name', this.key); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + renderItems: function() { + var self = this, + value = this.value || []; + + // Easier to just empty the $list but we could potentially have default items + // for a custom template. + _.each(this.items, function(item) { + item.$el.remove(); + }); + this.items = []; + //Add existing items if (value.length) { _.each(value, function(itemValue) { @@ -70,14 +89,6 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba else { if (!this.Editor.isAsync) this.addItem(); } - - this.setElement($el); - this.$el.attr('id', this.id); - this.$el.attr('name', this.key); - - if (this.hasFocus) this.trigger('blur', this); - - return this; }, /** @@ -98,11 +109,11 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba Editor: this.Editor, key: this.key }).render(); - + var _addItem = function() { self.items.push(item); self.$list.append(item.el); - + item.editor.on('all', function(event) { if (event === 'change') return; @@ -136,11 +147,11 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba self.trigger('blur', self); }, 0); }, self); - + if (userInitiated || value) { item.addEventTriggered = true; } - + if (userInitiated) { self.trigger('add', self, item.editor); self.trigger('change', self); @@ -157,7 +168,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba _addItem(); item.editor.focus(); } - + return item; }, @@ -174,7 +185,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba this.items[index].remove(); this.items.splice(index, 1); - + if (item.addEventTriggered) { this.trigger('remove', this, item.editor); this.trigger('change', this); @@ -194,20 +205,20 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba setValue: function(value) { this.value = value; - this.render(); + this.renderItems(); }, - + focus: function() { if (this.hasFocus) return; if (this.items[0]) this.items[0].editor.focus(); }, - + blur: function() { if (!this.hasFocus) return; var focusedItem = _.find(this.items, function(item) { return item.editor.hasFocus; }); - + if (focusedItem) focusedItem.editor.blur(); }, @@ -219,10 +230,10 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba Form.editors.Base.prototype.remove.call(this); }, - + /** * Run validation - * + * * @return {Object|Null} */ validate: function() { @@ -312,7 +323,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba //Replace the entire element so there isn't a wrapper tag this.setElement($el); - + return this; }, @@ -323,11 +334,11 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba setValue: function(value) { this.editor.setValue(value); }, - + focus: function() { this.editor.focus(); }, - + blur: function() { this.editor.blur(); }, @@ -396,7 +407,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba /** - * Base modal object editor for use with the List editor; used by Object + * Base modal object editor for use with the List editor; used by Object * and NestedModal list types */ Form.editors.List.Modal = Form.editors.Base.extend({ @@ -415,9 +426,9 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba */ initialize: function(options) { options = options || {}; - + Form.editors.Base.prototype.initialize.call(this, options); - + //Dependencies if (!Form.editors.List.Modal.ModalAdapter) throw new Error('A ModalAdapter is required'); @@ -466,7 +477,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba * Function which returns a generic string representation of an object * * @param {Object} value - * + * * @return {String} */ itemToString: function(value) { @@ -503,7 +514,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the generic method or custom overridden method return this.itemToString(value); }, @@ -528,7 +539,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba this.trigger('focus', this); modal.on('cancel', this.onModalClosed, this); - + modal.on('ok', _.bind(this.onModalSubmitted, this)); }, @@ -552,7 +563,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba this.renderSummary(); if (isNew) this.trigger('readyToAdd'); - + this.trigger('change', this); this.onModalClosed(); @@ -576,16 +587,16 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba setValue: function(value) { this.value = value; }, - + focus: function() { if (this.hasFocus) return; this.openEditor(); }, - + blur: function() { if (!this.hasFocus) return; - + if (this.modal) { this.modal.trigger('cancel'); } @@ -600,7 +611,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba //Defaults to BootstrapModal (http://github.com/powmedia/backbone.bootstrap-modal) //Can be replaced with another adapter that implements the same interface. ModalAdapter: Backbone.BootstrapModal, - + //Make the wait list for the 'ready' event before adding the item to the list isAsync: true }); @@ -643,7 +654,7 @@ define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Ba //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the model return new (schema.model)(value).toString(); } diff --git a/distribution.amd/editors/list.min.js b/distribution.amd/editors/list.min.js index 772f5831..764201d0 100644 --- a/distribution.amd/editors/list.min.js +++ b/distribution.amd/editors/list.min.js @@ -1 +1 @@ -define(["jquery","underscore","backbone","backbone-forms"],function(e,t,n){(function(r){r.editors.List=r.editors.Base.extend({events:{'click [data-action="add"]':function(e){e.preventDefault(),this.addItem(null,!0)}},initialize:function(e){e=e||{};var t=r.editors;t.Base.prototype.initialize.call(this,e);var n=this.schema;if(!n)throw new Error("Missing required option 'schema'");this.template=e.template||this.constructor.template,this.Editor=function(){var e=n.itemType;return e?t.List[e]?t.List[e]:t[e]:t.Text}(),this.items=[]},render:function(){var n=this,r=this.value||[],i=e(e.trim(this.template()));return this.$list=i.is("[data-items]")?i:i.find("[data-items]"),r.length?t.each(r,function(e){n.addItem(e)}):this.Editor.isAsync||this.addItem(),this.setElement(i),this.$el.attr("id",this.id),this.$el.attr("name",this.key),this.hasFocus&&this.trigger("blur",this),this},addItem:function(e,n){var i=this,s=r.editors,o=(new s.List.Item({list:this,form:this.form,schema:this.schema,value:e,Editor:this.Editor,key:this.key})).render(),u=function(){i.items.push(o),i.$list.append(o.el),o.editor.on("all",function(e){if(e==="change")return;var n=t.toArray(arguments);n[0]="item:"+e,n.splice(1,0,i),s.List.prototype.trigger.apply(this,n)},i),o.editor.on("change",function(){o.addEventTriggered||(o.addEventTriggered=!0,this.trigger("add",this,o.editor)),this.trigger("item:change",this,o.editor),this.trigger("change",this)},i),o.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},i),o.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(t.find(e.items,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},i);if(n||e)o.addEventTriggered=!0;n&&(i.trigger("add",i,o.editor),i.trigger("change",i))};return this.Editor.isAsync?o.editor.on("readyToAdd",u,this):(u(),o.editor.focus()),o},removeItem:function(e){var n=this.schema.confirmDelete;if(n&&!confirm(n))return;var r=t.indexOf(this.items,e);this.items[r].remove(),this.items.splice(r,1),e.addEventTriggered&&(this.trigger("remove",this,e.editor),this.trigger("change",this)),!this.items.length&&!this.Editor.isAsync&&this.addItem()},getValue:function(){var e=t.map(this.items,function(e){return e.getValue()});return t.without(e,undefined,"")},setValue:function(e){this.value=e,this.render()},focus:function(){if(this.hasFocus)return;this.items[0]&&this.items[0].editor.focus()},blur:function(){if(!this.hasFocus)return;var e=t.find(this.items,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){t.invoke(this.items,"remove"),r.editors.Base.prototype.remove.call(this)},validate:function(){if(!this.validators)return null;var e=t.map(this.items,function(e){return e.validate()}),n=t.compact(e).length?!0:!1;if(!n)return null;var r={type:"list",message:"Some of the items in the list failed validation",errors:e};return r}},{template:t.template('
',null,r.templateSettings)}),r.editors.List.Item=r.editors.Base.extend({events:{'click [data-action="remove"]':function(e){e.preventDefault(),this.list.removeItem(this)},"keydown input[type=text]":function(e){if(e.keyCode!==13)return;e.preventDefault(),this.list.addItem(),this.list.$list.find("> li:last input").focus()}},initialize:function(e){this.list=e.list,this.schema=e.schema||this.list.schema,this.value=e.value,this.Editor=e.Editor||r.editors.Text,this.key=e.key,this.template=e.template||this.schema.itemTemplate||this.constructor.template,this.errorClassName=e.errorClassName||this.constructor.errorClassName,this.form=e.form},render:function(){this.editor=(new this.Editor({key:this.key,schema:this.schema,value:this.value,list:this.list,item:this,form:this.form})).render();var t=e(e.trim(this.template()));return t.find("[data-editor]").append(this.editor.el),this.setElement(t),this},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),n.View.prototype.remove.call(this)},validate:function(){var e=this.getValue(),n=this.list.form?this.list.form.getValue():{},r=this.schema.validators,i=this.getValidator;if(!r)return null;var s=null;return t.every(r,function(t){return s=i(t)(e,n),s?!1:!0}),s?this.setError(s):this.clearError(),s?s:null},setError:function(e){this.$el.addClass(this.errorClassName),this.$el.attr("title",e.message)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$el.attr("title",null)}},{template:t.template('
',null,r.templateSettings),errorClassName:"error"}),r.editors.List.Modal=r.editors.Base.extend({events:{click:"openEditor"},initialize:function(e){e=e||{},r.editors.Base.prototype.initialize.call(this,e);if(!r.editors.List.Modal.ModalAdapter)throw new Error("A ModalAdapter is required");this.form=e.form;if(!e.form)throw new Error('Missing required option: "form"');this.template=e.template||this.constructor.template},render:function(){var e=this;return t.isEmpty(this.value)?this.openEditor():(this.renderSummary(),setTimeout(function(){e.trigger("readyToAdd")},0)),this.hasFocus&&this.trigger("blur",this),this},renderSummary:function(){this.$el.html(e.trim(this.template({summary:this.getStringValue()})))},itemToString:function(e){var n=function(e){var t={key:e};return r.Field.prototype.createTitle.call(t)};e=e||{};var i=[];return t.each(this.nestedSchema,function(r,s){var o=r.title?r.title:n(s),u=e[s];if(t.isUndefined(u)||t.isNull(u))u="";i.push(o+": "+u)}),i.join("
")},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?"[Empty]":e.itemToString?e.itemToString(n):this.itemToString(n)},openEditor:function(){var e=this,n=this.form.constructor,i=this.modalForm=new n({schema:this.nestedSchema,data:this.value}),s=this.modal=new r.editors.List.Modal.ModalAdapter({content:i,animate:!0});s.open(),this.trigger("open",this),this.trigger("focus",this),s.on("cancel",this.onModalClosed,this),s.on("ok",t.bind(this.onModalSubmitted,this))},onModalSubmitted:function(){var e=this.modal,t=this.modalForm,n=!this.value,r=t.validate();if(r)return e.preventClose();this.value=t.getValue(),this.renderSummary(),n&&this.trigger("readyToAdd"),this.trigger("change",this),this.onModalClosed()},onModalClosed:function(){this.modal=null,this.modalForm=null,this.trigger("close",this),this.trigger("blur",this)},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){if(this.hasFocus)return;this.openEditor()},blur:function(){if(!this.hasFocus)return;this.modal&&this.modal.trigger("cancel")}},{template:t.template("
<%= summary %>
",null,r.templateSettings),ModalAdapter:n.BootstrapModal,isAsync:!0}),r.editors.List.Object=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.initialize.apply(this,arguments);var e=this.schema;if(!e.subSchema)throw new Error('Missing required option "schema.subSchema"');this.nestedSchema=e.subSchema}}),r.editors.List.NestedModel=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.initialize.apply(this,arguments);var e=this.schema;if(!e.model)throw new Error('Missing required option "schema.model"');var n=e.model.prototype.schema;this.nestedSchema=t.isFunction(n)?n():n},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?null:e.itemToString?e.itemToString(n):(new e.model(n)).toString()}})})(n.Form)}) \ No newline at end of file +define(["jquery","underscore","backbone","backbone-forms"],function(e,t,n){(function(r){r.editors.List=r.editors.Base.extend({events:{'click [data-action="add"]':function(e){e.preventDefault(),this.addItem(null,!0)}},initialize:function(e){e=e||{};var t=r.editors;t.Base.prototype.initialize.call(this,e);var n=this.schema;if(!n)throw new Error("Missing required option 'schema'");this.template=e.template||this.constructor.template,this.Editor=function(){var e=n.itemType;return e?t.List[e]?t.List[e]:t[e]:t.Text}(),this.items=[]},render:function(){var t=e(e.trim(this.template()));return this.$list=t.is("[data-items]")?t:t.find("[data-items]"),this.renderItems(),this.setElement(t),this.$el.attr("id",this.id),this.$el.attr("name",this.key),this.hasFocus&&this.trigger("blur",this),this},renderItems:function(){var e=this,n=this.value||[];t.each(this.items,function(e){e.$el.remove()}),this.items=[],n.length?t.each(n,function(t){e.addItem(t)}):this.Editor.isAsync||this.addItem()},addItem:function(e,n){var i=this,s=r.editors,o=(new s.List.Item({list:this,form:this.form,schema:this.schema,value:e,Editor:this.Editor,key:this.key})).render(),u=function(){i.items.push(o),i.$list.append(o.el),o.editor.on("all",function(e){if(e==="change")return;var n=t.toArray(arguments);n[0]="item:"+e,n.splice(1,0,i),s.List.prototype.trigger.apply(this,n)},i),o.editor.on("change",function(){o.addEventTriggered||(o.addEventTriggered=!0,this.trigger("add",this,o.editor)),this.trigger("item:change",this,o.editor),this.trigger("change",this)},i),o.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},i),o.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(t.find(e.items,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},i);if(n||e)o.addEventTriggered=!0;n&&(i.trigger("add",i,o.editor),i.trigger("change",i))};return this.Editor.isAsync?o.editor.on("readyToAdd",u,this):(u(),o.editor.focus()),o},removeItem:function(e){var n=this.schema.confirmDelete;if(n&&!confirm(n))return;var r=t.indexOf(this.items,e);this.items[r].remove(),this.items.splice(r,1),e.addEventTriggered&&(this.trigger("remove",this,e.editor),this.trigger("change",this)),!this.items.length&&!this.Editor.isAsync&&this.addItem()},getValue:function(){var e=t.map(this.items,function(e){return e.getValue()});return t.without(e,undefined,"")},setValue:function(e){this.value=e,this.renderItems()},focus:function(){if(this.hasFocus)return;this.items[0]&&this.items[0].editor.focus()},blur:function(){if(!this.hasFocus)return;var e=t.find(this.items,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){t.invoke(this.items,"remove"),r.editors.Base.prototype.remove.call(this)},validate:function(){if(!this.validators)return null;var e=t.map(this.items,function(e){return e.validate()}),n=t.compact(e).length?!0:!1;if(!n)return null;var r={type:"list",message:"Some of the items in the list failed validation",errors:e};return r}},{template:t.template('
',null,r.templateSettings)}),r.editors.List.Item=r.editors.Base.extend({events:{'click [data-action="remove"]':function(e){e.preventDefault(),this.list.removeItem(this)},"keydown input[type=text]":function(e){if(e.keyCode!==13)return;e.preventDefault(),this.list.addItem(),this.list.$list.find("> li:last input").focus()}},initialize:function(e){this.list=e.list,this.schema=e.schema||this.list.schema,this.value=e.value,this.Editor=e.Editor||r.editors.Text,this.key=e.key,this.template=e.template||this.schema.itemTemplate||this.constructor.template,this.errorClassName=e.errorClassName||this.constructor.errorClassName,this.form=e.form},render:function(){this.editor=(new this.Editor({key:this.key,schema:this.schema,value:this.value,list:this.list,item:this,form:this.form})).render();var t=e(e.trim(this.template()));return t.find("[data-editor]").append(this.editor.el),this.setElement(t),this},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),n.View.prototype.remove.call(this)},validate:function(){var e=this.getValue(),n=this.list.form?this.list.form.getValue():{},r=this.schema.validators,i=this.getValidator;if(!r)return null;var s=null;return t.every(r,function(t){return s=i(t)(e,n),s?!1:!0}),s?this.setError(s):this.clearError(),s?s:null},setError:function(e){this.$el.addClass(this.errorClassName),this.$el.attr("title",e.message)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$el.attr("title",null)}},{template:t.template('
',null,r.templateSettings),errorClassName:"error"}),r.editors.List.Modal=r.editors.Base.extend({events:{click:"openEditor"},initialize:function(e){e=e||{},r.editors.Base.prototype.initialize.call(this,e);if(!r.editors.List.Modal.ModalAdapter)throw new Error("A ModalAdapter is required");this.form=e.form;if(!e.form)throw new Error('Missing required option: "form"');this.template=e.template||this.constructor.template},render:function(){var e=this;return t.isEmpty(this.value)?this.openEditor():(this.renderSummary(),setTimeout(function(){e.trigger("readyToAdd")},0)),this.hasFocus&&this.trigger("blur",this),this},renderSummary:function(){this.$el.html(e.trim(this.template({summary:this.getStringValue()})))},itemToString:function(e){var n=function(e){var t={key:e};return r.Field.prototype.createTitle.call(t)};e=e||{};var i=[];return t.each(this.nestedSchema,function(r,s){var o=r.title?r.title:n(s),u=e[s];if(t.isUndefined(u)||t.isNull(u))u="";i.push(o+": "+u)}),i.join("
")},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?"[Empty]":e.itemToString?e.itemToString(n):this.itemToString(n)},openEditor:function(){var e=this,n=this.form.constructor,i=this.modalForm=new n({schema:this.nestedSchema,data:this.value}),s=this.modal=new r.editors.List.Modal.ModalAdapter({content:i,animate:!0});s.open(),this.trigger("open",this),this.trigger("focus",this),s.on("cancel",this.onModalClosed,this),s.on("ok",t.bind(this.onModalSubmitted,this))},onModalSubmitted:function(){var e=this.modal,t=this.modalForm,n=!this.value,r=t.validate();if(r)return e.preventClose();this.value=t.getValue(),this.renderSummary(),n&&this.trigger("readyToAdd"),this.trigger("change",this),this.onModalClosed()},onModalClosed:function(){this.modal=null,this.modalForm=null,this.trigger("close",this),this.trigger("blur",this)},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){if(this.hasFocus)return;this.openEditor()},blur:function(){if(!this.hasFocus)return;this.modal&&this.modal.trigger("cancel")}},{template:t.template("
<%= summary %>
",null,r.templateSettings),ModalAdapter:n.BootstrapModal,isAsync:!0}),r.editors.List.Object=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.initialize.apply(this,arguments);var e=this.schema;if(!e.subSchema)throw new Error('Missing required option "schema.subSchema"');this.nestedSchema=e.subSchema}}),r.editors.List.NestedModel=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.initialize.apply(this,arguments);var e=this.schema;if(!e.model)throw new Error('Missing required option "schema.model"');var n=e.model.prototype.schema;this.nestedSchema=t.isFunction(n)?n():n},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?null:e.itemToString?e.itemToString(n):(new e.model(n)).toString()}})})(n.Form)}) \ No newline at end of file diff --git a/distribution/editors/list.js b/distribution/editors/list.js index 36509bc4..150a2872 100644 --- a/distribution/editors/list.js +++ b/distribution/editors/list.js @@ -2,7 +2,7 @@ /** * List editor - * + * * An array editor. Creates a list of other editor items. * * Special options: @@ -48,15 +48,34 @@ }, render: function() { - var self = this, - value = this.value || []; - //Create main element var $el = $($.trim(this.template())); //Store a reference to the list (item container) this.$list = $el.is('[data-items]') ? $el : $el.find('[data-items]'); + this.renderItems(); + + this.setElement($el); + this.$el.attr('id', this.id); + this.$el.attr('name', this.key); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + renderItems: function() { + var self = this, + value = this.value || []; + + // Easier to just empty the $list but we could potentially have default items + // for a custom template. + _.each(this.items, function(item) { + item.$el.remove(); + }); + this.items = []; + //Add existing items if (value.length) { _.each(value, function(itemValue) { @@ -68,14 +87,6 @@ else { if (!this.Editor.isAsync) this.addItem(); } - - this.setElement($el); - this.$el.attr('id', this.id); - this.$el.attr('name', this.key); - - if (this.hasFocus) this.trigger('blur', this); - - return this; }, /** @@ -96,11 +107,11 @@ Editor: this.Editor, key: this.key }).render(); - + var _addItem = function() { self.items.push(item); self.$list.append(item.el); - + item.editor.on('all', function(event) { if (event === 'change') return; @@ -134,11 +145,11 @@ self.trigger('blur', self); }, 0); }, self); - + if (userInitiated || value) { item.addEventTriggered = true; } - + if (userInitiated) { self.trigger('add', self, item.editor); self.trigger('change', self); @@ -155,7 +166,7 @@ _addItem(); item.editor.focus(); } - + return item; }, @@ -172,7 +183,7 @@ this.items[index].remove(); this.items.splice(index, 1); - + if (item.addEventTriggered) { this.trigger('remove', this, item.editor); this.trigger('change', this); @@ -192,20 +203,20 @@ setValue: function(value) { this.value = value; - this.render(); + this.renderItems(); }, - + focus: function() { if (this.hasFocus) return; if (this.items[0]) this.items[0].editor.focus(); }, - + blur: function() { if (!this.hasFocus) return; var focusedItem = _.find(this.items, function(item) { return item.editor.hasFocus; }); - + if (focusedItem) focusedItem.editor.blur(); }, @@ -217,10 +228,10 @@ Form.editors.Base.prototype.remove.call(this); }, - + /** * Run validation - * + * * @return {Object|Null} */ validate: function() { @@ -310,7 +321,7 @@ //Replace the entire element so there isn't a wrapper tag this.setElement($el); - + return this; }, @@ -321,11 +332,11 @@ setValue: function(value) { this.editor.setValue(value); }, - + focus: function() { this.editor.focus(); }, - + blur: function() { this.editor.blur(); }, @@ -394,7 +405,7 @@ /** - * Base modal object editor for use with the List editor; used by Object + * Base modal object editor for use with the List editor; used by Object * and NestedModal list types */ Form.editors.List.Modal = Form.editors.Base.extend({ @@ -413,9 +424,9 @@ */ initialize: function(options) { options = options || {}; - + Form.editors.Base.prototype.initialize.call(this, options); - + //Dependencies if (!Form.editors.List.Modal.ModalAdapter) throw new Error('A ModalAdapter is required'); @@ -464,7 +475,7 @@ * Function which returns a generic string representation of an object * * @param {Object} value - * + * * @return {String} */ itemToString: function(value) { @@ -501,7 +512,7 @@ //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the generic method or custom overridden method return this.itemToString(value); }, @@ -526,7 +537,7 @@ this.trigger('focus', this); modal.on('cancel', this.onModalClosed, this); - + modal.on('ok', _.bind(this.onModalSubmitted, this)); }, @@ -550,7 +561,7 @@ this.renderSummary(); if (isNew) this.trigger('readyToAdd'); - + this.trigger('change', this); this.onModalClosed(); @@ -574,16 +585,16 @@ setValue: function(value) { this.value = value; }, - + focus: function() { if (this.hasFocus) return; this.openEditor(); }, - + blur: function() { if (!this.hasFocus) return; - + if (this.modal) { this.modal.trigger('cancel'); } @@ -598,7 +609,7 @@ //Defaults to BootstrapModal (http://github.com/powmedia/backbone.bootstrap-modal) //Can be replaced with another adapter that implements the same interface. ModalAdapter: Backbone.BootstrapModal, - + //Make the wait list for the 'ready' event before adding the item to the list isAsync: true }); @@ -641,7 +652,7 @@ //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the model return new (schema.model)(value).toString(); } diff --git a/distribution/editors/list.min.js b/distribution/editors/list.min.js index 2411b129..c32b8915 100644 --- a/distribution/editors/list.min.js +++ b/distribution/editors/list.min.js @@ -1 +1 @@ -(function(e){e.editors.List=e.editors.Base.extend({events:{'click [data-action="add"]':function(e){e.preventDefault(),this.addItem(null,!0)}},initialize:function(t){t=t||{};var n=e.editors;n.Base.prototype.initialize.call(this,t);var r=this.schema;if(!r)throw new Error("Missing required option 'schema'");this.template=t.template||this.constructor.template,this.Editor=function(){var e=r.itemType;return e?n.List[e]?n.List[e]:n[e]:n.Text}(),this.items=[]},render:function(){var e=this,t=this.value||[],n=$($.trim(this.template()));return this.$list=n.is("[data-items]")?n:n.find("[data-items]"),t.length?_.each(t,function(t){e.addItem(t)}):this.Editor.isAsync||this.addItem(),this.setElement(n),this.$el.attr("id",this.id),this.$el.attr("name",this.key),this.hasFocus&&this.trigger("blur",this),this},addItem:function(t,n){var r=this,i=e.editors,s=(new i.List.Item({list:this,form:this.form,schema:this.schema,value:t,Editor:this.Editor,key:this.key})).render(),o=function(){r.items.push(s),r.$list.append(s.el),s.editor.on("all",function(e){if(e==="change")return;var t=_.toArray(arguments);t[0]="item:"+e,t.splice(1,0,r),i.List.prototype.trigger.apply(this,t)},r),s.editor.on("change",function(){s.addEventTriggered||(s.addEventTriggered=!0,this.trigger("add",this,s.editor)),this.trigger("item:change",this,s.editor),this.trigger("change",this)},r),s.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},r),s.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(_.find(e.items,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},r);if(n||t)s.addEventTriggered=!0;n&&(r.trigger("add",r,s.editor),r.trigger("change",r))};return this.Editor.isAsync?s.editor.on("readyToAdd",o,this):(o(),s.editor.focus()),s},removeItem:function(e){var t=this.schema.confirmDelete;if(t&&!confirm(t))return;var n=_.indexOf(this.items,e);this.items[n].remove(),this.items.splice(n,1),e.addEventTriggered&&(this.trigger("remove",this,e.editor),this.trigger("change",this)),!this.items.length&&!this.Editor.isAsync&&this.addItem()},getValue:function(){var e=_.map(this.items,function(e){return e.getValue()});return _.without(e,undefined,"")},setValue:function(e){this.value=e,this.render()},focus:function(){if(this.hasFocus)return;this.items[0]&&this.items[0].editor.focus()},blur:function(){if(!this.hasFocus)return;var e=_.find(this.items,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){_.invoke(this.items,"remove"),e.editors.Base.prototype.remove.call(this)},validate:function(){if(!this.validators)return null;var e=_.map(this.items,function(e){return e.validate()}),t=_.compact(e).length?!0:!1;if(!t)return null;var n={type:"list",message:"Some of the items in the list failed validation",errors:e};return n}},{template:_.template('
',null,e.templateSettings)}),e.editors.List.Item=e.editors.Base.extend({events:{'click [data-action="remove"]':function(e){e.preventDefault(),this.list.removeItem(this)},"keydown input[type=text]":function(e){if(e.keyCode!==13)return;e.preventDefault(),this.list.addItem(),this.list.$list.find("> li:last input").focus()}},initialize:function(t){this.list=t.list,this.schema=t.schema||this.list.schema,this.value=t.value,this.Editor=t.Editor||e.editors.Text,this.key=t.key,this.template=t.template||this.schema.itemTemplate||this.constructor.template,this.errorClassName=t.errorClassName||this.constructor.errorClassName,this.form=t.form},render:function(){this.editor=(new this.Editor({key:this.key,schema:this.schema,value:this.value,list:this.list,item:this,form:this.form})).render();var e=$($.trim(this.template()));return e.find("[data-editor]").append(this.editor.el),this.setElement(e),this},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){var e=this.getValue(),t=this.list.form?this.list.form.getValue():{},n=this.schema.validators,r=this.getValidator;if(!n)return null;var i=null;return _.every(n,function(n){return i=r(n)(e,t),i?!1:!0}),i?this.setError(i):this.clearError(),i?i:null},setError:function(e){this.$el.addClass(this.errorClassName),this.$el.attr("title",e.message)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$el.attr("title",null)}},{template:_.template('
',null,e.templateSettings),errorClassName:"error"}),e.editors.List.Modal=e.editors.Base.extend({events:{click:"openEditor"},initialize:function(t){t=t||{},e.editors.Base.prototype.initialize.call(this,t);if(!e.editors.List.Modal.ModalAdapter)throw new Error("A ModalAdapter is required");this.form=t.form;if(!t.form)throw new Error('Missing required option: "form"');this.template=t.template||this.constructor.template},render:function(){var e=this;return _.isEmpty(this.value)?this.openEditor():(this.renderSummary(),setTimeout(function(){e.trigger("readyToAdd")},0)),this.hasFocus&&this.trigger("blur",this),this},renderSummary:function(){this.$el.html($.trim(this.template({summary:this.getStringValue()})))},itemToString:function(t){var n=function(t){var n={key:t};return e.Field.prototype.createTitle.call(n)};t=t||{};var r=[];return _.each(this.nestedSchema,function(e,i){var s=e.title?e.title:n(i),o=t[i];if(_.isUndefined(o)||_.isNull(o))o="";r.push(s+": "+o)}),r.join("
")},getStringValue:function(){var e=this.schema,t=this.getValue();return _.isEmpty(t)?"[Empty]":e.itemToString?e.itemToString(t):this.itemToString(t)},openEditor:function(){var t=this,n=this.form.constructor,r=this.modalForm=new n({schema:this.nestedSchema,data:this.value}),i=this.modal=new e.editors.List.Modal.ModalAdapter({content:r,animate:!0});i.open(),this.trigger("open",this),this.trigger("focus",this),i.on("cancel",this.onModalClosed,this),i.on("ok",_.bind(this.onModalSubmitted,this))},onModalSubmitted:function(){var e=this.modal,t=this.modalForm,n=!this.value,r=t.validate();if(r)return e.preventClose();this.value=t.getValue(),this.renderSummary(),n&&this.trigger("readyToAdd"),this.trigger("change",this),this.onModalClosed()},onModalClosed:function(){this.modal=null,this.modalForm=null,this.trigger("close",this),this.trigger("blur",this)},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){if(this.hasFocus)return;this.openEditor()},blur:function(){if(!this.hasFocus)return;this.modal&&this.modal.trigger("cancel")}},{template:_.template("
<%= summary %>
",null,e.templateSettings),ModalAdapter:Backbone.BootstrapModal,isAsync:!0}),e.editors.List.Object=e.editors.List.Modal.extend({initialize:function(){e.editors.List.Modal.prototype.initialize.apply(this,arguments);var t=this.schema;if(!t.subSchema)throw new Error('Missing required option "schema.subSchema"');this.nestedSchema=t.subSchema}}),e.editors.List.NestedModel=e.editors.List.Modal.extend({initialize:function(){e.editors.List.Modal.prototype.initialize.apply(this,arguments);var t=this.schema;if(!t.model)throw new Error('Missing required option "schema.model"');var n=t.model.prototype.schema;this.nestedSchema=_.isFunction(n)?n():n},getStringValue:function(){var e=this.schema,t=this.getValue();return _.isEmpty(t)?null:e.itemToString?e.itemToString(t):(new e.model(t)).toString()}})})(Backbone.Form) \ No newline at end of file +(function(e){e.editors.List=e.editors.Base.extend({events:{'click [data-action="add"]':function(e){e.preventDefault(),this.addItem(null,!0)}},initialize:function(t){t=t||{};var n=e.editors;n.Base.prototype.initialize.call(this,t);var r=this.schema;if(!r)throw new Error("Missing required option 'schema'");this.template=t.template||this.constructor.template,this.Editor=function(){var e=r.itemType;return e?n.List[e]?n.List[e]:n[e]:n.Text}(),this.items=[]},render:function(){var e=$($.trim(this.template()));return this.$list=e.is("[data-items]")?e:e.find("[data-items]"),this.renderItems(),this.setElement(e),this.$el.attr("id",this.id),this.$el.attr("name",this.key),this.hasFocus&&this.trigger("blur",this),this},renderItems:function(){var e=this,t=this.value||[];_.each(this.items,function(e){e.$el.remove()}),this.items=[],t.length?_.each(t,function(t){e.addItem(t)}):this.Editor.isAsync||this.addItem()},addItem:function(t,n){var r=this,i=e.editors,s=(new i.List.Item({list:this,form:this.form,schema:this.schema,value:t,Editor:this.Editor,key:this.key})).render(),o=function(){r.items.push(s),r.$list.append(s.el),s.editor.on("all",function(e){if(e==="change")return;var t=_.toArray(arguments);t[0]="item:"+e,t.splice(1,0,r),i.List.prototype.trigger.apply(this,t)},r),s.editor.on("change",function(){s.addEventTriggered||(s.addEventTriggered=!0,this.trigger("add",this,s.editor)),this.trigger("item:change",this,s.editor),this.trigger("change",this)},r),s.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},r),s.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(_.find(e.items,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},r);if(n||t)s.addEventTriggered=!0;n&&(r.trigger("add",r,s.editor),r.trigger("change",r))};return this.Editor.isAsync?s.editor.on("readyToAdd",o,this):(o(),s.editor.focus()),s},removeItem:function(e){var t=this.schema.confirmDelete;if(t&&!confirm(t))return;var n=_.indexOf(this.items,e);this.items[n].remove(),this.items.splice(n,1),e.addEventTriggered&&(this.trigger("remove",this,e.editor),this.trigger("change",this)),!this.items.length&&!this.Editor.isAsync&&this.addItem()},getValue:function(){var e=_.map(this.items,function(e){return e.getValue()});return _.without(e,undefined,"")},setValue:function(e){this.value=e,this.renderItems()},focus:function(){if(this.hasFocus)return;this.items[0]&&this.items[0].editor.focus()},blur:function(){if(!this.hasFocus)return;var e=_.find(this.items,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){_.invoke(this.items,"remove"),e.editors.Base.prototype.remove.call(this)},validate:function(){if(!this.validators)return null;var e=_.map(this.items,function(e){return e.validate()}),t=_.compact(e).length?!0:!1;if(!t)return null;var n={type:"list",message:"Some of the items in the list failed validation",errors:e};return n}},{template:_.template('
',null,e.templateSettings)}),e.editors.List.Item=e.editors.Base.extend({events:{'click [data-action="remove"]':function(e){e.preventDefault(),this.list.removeItem(this)},"keydown input[type=text]":function(e){if(e.keyCode!==13)return;e.preventDefault(),this.list.addItem(),this.list.$list.find("> li:last input").focus()}},initialize:function(t){this.list=t.list,this.schema=t.schema||this.list.schema,this.value=t.value,this.Editor=t.Editor||e.editors.Text,this.key=t.key,this.template=t.template||this.schema.itemTemplate||this.constructor.template,this.errorClassName=t.errorClassName||this.constructor.errorClassName,this.form=t.form},render:function(){this.editor=(new this.Editor({key:this.key,schema:this.schema,value:this.value,list:this.list,item:this,form:this.form})).render();var e=$($.trim(this.template()));return e.find("[data-editor]").append(this.editor.el),this.setElement(e),this},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){var e=this.getValue(),t=this.list.form?this.list.form.getValue():{},n=this.schema.validators,r=this.getValidator;if(!n)return null;var i=null;return _.every(n,function(n){return i=r(n)(e,t),i?!1:!0}),i?this.setError(i):this.clearError(),i?i:null},setError:function(e){this.$el.addClass(this.errorClassName),this.$el.attr("title",e.message)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$el.attr("title",null)}},{template:_.template('
',null,e.templateSettings),errorClassName:"error"}),e.editors.List.Modal=e.editors.Base.extend({events:{click:"openEditor"},initialize:function(t){t=t||{},e.editors.Base.prototype.initialize.call(this,t);if(!e.editors.List.Modal.ModalAdapter)throw new Error("A ModalAdapter is required");this.form=t.form;if(!t.form)throw new Error('Missing required option: "form"');this.template=t.template||this.constructor.template},render:function(){var e=this;return _.isEmpty(this.value)?this.openEditor():(this.renderSummary(),setTimeout(function(){e.trigger("readyToAdd")},0)),this.hasFocus&&this.trigger("blur",this),this},renderSummary:function(){this.$el.html($.trim(this.template({summary:this.getStringValue()})))},itemToString:function(t){var n=function(t){var n={key:t};return e.Field.prototype.createTitle.call(n)};t=t||{};var r=[];return _.each(this.nestedSchema,function(e,i){var s=e.title?e.title:n(i),o=t[i];if(_.isUndefined(o)||_.isNull(o))o="";r.push(s+": "+o)}),r.join("
")},getStringValue:function(){var e=this.schema,t=this.getValue();return _.isEmpty(t)?"[Empty]":e.itemToString?e.itemToString(t):this.itemToString(t)},openEditor:function(){var t=this,n=this.form.constructor,r=this.modalForm=new n({schema:this.nestedSchema,data:this.value}),i=this.modal=new e.editors.List.Modal.ModalAdapter({content:r,animate:!0});i.open(),this.trigger("open",this),this.trigger("focus",this),i.on("cancel",this.onModalClosed,this),i.on("ok",_.bind(this.onModalSubmitted,this))},onModalSubmitted:function(){var e=this.modal,t=this.modalForm,n=!this.value,r=t.validate();if(r)return e.preventClose();this.value=t.getValue(),this.renderSummary(),n&&this.trigger("readyToAdd"),this.trigger("change",this),this.onModalClosed()},onModalClosed:function(){this.modal=null,this.modalForm=null,this.trigger("close",this),this.trigger("blur",this)},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){if(this.hasFocus)return;this.openEditor()},blur:function(){if(!this.hasFocus)return;this.modal&&this.modal.trigger("cancel")}},{template:_.template("
<%= summary %>
",null,e.templateSettings),ModalAdapter:Backbone.BootstrapModal,isAsync:!0}),e.editors.List.Object=e.editors.List.Modal.extend({initialize:function(){e.editors.List.Modal.prototype.initialize.apply(this,arguments);var t=this.schema;if(!t.subSchema)throw new Error('Missing required option "schema.subSchema"');this.nestedSchema=t.subSchema}}),e.editors.List.NestedModel=e.editors.List.Modal.extend({initialize:function(){e.editors.List.Modal.prototype.initialize.apply(this,arguments);var t=this.schema;if(!t.model)throw new Error('Missing required option "schema.model"');var n=t.model.prototype.schema;this.nestedSchema=_.isFunction(n)?n():n},getStringValue:function(){var e=this.schema,t=this.getValue();return _.isEmpty(t)?null:e.itemToString?e.itemToString(t):(new e.model(t)).toString()}})})(Backbone.Form) \ No newline at end of file diff --git a/src/editors/extra/list.js b/src/editors/extra/list.js index 36509bc4..150a2872 100644 --- a/src/editors/extra/list.js +++ b/src/editors/extra/list.js @@ -2,7 +2,7 @@ /** * List editor - * + * * An array editor. Creates a list of other editor items. * * Special options: @@ -48,15 +48,34 @@ }, render: function() { - var self = this, - value = this.value || []; - //Create main element var $el = $($.trim(this.template())); //Store a reference to the list (item container) this.$list = $el.is('[data-items]') ? $el : $el.find('[data-items]'); + this.renderItems(); + + this.setElement($el); + this.$el.attr('id', this.id); + this.$el.attr('name', this.key); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + renderItems: function() { + var self = this, + value = this.value || []; + + // Easier to just empty the $list but we could potentially have default items + // for a custom template. + _.each(this.items, function(item) { + item.$el.remove(); + }); + this.items = []; + //Add existing items if (value.length) { _.each(value, function(itemValue) { @@ -68,14 +87,6 @@ else { if (!this.Editor.isAsync) this.addItem(); } - - this.setElement($el); - this.$el.attr('id', this.id); - this.$el.attr('name', this.key); - - if (this.hasFocus) this.trigger('blur', this); - - return this; }, /** @@ -96,11 +107,11 @@ Editor: this.Editor, key: this.key }).render(); - + var _addItem = function() { self.items.push(item); self.$list.append(item.el); - + item.editor.on('all', function(event) { if (event === 'change') return; @@ -134,11 +145,11 @@ self.trigger('blur', self); }, 0); }, self); - + if (userInitiated || value) { item.addEventTriggered = true; } - + if (userInitiated) { self.trigger('add', self, item.editor); self.trigger('change', self); @@ -155,7 +166,7 @@ _addItem(); item.editor.focus(); } - + return item; }, @@ -172,7 +183,7 @@ this.items[index].remove(); this.items.splice(index, 1); - + if (item.addEventTriggered) { this.trigger('remove', this, item.editor); this.trigger('change', this); @@ -192,20 +203,20 @@ setValue: function(value) { this.value = value; - this.render(); + this.renderItems(); }, - + focus: function() { if (this.hasFocus) return; if (this.items[0]) this.items[0].editor.focus(); }, - + blur: function() { if (!this.hasFocus) return; var focusedItem = _.find(this.items, function(item) { return item.editor.hasFocus; }); - + if (focusedItem) focusedItem.editor.blur(); }, @@ -217,10 +228,10 @@ Form.editors.Base.prototype.remove.call(this); }, - + /** * Run validation - * + * * @return {Object|Null} */ validate: function() { @@ -310,7 +321,7 @@ //Replace the entire element so there isn't a wrapper tag this.setElement($el); - + return this; }, @@ -321,11 +332,11 @@ setValue: function(value) { this.editor.setValue(value); }, - + focus: function() { this.editor.focus(); }, - + blur: function() { this.editor.blur(); }, @@ -394,7 +405,7 @@ /** - * Base modal object editor for use with the List editor; used by Object + * Base modal object editor for use with the List editor; used by Object * and NestedModal list types */ Form.editors.List.Modal = Form.editors.Base.extend({ @@ -413,9 +424,9 @@ */ initialize: function(options) { options = options || {}; - + Form.editors.Base.prototype.initialize.call(this, options); - + //Dependencies if (!Form.editors.List.Modal.ModalAdapter) throw new Error('A ModalAdapter is required'); @@ -464,7 +475,7 @@ * Function which returns a generic string representation of an object * * @param {Object} value - * + * * @return {String} */ itemToString: function(value) { @@ -501,7 +512,7 @@ //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the generic method or custom overridden method return this.itemToString(value); }, @@ -526,7 +537,7 @@ this.trigger('focus', this); modal.on('cancel', this.onModalClosed, this); - + modal.on('ok', _.bind(this.onModalSubmitted, this)); }, @@ -550,7 +561,7 @@ this.renderSummary(); if (isNew) this.trigger('readyToAdd'); - + this.trigger('change', this); this.onModalClosed(); @@ -574,16 +585,16 @@ setValue: function(value) { this.value = value; }, - + focus: function() { if (this.hasFocus) return; this.openEditor(); }, - + blur: function() { if (!this.hasFocus) return; - + if (this.modal) { this.modal.trigger('cancel'); } @@ -598,7 +609,7 @@ //Defaults to BootstrapModal (http://github.com/powmedia/backbone.bootstrap-modal) //Can be replaced with another adapter that implements the same interface. ModalAdapter: Backbone.BootstrapModal, - + //Make the wait list for the 'ready' event before adding the item to the list isAsync: true }); @@ -641,7 +652,7 @@ //If there's a specified toString use that if (schema.itemToString) return schema.itemToString(value); - + //Otherwise use the model return new (schema.model)(value).toString(); }