From 5bb9ba2d2794d6da910ec5c04226636c8e3c6035 Mon Sep 17 00:00:00 2001 From: Charles Davison Date: Fri, 12 Oct 2012 05:03:35 +0100 Subject: [PATCH] Build --- README.md | 2 ++ distribution.amd/backbone-forms.js | 16 +++++++++++++--- distribution.amd/backbone-forms.min.js | 2 +- distribution/backbone-forms.js | 16 +++++++++++++--- distribution/backbone-forms.min.js | 2 +- test/index.html | 2 +- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 200062d7..8f3c35bd 100644 --- a/README.md +++ b/README.md @@ -892,6 +892,8 @@ Writing a custom editor is simple. They must extend from Backbone.Form.editors.B ##Changelog ###master +- Add Form.setValue(key, val) option for arguments (lennym) +- Support ordering years in descending order in Date field (lennym) - Allow the Number field type to accept decimal values (philfreo) - Added 'backbone-forms' as a dependency to the AMD wrapper for templates. (seanparmelee) - Allow use of required validator with checkbox fields (lennym) diff --git a/distribution.amd/backbone-forms.js b/distribution.amd/backbone-forms.js index 5f9db0ef..bb16f806 100644 --- a/distribution.amd/backbone-forms.js +++ b/distribution.amd/backbone-forms.js @@ -328,9 +328,16 @@ var Form = (function() { /** * Update field values, referenced by key - * @param {Object} data New values to set + * @param {Object|String} key New values to set, or property to set + * @param val Value to set */ - setValue: function(data) { + setValue: function(prop, val) { + var data = {}; + if (typeof prop === 'string') { + data[prop] = val; + } else { + data = prop; + } for (var key in data) { if (_.has(this.fields, key)) { this.fields[key].setValue(data[key]); @@ -1962,7 +1969,10 @@ Form.editors = (function() { return ''; }); - var yearsOptions = _.map(_.range(schema.yearStart, schema.yearEnd + 1), function(year) { + var yearRange = schema.yearStart < schema.yearEnd ? + _.range(schema.yearStart, schema.yearEnd + 1) : + _.range(schema.yearStart, schema.yearEnd - 1, -1); + var yearsOptions = _.map(yearRange, function(year) { return ''; }); diff --git a/distribution.amd/backbone-forms.min.js b/distribution.amd/backbone-forms.min.js index 7eeb917a..a430ca9c 100644 --- a/distribution.amd/backbone-forms.min.js +++ b/distribution.amd/backbone-forms.min.js @@ -1 +1 @@ -define(["jquery","underscore","backbone"],function($,_,Backbone){var Form=function(){return Backbone.View.extend({hasFocus:!1,initialize:function(options){if(!Form.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(options.schema)return options.schema;var model=options.model;if(!model)throw new Error("Could not find schema");return _.isFunction(model.schema)?model.schema():model.schema}(),options=_.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},options);if(!options.fieldsets){var fields=options.fields||_.keys(this.schema);options.fieldsets=[{fields:fields}]}this.options=options,this.model=options.model,this.data=options.data,this.fields={}},render:function(){var self=this,options=this.options,template=Form.templates[options.template],$form=$(template({fieldsets:''})),$fieldsetContainer=$(".bbf-tmp",$form);return _.each(options.fieldsets,function(fieldset){$fieldsetContainer.append(self.renderFieldset(fieldset))}),$fieldsetContainer.children().unwrap(),this.setElement($form),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(fieldset){var self=this,template=Form.templates[this.options.fieldsetTemplate],schema=this.schema,getNested=Form.helpers.getNested;_.isArray(fieldset)&&(fieldset={fields:fieldset});var $fieldset=$(template(_.extend({},fieldset,{legend:'',fields:''})));fieldset.legend?$fieldset.find(".bbf-tmp-legend").replaceWith(fieldset.legend):$fieldset.find(".bbf-tmp-legend").parent().remove();var $fieldsContainer=$(".bbf-tmp-fields",$fieldset);return _.each(fieldset.fields,function(key){var itemSchema=function(){if(schema[key])return schema[key];var path=key.replace(/\./g,".subSchema.");return getNested(schema,path)}();if(!itemSchema)throw"Field '"+key+"' not found in schema";var field=self.fields[key]=self.createField(key,itemSchema),fieldEl=field.render().el;field.editor.on("all",function(event){args=_.toArray(arguments),args[0]=key+":"+event,args.splice(1,0,this),this.trigger.apply(this,args)},self),field.editor.on("change",function(){this.trigger("change",self)},self),field.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},self),field.editor.on("blur",function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(_.find(self.fields,function(field){return field.editor.hasFocus}))return;self.trigger("blur",self)},0)},self),itemSchema.type!="Hidden"&&$fieldsContainer.append(fieldEl)}),$fieldsContainer=$fieldsContainer.children().unwrap(),$fieldset},createField:function(key,schema){schema.template=schema.template||this.options.fieldTemplate;var options={form:this,key:key,schema:schema,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?options.model=this.model:this.data?options.value=this.data[key]:options.value=null,new Form.Field(options)},validate:function(){var self=this,fields=this.fields,model=this.model,errors={};_.each(fields,function(field){var error=field.validate();error&&(errors[field.key]=error)});if(model&&model.validate){var modelErrors=model.validate(this.getValue());if(modelErrors){var isDictionary=_.isObject(modelErrors)&&!_.isArray(modelErrors);isDictionary||(errors._others=errors._others||[],errors._others.push(modelErrors)),isDictionary&&_.each(modelErrors,function(val,key){if(self.fields[key]&&!errors[key])self.fields[key].setError(val);else{errors._others=errors._others||[];var tmpErr={};tmpErr[key]=val,errors._others.push(tmpErr)}})}}return _.isEmpty(errors)?null:errors},commit:function(){var errors=this.validate();if(errors)return errors;var modelError;this.model.set(this.getValue(),{error:function(model,e){modelError=e}});if(modelError)return modelError},getValue:function(key){if(key)return this.fields[key].getValue();var values={};return _.each(this.fields,function(field){values[field.key]=field.getValue()}),values},setValue:function(data){for(var key in data)_.has(this.fields,key)&&this.fields[key].setValue(data[key])},focus:function(){if(this.hasFocus)return;var fieldset=this.options.fieldsets[0];if(fieldset){var field;_.isArray(fieldset)?field=fieldset[0]:field=fieldset.fields[0],field&&this.fields[field].editor.focus()}},blur:function(){if(!this.hasFocus)return;focusedField=_.find(this.fields,function(field){return field.editor.hasFocus}),focusedField&&focusedField.editor.blur()},remove:function(){var fields=this.fields;for(var key in fields)fields[key].remove();Backbone.View.prototype.remove.call(this)},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}})}();Form.helpers=function(){var helpers={};return helpers.getNested=function(obj,path){var fields=path.split("."),result=obj;for(var i=0,n=fields.length;i',help:''}},render:function(){var schema=this.schema,templates=Form.templates,options={form:this.form,key:this.key,schema:schema,idPrefix:this.options.idPrefix,id:this.getId()};this.model?options.model=this.model:options.value=this.value;var editor=this.editor=helpers.createEditor(schema.type,options),$field=$(templates[schema.template](this.renderingContext(schema,editor)));return $field.find(".bbf-tmp-editor").replaceWith(editor.render().el),this.$help=$(".bbf-tmp-help",$field).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.schema.fieldClass&&$field.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&$field.attr(this.schema.fieldAttrs),this.setElement($field),this},getId:function(){var prefix=this.options.idPrefix,id=this.key;return id=id.replace(/\./g,"_"),_.isString(prefix)||_.isNumber(prefix)?prefix+id:_.isNull(prefix)?id:this.model?this.model.cid+"_"+id:id},validate:function(){var error=this.editor.validate();return error?this.setError(error.message):this.clearError(),error},setError:function(msg){if(this.editor.hasNestedForm)return;var errClass=Form.classNames.error;this.$el.addClass(errClass),this.$help&&this.$help.html(msg)},clearError:function(){var errClass=Form.classNames.error;this.$el.removeClass(errClass);if(this.$help){this.$help.empty();var helpMsg=this.schema.help;helpMsg&&this.$help.html(helpMsg)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(value){this.editor.setValue(value)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)}})}(),Form.editors=function(){var helpers=Form.helpers,editors={};return editors.Base=Backbone.View.extend({defaultValue:null,hasFocus:!1,initialize:function(options){var options=options||{};if(options.model){if(!options.key)throw"Missing option: 'key'";this.model=options.model,this.value=this.model.get(options.key)}else options.value&&(this.value=options.value);this.value===undefined&&(this.value=this.defaultValue),this.key=options.key,this.form=options.form,this.schema=options.schema||{},this.validators=options.validators||this.schema.validators,this.$el.attr("name",this.getName()),this.schema.editorClass&&this.$el.addClass(this.schema.editorClass),this.schema.editorAttrs&&this.$el.attr(this.schema.editorAttrs)},getValue:function(){throw"Not implemented. Extend and override this method."},setValue:function(){throw"Not implemented. Extend and override this method."},focus:function(){throw"Not implemented. Extend and override this method."},blur:function(){throw"Not implemented. Extend and override this method."},getName:function(){var key=this.key||"";return key.replace(/\./g,"_")},commit:function(){var error=this.validate();if(error)return error;this.model.set(this.key,this.getValue(),{error:function(model,e){error=e}});if(error)return error},validate:function(){var $el=this.$el,error=null,value=this.getValue(),formValues=this.form?this.form.getValue():{},validators=this.validators,getValidator=Form.helpers.getValidator;return validators&&_.every(validators,function(validator){return error=getValidator(validator)(value,formValues),continueLoop=error?!1:!0}),error},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}}),editors.Text=editors.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(event){var self=this;setTimeout(function(){self.determineChange()},0)},select:function(event){this.trigger("select",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);var schema=this.schema,type="text";schema&&schema.editorAttrs&&schema.editorAttrs.type&&(type=schema.editorAttrs.type),schema&&schema.dataType&&(type=schema.dataType),this.$el.attr("type",type)},render:function(){return this.setValue(this.value),this},determineChange:function(event){var currentValue=this.$el.val(),changed=currentValue!=this.previousValue;changed&&(this.previousValue=currentValue,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),editors.Number=editors.Text.extend({defaultValue:0,events:_.extend({},editors.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(event){var self=this,delayedDetermineChange=function(){setTimeout(function(){self.determineChange()},0)};if(event.charCode==0){delayedDetermineChange();return}var newVal=this.$el.val()+String.fromCharCode(event.charCode),numeric=/^[0-9]*\.?[0-9]*?$/.test(newVal);numeric?delayedDetermineChange():event.preventDefault()},getValue:function(){var value=this.$el.val();return value===""?null:parseFloat(value,10)},setValue:function(value){value=function(){return _.isNumber(value)?value:_.isString(value)&&value!==""?parseFloat(value,10):null}(),_.isNaN(value)&&(value=null),editors.Text.prototype.setValue.call(this,value)}}),editors.Password=editors.Text.extend({initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","password")}}),editors.TextArea=editors.Text.extend({tagName:"textarea"}),editors.Checkbox=editors.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(value){value&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),editors.Hidden=editors.Base.extend({defaultValue:"",initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(value){this.value=value},focus:function(){},blur:function(){}}),editors.Select=editors.Base.extend({tagName:"select",events:{change:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(options){var self=this;if(options instanceof Backbone.Collection){var collection=options;collection.length>0?this.renderOptions(options):collection.fetch({success:function(collection){self.renderOptions(options)}})}else _.isFunction(options)?options(function(result){self.renderOptions(result)}):this.renderOptions(options)},renderOptions:function(options){var $select=this.$el,html;_.isString(options)?html=options:_.isArray(options)?html=this._arrayToHtml(options):options instanceof Backbone.Collection&&(html=this._collectionToHtml(options)),$select.html(html),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(collection){var array=[];collection.each(function(model){array.push({val:model.id,label:model.toString()})});var html=this._arrayToHtml(array);return html},_arrayToHtml:function(array){var html=[];return _.each(array,function(option){if(_.isObject(option)){var val=option.val?option.val:"";html.push('")}else html.push("")}),html.join("")}}),editors.Radio=editors.Select.extend({tagName:"ul",className:"bbf-radio",events:{"click input[type=radio]:not(:checked)":function(){this.trigger("change",this)},"focus input[type=radio]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=radio]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=radio]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(value){this.$("input[type=radio]").val([value])},focus:function(){if(this.hasFocus)return;var checked=this.$("input[type=radio]:checked");if(checked[0]){checked.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Checkboxes=editors.Select.extend({tagName:"ul",className:"bbf-checkboxes",events:{"click input[type=checkbox]":function(){this.trigger("change",this)},"focus input[type=checkbox]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=checkbox]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=checkbox]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){var values=[];return this.$("input[type=checkbox]:checked").each(function(){values.push($(this).val())}),values},setValue:function(values){_.isArray(values)||(values=[values]),this.$("input[type=checkbox]").val(values)},focus:function(){if(this.hasFocus)return;this.$("input[type=checkbox]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=checkbox]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Object=editors.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(options){this.value={},editors.Base.prototype.initialize.call(this,options);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new Form({schema:this.schema.subSchema,data:this.value,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){return this.form?this.form.getValue():this.value},setValue:function(value){this.value=value,this.render()},focus:function(){if(this.hasFocus)return;this.form.focus()},blur:function(){if(!this.hasFocus)return;this.form.blur()},remove:function(){this.form.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){args=_.toArray(arguments),args[1]=this,this.trigger.apply(this,args)},this)}}),editors.NestedModel=editors.Object.extend({initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!options.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var data=this.value||{},key=this.key,nestedModel=this.schema.model,modelInstance=data.constructor==nestedModel?data:new nestedModel(data);return this.form=new Form({model:modelInstance,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var error=this.form.commit();return error?(this.$el.addClass("error"),error):editors.Object.prototype.commit.call(this)}}),editors.Date=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options);var Self=editors.Date,today=new Date;this.options=_.extend({monthNames:Self.monthNames,showMonthNames:Self.showMonthNames},options),this.schema=_.extend({yearStart:today.getFullYear()-100,yearEnd:today.getFullYear()},options.schema||{}),this.value&&!_.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var date=new Date;date.setSeconds(0),date.setMilliseconds(0),this.value=date}},render:function(){var options=this.options,schema=this.schema,datesOptions=_.map(_.range(1,32),function(date){return'"}),monthsOptions=_.map(_.range(0,12),function(month){var value=options.showMonthNames?options.monthNames[month]:month+1;return'"}),yearsOptions=_.map(_.range(schema.yearStart,schema.yearEnd+1),function(year){return'"}),$el=$(Form.templates.date({dates:datesOptions.join(""),months:monthsOptions.join(""),years:yearsOptions.join("")}));return this.$date=$el.find('select[data-type="date"]'),this.$month=$el.find('select[data-type="month"]'),this.$year=$el.find('select[data-type="year"]'),this.$hidden=$(''),$el.append(this.$hidden),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var year=this.$year.val(),month=this.$month.val(),date=this.$date.val();return!year||!month||!date?null:new Date(year,month,date)},setValue:function(date){this.$date.val(date.getDate()),this.$month.val(date.getMonth()),this.$year.val(date.getFullYear()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),editors.DateTime=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options),this.options=_.extend({DateEditor:editors.DateTime.DateEditor},options),this.schema=_.extend({minsInterval:15},options.schema||{}),this.dateEditor=new this.options.DateEditor(options),this.value=this.dateEditor.value},render:function(){function pad(n){return n<10?"0"+n:n}var schema=this.schema,hoursOptions=_.map(_.range(0,24),function(hour){return'"}),minsOptions=_.map(_.range(0,60,schema.minsInterval),function(min){return'"}),$el=$(Form.templates.dateTime({date:'',hours:hoursOptions.join(),mins:minsOptions.join()}));return $el.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=$el.find('select[data-type="hour"]'),this.$min=$el.find('select[data-type="min"]'),this.$hidden=$el.find('input[type="hidden"]'),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var date=this.dateEditor.getValue(),hour=this.$hour.val(),min=this.$min.val();return!date||!hour||!min?null:(date.setHours(hour),date.setMinutes(min),date)},setValue:function(date){_.isDate(date)||(date=new Date(date)),this.dateEditor.setValue(date),this.$hour.val(date.getHours()),this.$min.val(date.getMinutes()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)},remove:function(){this.dateEditor.remove(),editors.Base.prototype.remove.call(this)}},{DateEditor:editors.Date}),editors}(),Form.setTemplates=Form.helpers.setTemplates,Form.setTemplateCompiler=Form.helpers.setTemplateCompiler,Form.templates={},Form.setTemplates({form:'
    {{fieldsets}}
    ',fieldset:"
    {{legend}}
      {{fields}}
    ",field:'
  • {{editor}}
    {{help}}
  • ',nestedField:'
  • {{editor}}
    {{help}}
  • ',list:'
      {{items}}
    ',listItem:'
  • {{editor}}
  • ',date:'
    ',dateTime:'
    {{date}}
    :
    ',"list.Modal":'
    {{summary}}
    '},{error:"bbf-error"}),Form.VERSION="0.10.1",Backbone.Form=Form}) \ No newline at end of file +define(["jquery","underscore","backbone"],function($,_,Backbone){var Form=function(){return Backbone.View.extend({hasFocus:!1,initialize:function(options){if(!Form.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(options.schema)return options.schema;var model=options.model;if(!model)throw new Error("Could not find schema");return _.isFunction(model.schema)?model.schema():model.schema}(),options=_.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},options);if(!options.fieldsets){var fields=options.fields||_.keys(this.schema);options.fieldsets=[{fields:fields}]}this.options=options,this.model=options.model,this.data=options.data,this.fields={}},render:function(){var self=this,options=this.options,template=Form.templates[options.template],$form=$(template({fieldsets:''})),$fieldsetContainer=$(".bbf-tmp",$form);return _.each(options.fieldsets,function(fieldset){$fieldsetContainer.append(self.renderFieldset(fieldset))}),$fieldsetContainer.children().unwrap(),this.setElement($form),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(fieldset){var self=this,template=Form.templates[this.options.fieldsetTemplate],schema=this.schema,getNested=Form.helpers.getNested;_.isArray(fieldset)&&(fieldset={fields:fieldset});var $fieldset=$(template(_.extend({},fieldset,{legend:'',fields:''})));fieldset.legend?$fieldset.find(".bbf-tmp-legend").replaceWith(fieldset.legend):$fieldset.find(".bbf-tmp-legend").parent().remove();var $fieldsContainer=$(".bbf-tmp-fields",$fieldset);return _.each(fieldset.fields,function(key){var itemSchema=function(){if(schema[key])return schema[key];var path=key.replace(/\./g,".subSchema.");return getNested(schema,path)}();if(!itemSchema)throw"Field '"+key+"' not found in schema";var field=self.fields[key]=self.createField(key,itemSchema),fieldEl=field.render().el;field.editor.on("all",function(event){args=_.toArray(arguments),args[0]=key+":"+event,args.splice(1,0,this),this.trigger.apply(this,args)},self),field.editor.on("change",function(){this.trigger("change",self)},self),field.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},self),field.editor.on("blur",function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(_.find(self.fields,function(field){return field.editor.hasFocus}))return;self.trigger("blur",self)},0)},self),itemSchema.type!="Hidden"&&$fieldsContainer.append(fieldEl)}),$fieldsContainer=$fieldsContainer.children().unwrap(),$fieldset},createField:function(key,schema){schema.template=schema.template||this.options.fieldTemplate;var options={form:this,key:key,schema:schema,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?options.model=this.model:this.data?options.value=this.data[key]:options.value=null,new Form.Field(options)},validate:function(){var self=this,fields=this.fields,model=this.model,errors={};_.each(fields,function(field){var error=field.validate();error&&(errors[field.key]=error)});if(model&&model.validate){var modelErrors=model.validate(this.getValue());if(modelErrors){var isDictionary=_.isObject(modelErrors)&&!_.isArray(modelErrors);isDictionary||(errors._others=errors._others||[],errors._others.push(modelErrors)),isDictionary&&_.each(modelErrors,function(val,key){if(self.fields[key]&&!errors[key])self.fields[key].setError(val);else{errors._others=errors._others||[];var tmpErr={};tmpErr[key]=val,errors._others.push(tmpErr)}})}}return _.isEmpty(errors)?null:errors},commit:function(){var errors=this.validate();if(errors)return errors;var modelError;this.model.set(this.getValue(),{error:function(model,e){modelError=e}});if(modelError)return modelError},getValue:function(key){if(key)return this.fields[key].getValue();var values={};return _.each(this.fields,function(field){values[field.key]=field.getValue()}),values},setValue:function(prop,val){var data={};typeof prop=="string"?data[prop]=val:data=prop;for(var key in data)_.has(this.fields,key)&&this.fields[key].setValue(data[key])},focus:function(){if(this.hasFocus)return;var fieldset=this.options.fieldsets[0];if(fieldset){var field;_.isArray(fieldset)?field=fieldset[0]:field=fieldset.fields[0],field&&this.fields[field].editor.focus()}},blur:function(){if(!this.hasFocus)return;focusedField=_.find(this.fields,function(field){return field.editor.hasFocus}),focusedField&&focusedField.editor.blur()},remove:function(){var fields=this.fields;for(var key in fields)fields[key].remove();Backbone.View.prototype.remove.call(this)},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}})}();Form.helpers=function(){var helpers={};return helpers.getNested=function(obj,path){var fields=path.split("."),result=obj;for(var i=0,n=fields.length;i',help:''}},render:function(){var schema=this.schema,templates=Form.templates,options={form:this.form,key:this.key,schema:schema,idPrefix:this.options.idPrefix,id:this.getId()};this.model?options.model=this.model:options.value=this.value;var editor=this.editor=helpers.createEditor(schema.type,options),$field=$(templates[schema.template](this.renderingContext(schema,editor)));return $field.find(".bbf-tmp-editor").replaceWith(editor.render().el),this.$help=$(".bbf-tmp-help",$field).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.schema.fieldClass&&$field.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&$field.attr(this.schema.fieldAttrs),this.setElement($field),this},getId:function(){var prefix=this.options.idPrefix,id=this.key;return id=id.replace(/\./g,"_"),_.isString(prefix)||_.isNumber(prefix)?prefix+id:_.isNull(prefix)?id:this.model?this.model.cid+"_"+id:id},validate:function(){var error=this.editor.validate();return error?this.setError(error.message):this.clearError(),error},setError:function(msg){if(this.editor.hasNestedForm)return;var errClass=Form.classNames.error;this.$el.addClass(errClass),this.$help&&this.$help.html(msg)},clearError:function(){var errClass=Form.classNames.error;this.$el.removeClass(errClass);if(this.$help){this.$help.empty();var helpMsg=this.schema.help;helpMsg&&this.$help.html(helpMsg)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(value){this.editor.setValue(value)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)}})}(),Form.editors=function(){var helpers=Form.helpers,editors={};return editors.Base=Backbone.View.extend({defaultValue:null,hasFocus:!1,initialize:function(options){var options=options||{};if(options.model){if(!options.key)throw"Missing option: 'key'";this.model=options.model,this.value=this.model.get(options.key)}else options.value&&(this.value=options.value);this.value===undefined&&(this.value=this.defaultValue),this.key=options.key,this.form=options.form,this.schema=options.schema||{},this.validators=options.validators||this.schema.validators,this.$el.attr("name",this.getName()),this.schema.editorClass&&this.$el.addClass(this.schema.editorClass),this.schema.editorAttrs&&this.$el.attr(this.schema.editorAttrs)},getValue:function(){throw"Not implemented. Extend and override this method."},setValue:function(){throw"Not implemented. Extend and override this method."},focus:function(){throw"Not implemented. Extend and override this method."},blur:function(){throw"Not implemented. Extend and override this method."},getName:function(){var key=this.key||"";return key.replace(/\./g,"_")},commit:function(){var error=this.validate();if(error)return error;this.model.set(this.key,this.getValue(),{error:function(model,e){error=e}});if(error)return error},validate:function(){var $el=this.$el,error=null,value=this.getValue(),formValues=this.form?this.form.getValue():{},validators=this.validators,getValidator=Form.helpers.getValidator;return validators&&_.every(validators,function(validator){return error=getValidator(validator)(value,formValues),continueLoop=error?!1:!0}),error},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}}),editors.Text=editors.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(event){var self=this;setTimeout(function(){self.determineChange()},0)},select:function(event){this.trigger("select",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);var schema=this.schema,type="text";schema&&schema.editorAttrs&&schema.editorAttrs.type&&(type=schema.editorAttrs.type),schema&&schema.dataType&&(type=schema.dataType),this.$el.attr("type",type)},render:function(){return this.setValue(this.value),this},determineChange:function(event){var currentValue=this.$el.val(),changed=currentValue!=this.previousValue;changed&&(this.previousValue=currentValue,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),editors.Number=editors.Text.extend({defaultValue:0,events:_.extend({},editors.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(event){var self=this,delayedDetermineChange=function(){setTimeout(function(){self.determineChange()},0)};if(event.charCode==0){delayedDetermineChange();return}var newVal=this.$el.val()+String.fromCharCode(event.charCode),numeric=/^[0-9]*\.?[0-9]*?$/.test(newVal);numeric?delayedDetermineChange():event.preventDefault()},getValue:function(){var value=this.$el.val();return value===""?null:parseFloat(value,10)},setValue:function(value){value=function(){return _.isNumber(value)?value:_.isString(value)&&value!==""?parseFloat(value,10):null}(),_.isNaN(value)&&(value=null),editors.Text.prototype.setValue.call(this,value)}}),editors.Password=editors.Text.extend({initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","password")}}),editors.TextArea=editors.Text.extend({tagName:"textarea"}),editors.Checkbox=editors.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(value){value&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),editors.Hidden=editors.Base.extend({defaultValue:"",initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(value){this.value=value},focus:function(){},blur:function(){}}),editors.Select=editors.Base.extend({tagName:"select",events:{change:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(options){var self=this;if(options instanceof Backbone.Collection){var collection=options;collection.length>0?this.renderOptions(options):collection.fetch({success:function(collection){self.renderOptions(options)}})}else _.isFunction(options)?options(function(result){self.renderOptions(result)}):this.renderOptions(options)},renderOptions:function(options){var $select=this.$el,html;_.isString(options)?html=options:_.isArray(options)?html=this._arrayToHtml(options):options instanceof Backbone.Collection&&(html=this._collectionToHtml(options)),$select.html(html),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(collection){var array=[];collection.each(function(model){array.push({val:model.id,label:model.toString()})});var html=this._arrayToHtml(array);return html},_arrayToHtml:function(array){var html=[];return _.each(array,function(option){if(_.isObject(option)){var val=option.val?option.val:"";html.push('")}else html.push("")}),html.join("")}}),editors.Radio=editors.Select.extend({tagName:"ul",className:"bbf-radio",events:{"click input[type=radio]:not(:checked)":function(){this.trigger("change",this)},"focus input[type=radio]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=radio]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=radio]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(value){this.$("input[type=radio]").val([value])},focus:function(){if(this.hasFocus)return;var checked=this.$("input[type=radio]:checked");if(checked[0]){checked.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Checkboxes=editors.Select.extend({tagName:"ul",className:"bbf-checkboxes",events:{"click input[type=checkbox]":function(){this.trigger("change",this)},"focus input[type=checkbox]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=checkbox]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=checkbox]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){var values=[];return this.$("input[type=checkbox]:checked").each(function(){values.push($(this).val())}),values},setValue:function(values){_.isArray(values)||(values=[values]),this.$("input[type=checkbox]").val(values)},focus:function(){if(this.hasFocus)return;this.$("input[type=checkbox]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=checkbox]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Object=editors.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(options){this.value={},editors.Base.prototype.initialize.call(this,options);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new Form({schema:this.schema.subSchema,data:this.value,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){return this.form?this.form.getValue():this.value},setValue:function(value){this.value=value,this.render()},focus:function(){if(this.hasFocus)return;this.form.focus()},blur:function(){if(!this.hasFocus)return;this.form.blur()},remove:function(){this.form.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){args=_.toArray(arguments),args[1]=this,this.trigger.apply(this,args)},this)}}),editors.NestedModel=editors.Object.extend({initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!options.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var data=this.value||{},key=this.key,nestedModel=this.schema.model,modelInstance=data.constructor==nestedModel?data:new nestedModel(data);return this.form=new Form({model:modelInstance,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var error=this.form.commit();return error?(this.$el.addClass("error"),error):editors.Object.prototype.commit.call(this)}}),editors.Date=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options);var Self=editors.Date,today=new Date;this.options=_.extend({monthNames:Self.monthNames,showMonthNames:Self.showMonthNames},options),this.schema=_.extend({yearStart:today.getFullYear()-100,yearEnd:today.getFullYear()},options.schema||{}),this.value&&!_.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var date=new Date;date.setSeconds(0),date.setMilliseconds(0),this.value=date}},render:function(){var options=this.options,schema=this.schema,datesOptions=_.map(_.range(1,32),function(date){return'"}),monthsOptions=_.map(_.range(0,12),function(month){var value=options.showMonthNames?options.monthNames[month]:month+1;return'"}),yearRange=schema.yearStart'+year+""}),$el=$(Form.templates.date({dates:datesOptions.join(""),months:monthsOptions.join(""),years:yearsOptions.join("")}));return this.$date=$el.find('select[data-type="date"]'),this.$month=$el.find('select[data-type="month"]'),this.$year=$el.find('select[data-type="year"]'),this.$hidden=$(''),$el.append(this.$hidden),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var year=this.$year.val(),month=this.$month.val(),date=this.$date.val();return!year||!month||!date?null:new Date(year,month,date)},setValue:function(date){this.$date.val(date.getDate()),this.$month.val(date.getMonth()),this.$year.val(date.getFullYear()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),editors.DateTime=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options),this.options=_.extend({DateEditor:editors.DateTime.DateEditor},options),this.schema=_.extend({minsInterval:15},options.schema||{}),this.dateEditor=new this.options.DateEditor(options),this.value=this.dateEditor.value},render:function(){function pad(n){return n<10?"0"+n:n}var schema=this.schema,hoursOptions=_.map(_.range(0,24),function(hour){return'"}),minsOptions=_.map(_.range(0,60,schema.minsInterval),function(min){return'"}),$el=$(Form.templates.dateTime({date:'',hours:hoursOptions.join(),mins:minsOptions.join()}));return $el.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=$el.find('select[data-type="hour"]'),this.$min=$el.find('select[data-type="min"]'),this.$hidden=$el.find('input[type="hidden"]'),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var date=this.dateEditor.getValue(),hour=this.$hour.val(),min=this.$min.val();return!date||!hour||!min?null:(date.setHours(hour),date.setMinutes(min),date)},setValue:function(date){_.isDate(date)||(date=new Date(date)),this.dateEditor.setValue(date),this.$hour.val(date.getHours()),this.$min.val(date.getMinutes()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)},remove:function(){this.dateEditor.remove(),editors.Base.prototype.remove.call(this)}},{DateEditor:editors.Date}),editors}(),Form.setTemplates=Form.helpers.setTemplates,Form.setTemplateCompiler=Form.helpers.setTemplateCompiler,Form.templates={},Form.setTemplates({form:'
    {{fieldsets}}
    ',fieldset:"
    {{legend}}
      {{fields}}
    ",field:'
  • {{editor}}
    {{help}}
  • ',nestedField:'
  • {{editor}}
    {{help}}
  • ',list:'
      {{items}}
    ',listItem:'
  • {{editor}}
  • ',date:'
    ',dateTime:'
    {{date}}
    :
    ',"list.Modal":'
    {{summary}}
    '},{error:"bbf-error"}),Form.VERSION="0.10.1",Backbone.Form=Form}) \ No newline at end of file diff --git a/distribution/backbone-forms.js b/distribution/backbone-forms.js index 88120d93..6b385973 100644 --- a/distribution/backbone-forms.js +++ b/distribution/backbone-forms.js @@ -341,9 +341,16 @@ var Form = (function() { /** * Update field values, referenced by key - * @param {Object} data New values to set + * @param {Object|String} key New values to set, or property to set + * @param val Value to set */ - setValue: function(data) { + setValue: function(prop, val) { + var data = {}; + if (typeof prop === 'string') { + data[prop] = val; + } else { + data = prop; + } for (var key in data) { if (_.has(this.fields, key)) { this.fields[key].setValue(data[key]); @@ -1975,7 +1982,10 @@ Form.editors = (function() { return ''; }); - var yearsOptions = _.map(_.range(schema.yearStart, schema.yearEnd + 1), function(year) { + var yearRange = schema.yearStart < schema.yearEnd ? + _.range(schema.yearStart, schema.yearEnd + 1) : + _.range(schema.yearStart, schema.yearEnd - 1, -1); + var yearsOptions = _.map(yearRange, function(year) { return ''; }); diff --git a/distribution/backbone-forms.min.js b/distribution/backbone-forms.min.js index e7be01c0..fe9009c7 100644 --- a/distribution/backbone-forms.min.js +++ b/distribution/backbone-forms.min.js @@ -1 +1 @@ -(function(root){if(typeof exports!="undefined"&&typeof require!="undefined")var $=root.jQuery||root.Zepto||root.ender||require("jquery"),_=root._||require("underscore"),Backbone=root.Backbone||require("backbone");else var $=root.jQuery,_=root._,Backbone=root.Backbone;var Form=function(){return Backbone.View.extend({hasFocus:!1,initialize:function(options){if(!Form.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(options.schema)return options.schema;var model=options.model;if(!model)throw new Error("Could not find schema");return _.isFunction(model.schema)?model.schema():model.schema}(),options=_.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},options);if(!options.fieldsets){var fields=options.fields||_.keys(this.schema);options.fieldsets=[{fields:fields}]}this.options=options,this.model=options.model,this.data=options.data,this.fields={}},render:function(){var self=this,options=this.options,template=Form.templates[options.template],$form=$(template({fieldsets:''})),$fieldsetContainer=$(".bbf-tmp",$form);return _.each(options.fieldsets,function(fieldset){$fieldsetContainer.append(self.renderFieldset(fieldset))}),$fieldsetContainer.children().unwrap(),this.setElement($form),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(fieldset){var self=this,template=Form.templates[this.options.fieldsetTemplate],schema=this.schema,getNested=Form.helpers.getNested;_.isArray(fieldset)&&(fieldset={fields:fieldset});var $fieldset=$(template(_.extend({},fieldset,{legend:'',fields:''})));fieldset.legend?$fieldset.find(".bbf-tmp-legend").replaceWith(fieldset.legend):$fieldset.find(".bbf-tmp-legend").parent().remove();var $fieldsContainer=$(".bbf-tmp-fields",$fieldset);return _.each(fieldset.fields,function(key){var itemSchema=function(){if(schema[key])return schema[key];var path=key.replace(/\./g,".subSchema.");return getNested(schema,path)}();if(!itemSchema)throw"Field '"+key+"' not found in schema";var field=self.fields[key]=self.createField(key,itemSchema),fieldEl=field.render().el;field.editor.on("all",function(event){args=_.toArray(arguments),args[0]=key+":"+event,args.splice(1,0,this),this.trigger.apply(this,args)},self),field.editor.on("change",function(){this.trigger("change",self)},self),field.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},self),field.editor.on("blur",function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(_.find(self.fields,function(field){return field.editor.hasFocus}))return;self.trigger("blur",self)},0)},self),itemSchema.type!="Hidden"&&$fieldsContainer.append(fieldEl)}),$fieldsContainer=$fieldsContainer.children().unwrap(),$fieldset},createField:function(key,schema){schema.template=schema.template||this.options.fieldTemplate;var options={form:this,key:key,schema:schema,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?options.model=this.model:this.data?options.value=this.data[key]:options.value=null,new Form.Field(options)},validate:function(){var self=this,fields=this.fields,model=this.model,errors={};_.each(fields,function(field){var error=field.validate();error&&(errors[field.key]=error)});if(model&&model.validate){var modelErrors=model.validate(this.getValue());if(modelErrors){var isDictionary=_.isObject(modelErrors)&&!_.isArray(modelErrors);isDictionary||(errors._others=errors._others||[],errors._others.push(modelErrors)),isDictionary&&_.each(modelErrors,function(val,key){if(self.fields[key]&&!errors[key])self.fields[key].setError(val);else{errors._others=errors._others||[];var tmpErr={};tmpErr[key]=val,errors._others.push(tmpErr)}})}}return _.isEmpty(errors)?null:errors},commit:function(){var errors=this.validate();if(errors)return errors;var modelError;this.model.set(this.getValue(),{error:function(model,e){modelError=e}});if(modelError)return modelError},getValue:function(key){if(key)return this.fields[key].getValue();var values={};return _.each(this.fields,function(field){values[field.key]=field.getValue()}),values},setValue:function(data){for(var key in data)_.has(this.fields,key)&&this.fields[key].setValue(data[key])},focus:function(){if(this.hasFocus)return;var fieldset=this.options.fieldsets[0];if(fieldset){var field;_.isArray(fieldset)?field=fieldset[0]:field=fieldset.fields[0],field&&this.fields[field].editor.focus()}},blur:function(){if(!this.hasFocus)return;focusedField=_.find(this.fields,function(field){return field.editor.hasFocus}),focusedField&&focusedField.editor.blur()},remove:function(){var fields=this.fields;for(var key in fields)fields[key].remove();Backbone.View.prototype.remove.call(this)},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}})}();Form.helpers=function(){var helpers={};return helpers.getNested=function(obj,path){var fields=path.split("."),result=obj;for(var i=0,n=fields.length;i',help:''}},render:function(){var schema=this.schema,templates=Form.templates,options={form:this.form,key:this.key,schema:schema,idPrefix:this.options.idPrefix,id:this.getId()};this.model?options.model=this.model:options.value=this.value;var editor=this.editor=helpers.createEditor(schema.type,options),$field=$(templates[schema.template](this.renderingContext(schema,editor)));return $field.find(".bbf-tmp-editor").replaceWith(editor.render().el),this.$help=$(".bbf-tmp-help",$field).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.schema.fieldClass&&$field.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&$field.attr(this.schema.fieldAttrs),this.setElement($field),this},getId:function(){var prefix=this.options.idPrefix,id=this.key;return id=id.replace(/\./g,"_"),_.isString(prefix)||_.isNumber(prefix)?prefix+id:_.isNull(prefix)?id:this.model?this.model.cid+"_"+id:id},validate:function(){var error=this.editor.validate();return error?this.setError(error.message):this.clearError(),error},setError:function(msg){if(this.editor.hasNestedForm)return;var errClass=Form.classNames.error;this.$el.addClass(errClass),this.$help&&this.$help.html(msg)},clearError:function(){var errClass=Form.classNames.error;this.$el.removeClass(errClass);if(this.$help){this.$help.empty();var helpMsg=this.schema.help;helpMsg&&this.$help.html(helpMsg)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(value){this.editor.setValue(value)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)}})}(),Form.editors=function(){var helpers=Form.helpers,editors={};return editors.Base=Backbone.View.extend({defaultValue:null,hasFocus:!1,initialize:function(options){var options=options||{};if(options.model){if(!options.key)throw"Missing option: 'key'";this.model=options.model,this.value=this.model.get(options.key)}else options.value&&(this.value=options.value);this.value===undefined&&(this.value=this.defaultValue),this.key=options.key,this.form=options.form,this.schema=options.schema||{},this.validators=options.validators||this.schema.validators,this.$el.attr("name",this.getName()),this.schema.editorClass&&this.$el.addClass(this.schema.editorClass),this.schema.editorAttrs&&this.$el.attr(this.schema.editorAttrs)},getValue:function(){throw"Not implemented. Extend and override this method."},setValue:function(){throw"Not implemented. Extend and override this method."},focus:function(){throw"Not implemented. Extend and override this method."},blur:function(){throw"Not implemented. Extend and override this method."},getName:function(){var key=this.key||"";return key.replace(/\./g,"_")},commit:function(){var error=this.validate();if(error)return error;this.model.set(this.key,this.getValue(),{error:function(model,e){error=e}});if(error)return error},validate:function(){var $el=this.$el,error=null,value=this.getValue(),formValues=this.form?this.form.getValue():{},validators=this.validators,getValidator=Form.helpers.getValidator;return validators&&_.every(validators,function(validator){return error=getValidator(validator)(value,formValues),continueLoop=error?!1:!0}),error},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}}),editors.Text=editors.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(event){var self=this;setTimeout(function(){self.determineChange()},0)},select:function(event){this.trigger("select",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);var schema=this.schema,type="text";schema&&schema.editorAttrs&&schema.editorAttrs.type&&(type=schema.editorAttrs.type),schema&&schema.dataType&&(type=schema.dataType),this.$el.attr("type",type)},render:function(){return this.setValue(this.value),this},determineChange:function(event){var currentValue=this.$el.val(),changed=currentValue!=this.previousValue;changed&&(this.previousValue=currentValue,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),editors.Number=editors.Text.extend({defaultValue:0,events:_.extend({},editors.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(event){var self=this,delayedDetermineChange=function(){setTimeout(function(){self.determineChange()},0)};if(event.charCode==0){delayedDetermineChange();return}var newVal=this.$el.val()+String.fromCharCode(event.charCode),numeric=/^[0-9]*\.?[0-9]*?$/.test(newVal);numeric?delayedDetermineChange():event.preventDefault()},getValue:function(){var value=this.$el.val();return value===""?null:parseFloat(value,10)},setValue:function(value){value=function(){return _.isNumber(value)?value:_.isString(value)&&value!==""?parseFloat(value,10):null}(),_.isNaN(value)&&(value=null),editors.Text.prototype.setValue.call(this,value)}}),editors.Password=editors.Text.extend({initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","password")}}),editors.TextArea=editors.Text.extend({tagName:"textarea"}),editors.Checkbox=editors.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(value){value&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),editors.Hidden=editors.Base.extend({defaultValue:"",initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(value){this.value=value},focus:function(){},blur:function(){}}),editors.Select=editors.Base.extend({tagName:"select",events:{change:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(options){var self=this;if(options instanceof Backbone.Collection){var collection=options;collection.length>0?this.renderOptions(options):collection.fetch({success:function(collection){self.renderOptions(options)}})}else _.isFunction(options)?options(function(result){self.renderOptions(result)}):this.renderOptions(options)},renderOptions:function(options){var $select=this.$el,html;_.isString(options)?html=options:_.isArray(options)?html=this._arrayToHtml(options):options instanceof Backbone.Collection&&(html=this._collectionToHtml(options)),$select.html(html),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(collection){var array=[];collection.each(function(model){array.push({val:model.id,label:model.toString()})});var html=this._arrayToHtml(array);return html},_arrayToHtml:function(array){var html=[];return _.each(array,function(option){if(_.isObject(option)){var val=option.val?option.val:"";html.push('")}else html.push("")}),html.join("")}}),editors.Radio=editors.Select.extend({tagName:"ul",className:"bbf-radio",events:{"click input[type=radio]:not(:checked)":function(){this.trigger("change",this)},"focus input[type=radio]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=radio]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=radio]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(value){this.$("input[type=radio]").val([value])},focus:function(){if(this.hasFocus)return;var checked=this.$("input[type=radio]:checked");if(checked[0]){checked.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Checkboxes=editors.Select.extend({tagName:"ul",className:"bbf-checkboxes",events:{"click input[type=checkbox]":function(){this.trigger("change",this)},"focus input[type=checkbox]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=checkbox]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=checkbox]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){var values=[];return this.$("input[type=checkbox]:checked").each(function(){values.push($(this).val())}),values},setValue:function(values){_.isArray(values)||(values=[values]),this.$("input[type=checkbox]").val(values)},focus:function(){if(this.hasFocus)return;this.$("input[type=checkbox]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=checkbox]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Object=editors.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(options){this.value={},editors.Base.prototype.initialize.call(this,options);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new Form({schema:this.schema.subSchema,data:this.value,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){return this.form?this.form.getValue():this.value},setValue:function(value){this.value=value,this.render()},focus:function(){if(this.hasFocus)return;this.form.focus()},blur:function(){if(!this.hasFocus)return;this.form.blur()},remove:function(){this.form.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){args=_.toArray(arguments),args[1]=this,this.trigger.apply(this,args)},this)}}),editors.NestedModel=editors.Object.extend({initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!options.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var data=this.value||{},key=this.key,nestedModel=this.schema.model,modelInstance=data.constructor==nestedModel?data:new nestedModel(data);return this.form=new Form({model:modelInstance,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var error=this.form.commit();return error?(this.$el.addClass("error"),error):editors.Object.prototype.commit.call(this)}}),editors.Date=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options);var Self=editors.Date,today=new Date;this.options=_.extend({monthNames:Self.monthNames,showMonthNames:Self.showMonthNames},options),this.schema=_.extend({yearStart:today.getFullYear()-100,yearEnd:today.getFullYear()},options.schema||{}),this.value&&!_.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var date=new Date;date.setSeconds(0),date.setMilliseconds(0),this.value=date}},render:function(){var options=this.options,schema=this.schema,datesOptions=_.map(_.range(1,32),function(date){return'"}),monthsOptions=_.map(_.range(0,12),function(month){var value=options.showMonthNames?options.monthNames[month]:month+1;return'"}),yearsOptions=_.map(_.range(schema.yearStart,schema.yearEnd+1),function(year){return'"}),$el=$(Form.templates.date({dates:datesOptions.join(""),months:monthsOptions.join(""),years:yearsOptions.join("")}));return this.$date=$el.find('select[data-type="date"]'),this.$month=$el.find('select[data-type="month"]'),this.$year=$el.find('select[data-type="year"]'),this.$hidden=$(''),$el.append(this.$hidden),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var year=this.$year.val(),month=this.$month.val(),date=this.$date.val();return!year||!month||!date?null:new Date(year,month,date)},setValue:function(date){this.$date.val(date.getDate()),this.$month.val(date.getMonth()),this.$year.val(date.getFullYear()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),editors.DateTime=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options),this.options=_.extend({DateEditor:editors.DateTime.DateEditor},options),this.schema=_.extend({minsInterval:15},options.schema||{}),this.dateEditor=new this.options.DateEditor(options),this.value=this.dateEditor.value},render:function(){function pad(n){return n<10?"0"+n:n}var schema=this.schema,hoursOptions=_.map(_.range(0,24),function(hour){return'"}),minsOptions=_.map(_.range(0,60,schema.minsInterval),function(min){return'"}),$el=$(Form.templates.dateTime({date:'',hours:hoursOptions.join(),mins:minsOptions.join()}));return $el.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=$el.find('select[data-type="hour"]'),this.$min=$el.find('select[data-type="min"]'),this.$hidden=$el.find('input[type="hidden"]'),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var date=this.dateEditor.getValue(),hour=this.$hour.val(),min=this.$min.val();return!date||!hour||!min?null:(date.setHours(hour),date.setMinutes(min),date)},setValue:function(date){_.isDate(date)||(date=new Date(date)),this.dateEditor.setValue(date),this.$hour.val(date.getHours()),this.$min.val(date.getMinutes()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)},remove:function(){this.dateEditor.remove(),editors.Base.prototype.remove.call(this)}},{DateEditor:editors.Date}),editors}(),Form.setTemplates=Form.helpers.setTemplates,Form.setTemplateCompiler=Form.helpers.setTemplateCompiler,Form.templates={},Form.setTemplates({form:'
    {{fieldsets}}
    ',fieldset:"
    {{legend}}
      {{fields}}
    ",field:'
  • {{editor}}
    {{help}}
  • ',nestedField:'
  • {{editor}}
    {{help}}
  • ',list:'
      {{items}}
    ',listItem:'
  • {{editor}}
  • ',date:'
    ',dateTime:'
    {{date}}
    :
    ',"list.Modal":'
    {{summary}}
    '},{error:"bbf-error"}),Form.VERSION="0.10.1",Backbone.Form=Form})(this) \ No newline at end of file +(function(root){if(typeof exports!="undefined"&&typeof require!="undefined")var $=root.jQuery||root.Zepto||root.ender||require("jquery"),_=root._||require("underscore"),Backbone=root.Backbone||require("backbone");else var $=root.jQuery,_=root._,Backbone=root.Backbone;var Form=function(){return Backbone.View.extend({hasFocus:!1,initialize:function(options){if(!Form.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(options.schema)return options.schema;var model=options.model;if(!model)throw new Error("Could not find schema");return _.isFunction(model.schema)?model.schema():model.schema}(),options=_.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},options);if(!options.fieldsets){var fields=options.fields||_.keys(this.schema);options.fieldsets=[{fields:fields}]}this.options=options,this.model=options.model,this.data=options.data,this.fields={}},render:function(){var self=this,options=this.options,template=Form.templates[options.template],$form=$(template({fieldsets:''})),$fieldsetContainer=$(".bbf-tmp",$form);return _.each(options.fieldsets,function(fieldset){$fieldsetContainer.append(self.renderFieldset(fieldset))}),$fieldsetContainer.children().unwrap(),this.setElement($form),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(fieldset){var self=this,template=Form.templates[this.options.fieldsetTemplate],schema=this.schema,getNested=Form.helpers.getNested;_.isArray(fieldset)&&(fieldset={fields:fieldset});var $fieldset=$(template(_.extend({},fieldset,{legend:'',fields:''})));fieldset.legend?$fieldset.find(".bbf-tmp-legend").replaceWith(fieldset.legend):$fieldset.find(".bbf-tmp-legend").parent().remove();var $fieldsContainer=$(".bbf-tmp-fields",$fieldset);return _.each(fieldset.fields,function(key){var itemSchema=function(){if(schema[key])return schema[key];var path=key.replace(/\./g,".subSchema.");return getNested(schema,path)}();if(!itemSchema)throw"Field '"+key+"' not found in schema";var field=self.fields[key]=self.createField(key,itemSchema),fieldEl=field.render().el;field.editor.on("all",function(event){args=_.toArray(arguments),args[0]=key+":"+event,args.splice(1,0,this),this.trigger.apply(this,args)},self),field.editor.on("change",function(){this.trigger("change",self)},self),field.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},self),field.editor.on("blur",function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(_.find(self.fields,function(field){return field.editor.hasFocus}))return;self.trigger("blur",self)},0)},self),itemSchema.type!="Hidden"&&$fieldsContainer.append(fieldEl)}),$fieldsContainer=$fieldsContainer.children().unwrap(),$fieldset},createField:function(key,schema){schema.template=schema.template||this.options.fieldTemplate;var options={form:this,key:key,schema:schema,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?options.model=this.model:this.data?options.value=this.data[key]:options.value=null,new Form.Field(options)},validate:function(){var self=this,fields=this.fields,model=this.model,errors={};_.each(fields,function(field){var error=field.validate();error&&(errors[field.key]=error)});if(model&&model.validate){var modelErrors=model.validate(this.getValue());if(modelErrors){var isDictionary=_.isObject(modelErrors)&&!_.isArray(modelErrors);isDictionary||(errors._others=errors._others||[],errors._others.push(modelErrors)),isDictionary&&_.each(modelErrors,function(val,key){if(self.fields[key]&&!errors[key])self.fields[key].setError(val);else{errors._others=errors._others||[];var tmpErr={};tmpErr[key]=val,errors._others.push(tmpErr)}})}}return _.isEmpty(errors)?null:errors},commit:function(){var errors=this.validate();if(errors)return errors;var modelError;this.model.set(this.getValue(),{error:function(model,e){modelError=e}});if(modelError)return modelError},getValue:function(key){if(key)return this.fields[key].getValue();var values={};return _.each(this.fields,function(field){values[field.key]=field.getValue()}),values},setValue:function(prop,val){var data={};typeof prop=="string"?data[prop]=val:data=prop;for(var key in data)_.has(this.fields,key)&&this.fields[key].setValue(data[key])},focus:function(){if(this.hasFocus)return;var fieldset=this.options.fieldsets[0];if(fieldset){var field;_.isArray(fieldset)?field=fieldset[0]:field=fieldset.fields[0],field&&this.fields[field].editor.focus()}},blur:function(){if(!this.hasFocus)return;focusedField=_.find(this.fields,function(field){return field.editor.hasFocus}),focusedField&&focusedField.editor.blur()},remove:function(){var fields=this.fields;for(var key in fields)fields[key].remove();Backbone.View.prototype.remove.call(this)},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}})}();Form.helpers=function(){var helpers={};return helpers.getNested=function(obj,path){var fields=path.split("."),result=obj;for(var i=0,n=fields.length;i',help:''}},render:function(){var schema=this.schema,templates=Form.templates,options={form:this.form,key:this.key,schema:schema,idPrefix:this.options.idPrefix,id:this.getId()};this.model?options.model=this.model:options.value=this.value;var editor=this.editor=helpers.createEditor(schema.type,options),$field=$(templates[schema.template](this.renderingContext(schema,editor)));return $field.find(".bbf-tmp-editor").replaceWith(editor.render().el),this.$help=$(".bbf-tmp-help",$field).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.schema.fieldClass&&$field.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&$field.attr(this.schema.fieldAttrs),this.setElement($field),this},getId:function(){var prefix=this.options.idPrefix,id=this.key;return id=id.replace(/\./g,"_"),_.isString(prefix)||_.isNumber(prefix)?prefix+id:_.isNull(prefix)?id:this.model?this.model.cid+"_"+id:id},validate:function(){var error=this.editor.validate();return error?this.setError(error.message):this.clearError(),error},setError:function(msg){if(this.editor.hasNestedForm)return;var errClass=Form.classNames.error;this.$el.addClass(errClass),this.$help&&this.$help.html(msg)},clearError:function(){var errClass=Form.classNames.error;this.$el.removeClass(errClass);if(this.$help){this.$help.empty();var helpMsg=this.schema.help;helpMsg&&this.$help.html(helpMsg)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(value){this.editor.setValue(value)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),Backbone.View.prototype.remove.call(this)}})}(),Form.editors=function(){var helpers=Form.helpers,editors={};return editors.Base=Backbone.View.extend({defaultValue:null,hasFocus:!1,initialize:function(options){var options=options||{};if(options.model){if(!options.key)throw"Missing option: 'key'";this.model=options.model,this.value=this.model.get(options.key)}else options.value&&(this.value=options.value);this.value===undefined&&(this.value=this.defaultValue),this.key=options.key,this.form=options.form,this.schema=options.schema||{},this.validators=options.validators||this.schema.validators,this.$el.attr("name",this.getName()),this.schema.editorClass&&this.$el.addClass(this.schema.editorClass),this.schema.editorAttrs&&this.$el.attr(this.schema.editorAttrs)},getValue:function(){throw"Not implemented. Extend and override this method."},setValue:function(){throw"Not implemented. Extend and override this method."},focus:function(){throw"Not implemented. Extend and override this method."},blur:function(){throw"Not implemented. Extend and override this method."},getName:function(){var key=this.key||"";return key.replace(/\./g,"_")},commit:function(){var error=this.validate();if(error)return error;this.model.set(this.key,this.getValue(),{error:function(model,e){error=e}});if(error)return error},validate:function(){var $el=this.$el,error=null,value=this.getValue(),formValues=this.form?this.form.getValue():{},validators=this.validators,getValidator=Form.helpers.getValidator;return validators&&_.every(validators,function(validator){return error=getValidator(validator)(value,formValues),continueLoop=error?!1:!0}),error},trigger:function(event){return event=="focus"?this.hasFocus=!0:event=="blur"&&(this.hasFocus=!1),Backbone.View.prototype.trigger.apply(this,arguments)}}),editors.Text=editors.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(event){var self=this;setTimeout(function(){self.determineChange()},0)},select:function(event){this.trigger("select",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);var schema=this.schema,type="text";schema&&schema.editorAttrs&&schema.editorAttrs.type&&(type=schema.editorAttrs.type),schema&&schema.dataType&&(type=schema.dataType),this.$el.attr("type",type)},render:function(){return this.setValue(this.value),this},determineChange:function(event){var currentValue=this.$el.val(),changed=currentValue!=this.previousValue;changed&&(this.previousValue=currentValue,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),editors.Number=editors.Text.extend({defaultValue:0,events:_.extend({},editors.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(event){var self=this,delayedDetermineChange=function(){setTimeout(function(){self.determineChange()},0)};if(event.charCode==0){delayedDetermineChange();return}var newVal=this.$el.val()+String.fromCharCode(event.charCode),numeric=/^[0-9]*\.?[0-9]*?$/.test(newVal);numeric?delayedDetermineChange():event.preventDefault()},getValue:function(){var value=this.$el.val();return value===""?null:parseFloat(value,10)},setValue:function(value){value=function(){return _.isNumber(value)?value:_.isString(value)&&value!==""?parseFloat(value,10):null}(),_.isNaN(value)&&(value=null),editors.Text.prototype.setValue.call(this,value)}}),editors.Password=editors.Text.extend({initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","password")}}),editors.TextArea=editors.Text.extend({tagName:"textarea"}),editors.Checkbox=editors.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(value){value&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),editors.Hidden=editors.Base.extend({defaultValue:"",initialize:function(options){editors.Text.prototype.initialize.call(this,options),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(value){this.value=value},focus:function(){},blur:function(){}}),editors.Select=editors.Base.extend({tagName:"select",events:{change:function(event){this.trigger("change",this)},focus:function(event){this.trigger("focus",this)},blur:function(event){this.trigger("blur",this)}},initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(options){var self=this;if(options instanceof Backbone.Collection){var collection=options;collection.length>0?this.renderOptions(options):collection.fetch({success:function(collection){self.renderOptions(options)}})}else _.isFunction(options)?options(function(result){self.renderOptions(result)}):this.renderOptions(options)},renderOptions:function(options){var $select=this.$el,html;_.isString(options)?html=options:_.isArray(options)?html=this._arrayToHtml(options):options instanceof Backbone.Collection&&(html=this._collectionToHtml(options)),$select.html(html),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(value){this.$el.val(value)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(collection){var array=[];collection.each(function(model){array.push({val:model.id,label:model.toString()})});var html=this._arrayToHtml(array);return html},_arrayToHtml:function(array){var html=[];return _.each(array,function(option){if(_.isObject(option)){var val=option.val?option.val:"";html.push('")}else html.push("")}),html.join("")}}),editors.Radio=editors.Select.extend({tagName:"ul",className:"bbf-radio",events:{"click input[type=radio]:not(:checked)":function(){this.trigger("change",this)},"focus input[type=radio]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=radio]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=radio]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(value){this.$("input[type=radio]").val([value])},focus:function(){if(this.hasFocus)return;var checked=this.$("input[type=radio]:checked");if(checked[0]){checked.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Checkboxes=editors.Select.extend({tagName:"ul",className:"bbf-checkboxes",events:{"click input[type=checkbox]":function(){this.trigger("change",this)},"focus input[type=checkbox]":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur input[type=checkbox]":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("input[type=checkbox]:focus")[0])return;self.trigger("blur",self)},0)}},getValue:function(){var values=[];return this.$("input[type=checkbox]:checked").each(function(){values.push($(this).val())}),values},setValue:function(values){_.isArray(values)||(values=[values]),this.$("input[type=checkbox]").val(values)},focus:function(){if(this.hasFocus)return;this.$("input[type=checkbox]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=checkbox]:focus").blur()},_arrayToHtml:function(array){var html=[],self=this;return _.each(array,function(option,index){var itemHtml="
  • ";if(_.isObject(option)){var val=option.val?option.val:"";itemHtml+='',itemHtml+='"}else itemHtml+='',itemHtml+='";itemHtml+="
  • ",html.push(itemHtml)}),html.join("")}}),editors.Object=editors.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(options){this.value={},editors.Base.prototype.initialize.call(this,options);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new Form({schema:this.schema.subSchema,data:this.value,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){return this.form?this.form.getValue():this.value},setValue:function(value){this.value=value,this.render()},focus:function(){if(this.hasFocus)return;this.form.focus()},blur:function(){if(!this.hasFocus)return;this.form.blur()},remove:function(){this.form.remove(),Backbone.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){args=_.toArray(arguments),args[1]=this,this.trigger.apply(this,args)},this)}}),editors.NestedModel=editors.Object.extend({initialize:function(options){editors.Base.prototype.initialize.call(this,options);if(!options.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var data=this.value||{},key=this.key,nestedModel=this.schema.model,modelInstance=data.constructor==nestedModel?data:new nestedModel(data);return this.form=new Form({model:modelInstance,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var error=this.form.commit();return error?(this.$el.addClass("error"),error):editors.Object.prototype.commit.call(this)}}),editors.Date=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options);var Self=editors.Date,today=new Date;this.options=_.extend({monthNames:Self.monthNames,showMonthNames:Self.showMonthNames},options),this.schema=_.extend({yearStart:today.getFullYear()-100,yearEnd:today.getFullYear()},options.schema||{}),this.value&&!_.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var date=new Date;date.setSeconds(0),date.setMilliseconds(0),this.value=date}},render:function(){var options=this.options,schema=this.schema,datesOptions=_.map(_.range(1,32),function(date){return'"}),monthsOptions=_.map(_.range(0,12),function(month){var value=options.showMonthNames?options.monthNames[month]:month+1;return'"}),yearRange=schema.yearStart'+year+""}),$el=$(Form.templates.date({dates:datesOptions.join(""),months:monthsOptions.join(""),years:yearsOptions.join("")}));return this.$date=$el.find('select[data-type="date"]'),this.$month=$el.find('select[data-type="month"]'),this.$year=$el.find('select[data-type="year"]'),this.$hidden=$(''),$el.append(this.$hidden),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var year=this.$year.val(),month=this.$month.val(),date=this.$date.val();return!year||!month||!date?null:new Date(year,month,date)},setValue:function(date){this.$date.val(date.getDate()),this.$month.val(date.getMonth()),this.$year.val(date.getFullYear()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),editors.DateTime=editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var self=this;setTimeout(function(){if(self.$("select:focus")[0])return;self.trigger("blur",self)},0)}},initialize:function(options){options=options||{},editors.Base.prototype.initialize.call(this,options),this.options=_.extend({DateEditor:editors.DateTime.DateEditor},options),this.schema=_.extend({minsInterval:15},options.schema||{}),this.dateEditor=new this.options.DateEditor(options),this.value=this.dateEditor.value},render:function(){function pad(n){return n<10?"0"+n:n}var schema=this.schema,hoursOptions=_.map(_.range(0,24),function(hour){return'"}),minsOptions=_.map(_.range(0,60,schema.minsInterval),function(min){return'"}),$el=$(Form.templates.dateTime({date:'',hours:hoursOptions.join(),mins:minsOptions.join()}));return $el.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=$el.find('select[data-type="hour"]'),this.$min=$el.find('select[data-type="min"]'),this.$hidden=$el.find('input[type="hidden"]'),this.setValue(this.value),this.setElement($el),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var date=this.dateEditor.getValue(),hour=this.$hour.val(),min=this.$min.val();return!date||!hour||!min?null:(date.setHours(hour),date.setMinutes(min),date)},setValue:function(date){_.isDate(date)||(date=new Date(date)),this.dateEditor.setValue(date),this.$hour.val(date.getHours()),this.$min.val(date.getMinutes()),this.updateHidden()},focus:function(){if(this.hasFocus)return;this.$("select").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("select:focus").blur()},updateHidden:function(){var val=this.getValue();_.isDate(val)&&(val=val.toISOString()),this.$hidden.val(val)},remove:function(){this.dateEditor.remove(),editors.Base.prototype.remove.call(this)}},{DateEditor:editors.Date}),editors}(),Form.setTemplates=Form.helpers.setTemplates,Form.setTemplateCompiler=Form.helpers.setTemplateCompiler,Form.templates={},Form.setTemplates({form:'
    {{fieldsets}}
    ',fieldset:"
    {{legend}}
      {{fields}}
    ",field:'
  • {{editor}}
    {{help}}
  • ',nestedField:'
  • {{editor}}
    {{help}}
  • ',list:'
      {{items}}
    ',listItem:'
  • {{editor}}
  • ',date:'
    ',dateTime:'
    {{date}}
    :
    ',"list.Modal":'
    {{summary}}
    '},{error:"bbf-error"}),Form.VERSION="0.10.1",Backbone.Form=Form})(this) \ No newline at end of file diff --git a/test/index.html b/test/index.html index a98bf289..3288d996 100644 --- a/test/index.html +++ b/test/index.html @@ -100,7 +100,7 @@

    customTemplate: { template: 'customField' }, shorthand: 'Password', date: { type: 'Date' }, - dateTime: { type: 'DateTime', yearStart: 1980, yearEnd: 2000 }, + dateTime: { type: 'DateTime', yearStart: 2000, yearEnd: 1980 }, //List textList: { type: 'List', itemType: 'Text', validators: ['required', 'email'] },