Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add context for Select.options function #159

Closed
wants to merge 6 commits into from

3 participants

@7footmoustache

When calling the possible function of options on the schema definition of a Select editor, we found it handy to have context to the Form.

With this patch, when defining a function, "this" now refers to the Form and not the global "window".

@philfreo
Collaborator

Took care of this in 02c6b78
I think the new parameter was a safer choice than this since you could also argue this should be the Model in a Model's schema.

Let me know if this doesn't work for you for some reason.

@philfreo philfreo closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
33 distribution.amd/backbone-forms.js
@@ -76,6 +76,15 @@ var Form = (function() {
},
/**
+ * The rendering context for the form template
+ */
+ renderingContext: function() {
+ return {
+ fieldsets: '<b class="bbf-tmp"></b>'
+ };
+ },
+
+ /**
* Renders the form and all fields
*/
render: function() {
@@ -84,9 +93,7 @@ var Form = (function() {
template = Form.templates[options.template];
//Create el from template
- var $form = $(template({
- fieldsets: '<b class="bbf-tmp"></b>'
- }));
+ var $form = $(template(self.renderingContext()));
//Render fieldsets
var $fieldsetContainer = $('.bbf-tmp', $form);
@@ -163,6 +170,8 @@ var Form = (function() {
//Render the fields with editors, apart from Hidden fields
var fieldEl = field.render().el;
+ self.trigger(key + ':render', self, field);
+ self.trigger('field:render', self, field);
field.editor.on('all', function(event) {
// args = ["change", editor]
@@ -175,14 +184,17 @@ var Form = (function() {
}, self);
field.editor.on('change', function() {
- this.trigger('change', self);
+ this.trigger('change', this);
+ this.trigger('field:change', this, field);
}, self);
field.editor.on('focus', function() {
+ this.trigger('field:focus', this, field);
if (this.hasFocus) return;
this.trigger('focus', this);
}, self);
field.editor.on('blur', function() {
+ this.trigger('field:blur', this, field);
if (!this.hasFocus) return;
var self = this;
setTimeout(function() {
@@ -194,6 +206,9 @@ var Form = (function() {
if (itemSchema.type !== 'Hidden') {
$fieldsContainer.append(fieldEl);
}
+
+ self.trigger(key + ':show', self, field);
+ self.trigger('field:show', self, field);
});
$fieldsContainer = $fieldsContainer.children().unwrap();
@@ -228,7 +243,8 @@ var Form = (function() {
options.value = null;
}
- return new Form.Field(options);
+ var Field = this.Field || Backbone.Form.Field;
+ return new Field(options);
},
/**
@@ -1094,7 +1110,8 @@ Form.editors = (function() {
* @return {String}
*/
validate: function() {
- var $el = this.$el,
+ var self = this,
+ $el = this.$el,
error = null,
value = this.getValue(),
formValues = this.form ? this.form.getValue() : {},
@@ -1104,7 +1121,7 @@ Form.editors = (function() {
if (validators) {
//Run through validators until an error is found
_.every(validators, function(validator) {
- error = getValidator(validator)(value, formValues);
+ error = getValidator(validator).call(self, value, formValues);
return error ? false : true;
});
@@ -1468,7 +1485,7 @@ Form.editors = (function() {
//If a function was passed, run it to get the options
else if (_.isFunction(options)) {
- options(function(result) {
+ options.call(self, function(result) {
self.renderOptions(result);
});
}
View
2  distribution.amd/backbone-forms.min.js
@@ -1 +1 @@
-define(["jquery","underscore","backbone"],function(e,t,n){var r=function(){return n.View.extend({hasFocus:!1,initialize:function(e){if(!r.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(e.schema)return e.schema;var n=e.model;if(!n)throw new Error("Could not find schema");return t.isFunction(n.schema)?n.schema():n.schema}(),e=t.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},e);if(!e.fieldsets){var n=e.fields||t.keys(this.schema);e.fieldsets=[{fields:n}]}this.options=e,this.model=e.model,this.data=e.data,this.fields={}},render:function(){var n=this,i=this.options,s=r.templates[i.template],o=e(s({fieldsets:'<b class="bbf-tmp"></b>'})),u=e(".bbf-tmp",o);return t.each(i.fieldsets,function(e){u.append(n.renderFieldset(e))}),u.children().unwrap(),this.setElement(o),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(n){var i=this,s=r.templates[this.options.fieldsetTemplate],o=this.schema,u=r.helpers.getNested;t.isArray(n)&&(n={fields:n});var a=e(s(t.extend({},n,{legend:'<b class="bbf-tmp-legend"></b>',fields:'<b class="bbf-tmp-fields"></b>'})));n.legend?a.find(".bbf-tmp-legend").replaceWith(n.legend):a.find(".bbf-tmp-legend").parent().remove();var f=e(".bbf-tmp-fields",a);return t.each(n.fields,function(e){var n=function(){if(o[e])return o[e];var t=e.replace(/\./g,".subSchema.");return u(o,t)}();if(!n)throw"Field '"+e+"' not found in schema";var r=i.fields[e]=i.createField(e,n),s=r.render().el;r.editor.on("all",function(n){var r=t.toArray(arguments);r[0]=e+":"+n,r.splice(1,0,this),this.trigger.apply(this,r)},i),r.editor.on("change",function(){this.trigger("change",i)},i),r.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},i),r.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(t.find(e.fields,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},i),n.type!=="Hidden"&&f.append(s)}),f=f.children().unwrap(),a},createField:function(e,t){t.template=t.template||this.options.fieldTemplate;var n={form:this,key:e,schema:t,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?n.model=this.model:this.data?n.value=this.data[e]:n.value=null,new r.Field(n)},validate:function(){var e=this,n=this.fields,r=this.model,i={};t.each(n,function(e){var t=e.validate();t&&(i[e.key]=t)});if(r&&r.validate){var s=r.validate(this.getValue());if(s){var o=t.isObject(s)&&!t.isArray(s);o||(i._others=i._others||[],i._others.push(s)),o&&t.each(s,function(t,n){if(e.fields[n]&&!i[n])e.fields[n].setError(t),i[n]=t;else{i._others=i._others||[];var r={};r[n]=t,i._others.push(r)}})}}return t.isEmpty(i)?null:i},commit:function(){var e=this.validate();if(e)return e;var t;this.model.set(this.getValue(),{error:function(e,n){t=n}});if(t)return t},getValue:function(e){if(e)return this.fields[e].getValue();var n={};return t.each(this.fields,function(e){n[e.key]=e.getValue()}),n},setValue:function(e,t){var n={};typeof e=="string"?n[e]=t:n=e;var r;for(r in this.schema)n[r]!==undefined&&this.fields[r].setValue(n[r])},focus:function(){if(this.hasFocus)return;var e=this.options.fieldsets[0];if(e){var n;t.isArray(e)?n=e[0]:n=e.fields[0],n&&this.fields[n].editor.focus()}},blur:function(){if(!this.hasFocus)return;var e=t.find(this.fields,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){var e=this.fields;for(var t in e)e[t].remove();n.View.prototype.remove.call(this)},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),n.View.prototype.trigger.apply(this,arguments)}})}();return r.helpers=function(){var e={};return e.getNested=function(e,t){var n=t.split("."),r=e;for(var i=0,s=n.length;i<s;i++)r=r[n[i]];return r},e.keyToTitle=function(e){return e=e.replace(/([A-Z])/g," $1"),e=e.replace(/^./,function(e){return e.toUpperCase()}),e},e.compileTemplate=function(e){var n=t.templateSettings.interpolate;t.templateSettings.interpolate=/\{\{(.+?)\}\}/g;var r=t.template(e);return t.templateSettings.interpolate=n,r},e.createTemplate=function(t,n){var r=e.compileTemplate(t);return n?r(n):r},e.setTemplateCompiler=function(t){e.compileTemplate=t},e.setTemplates=function(n,i){var s=e.createTemplate;r.templates=r.templates||{},r.classNames=r.classNames||{},t.each(n,function(e,n,i){t.isString(e)&&(e=s(e)),r.templates[n]=e}),t.extend(r.classNames,i)},e.createEditor=function(e,n){var i;return t.isString(e)?i=r.editors[e]:i=e,new i(n)},e.triggerCancellableEvent=function(e,t,n,r){if(!e._callbacks||!e._callbacks[t])return r();var i=e._callbacks[t].next;if(!i)return r();var s=i.callback,o=i.context||this;n.push(r),s.apply(o,n)},e.getValidator=function(e){var n=r.validators;if(t.isRegExp(e))return n.regexp({regexp:e});if(t.isString(e)){if(!n[e])throw new Error('Validator "'+e+'" not found');return n[e]()}if(t.isFunction(e))return e;if(t.isObject(e)&&e.type){var i=e;return n[i.type](i)}throw new Error("Invalid validator: "+e)},e}(),r.validators=function(){var e={};return e.errMessages={required:"Required",regexp:"Invalid",email:"Invalid email address",url:"Invalid URL",match:'Must match field "{{field}}"'},e.required=function(e){return e=t.extend({type:"required",message:this.errMessages.required},e),function(n){e.value=n;var i={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return i}},e.regexp=function(e){if(!e.regexp)throw new Error('Missing required "regexp" option for "regexp" validator');return e=t.extend({type:"regexp",message:this.errMessages.regexp},e),function(n){e.value=n;var i={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(!e.regexp.test(n))return i}},e.email=function(n){return n=t.extend({type:"email",message:this.errMessages.email,regexp:/^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/},n),e.regexp(n)},e.url=function(n){return n=t.extend({type:"url",message:this.errMessages.url,regexp:/^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i},n),e.regexp(n)},e.match=function(e){if(!e.field)throw new Error('Missing required "field" options for "match" validator');return e=t.extend({type:"match",message:this.errMessages.match},e),function(n,i){e.value=n;var s={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(n!==i[e.field])return s}},e}(),r.Field=function(){var i=r.helpers,s=r.templates;return n.View.extend({initialize:function(e){e=e||{},this.form=e.form,this.key=e.key,this.value=e.value,this.model=e.model,t.isString(e.schema)&&(e.schema={type:e.schema}),this.schema=t.extend({type:"Text",title:i.keyToTitle(this.key),template:"field"},e.schema)},renderingContext:function(e,t){return{key:this.key,title:e.title,id:t.id,type:e.type,editor:'<b class="bbf-tmp-editor"></b>',help:'<b class="bbf-tmp-help"></b>',error:'<b class="bbf-tmp-error"></b>'}},render:function(){var t=this.schema,n=r.templates,s={form:this.form,key:this.key,schema:t,idPrefix:this.options.idPrefix,id:this.getId()};this.model?s.model=this.model:s.value=this.value;var o=this.editor=i.createEditor(t.type,s),u=e(n[t.template](this.renderingContext(t,o)));return t.title===!1&&u.find('label[for="'+o.id+'"]').first().remove(),u.find(".bbf-tmp-editor").replaceWith(o.render().el),this.$help=e(".bbf-tmp-help",u).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.$error=e(e(".bbf-tmp-error",u).parent()[0]),this.$error&&this.$error.empty(),this.schema.fieldClass&&u.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&u.attr(this.schema.fieldAttrs),this.setElement(u),this},getId:function(){var e=this.options.idPrefix,n=this.key;return n=n.replace(/\./g,"_"),t.isString(e)||t.isNumber(e)?e+n:t.isNull(e)?n:this.model?this.model.cid+"_"+n:n},validate:function(){var e=this.editor.validate();return e?this.setError(e.message):this.clearError(),e},setError:function(e){if(this.editor.hasNestedForm)return;var t=r.classNames.error;this.$el.addClass(t),this.$error?this.$error.html(e):this.$help&&this.$help.html(e)},clearError:function(){var e=r.classNames.error;this.$el.removeClass(e);if(this.$error)this.$error.empty();else if(this.$help){this.$help.empty();var t=this.schema.help;t&&this.$help.html(t)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),n.View.prototype.remove.call(this)}})}(),r.editors=function(){var i=r.helpers,s={};return s.Base=n.View.extend({defaultValue:null,hasFocus:!1,initialize:function(e){var e=e||{};if(e.model){if(!e.key)throw"Missing option: 'key'";this.model=e.model,this.value=this.model.get(e.key)}else e.value&&(this.value=e.value);this.value===undefined&&(this.value=this.defaultValue),this.key=e.key,this.form=e.form,this.schema=e.schema||{},this.validators=e.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 e=this.key||"";return e.replace(/\./g,"_")},commit:function(){var e=this.validate();if(e)return e;this.model.set(this.key,this.getValue(),{error:function(t,n){e=n}});if(e)return e},validate:function(){var e=this.$el,n=null,i=this.getValue(),s=this.form?this.form.getValue():{},o=this.validators,u=r.helpers.getValidator;return o&&t.every(o,function(e){return n=u(e)(i,s),n?!1:!0}),n},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),n.View.prototype.trigger.apply(this,arguments)}}),s.Text=s.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(e){var t=this;setTimeout(function(){t.determineChange()},0)},select:function(e){this.trigger("select",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);var t=this.schema,n="text";t&&t.editorAttrs&&t.editorAttrs.type&&(n=t.editorAttrs.type),t&&t.dataType&&(n=t.dataType),this.$el.attr("type",n)},render:function(){return this.setValue(this.value),this},determineChange:function(e){var t=this.$el.val(),n=t!==this.previousValue;n&&(this.previousValue=t,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),s.Number=s.Text.extend({defaultValue:0,events:t.extend({},s.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(e){var t=this,n=function(){setTimeout(function(){t.determineChange()},0)};if(e.charCode===0){n();return}var r=this.$el.val()+String.fromCharCode(e.charCode),i=/^[0-9]*\.?[0-9]*?$/.test(r);i?n():e.preventDefault()},getValue:function(){var e=this.$el.val();return e===""?null:parseFloat(e,10)},setValue:function(e){e=function(){return t.isNumber(e)?e:t.isString(e)&&e!==""?parseFloat(e,10):null}(),t.isNaN(e)&&(e=null),s.Text.prototype.setValue.call(this,e)}}),s.Password=s.Text.extend({initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","password")}}),s.TextArea=s.Text.extend({tagName:"textarea"}),s.Checkbox=s.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(e){e&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),s.Hidden=s.Base.extend({defaultValue:"",initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){},blur:function(){}}),s.Select=s.Base.extend({tagName:"select",events:{change:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(e){var r=this;if(e instanceof n.Collection){var i=e;i.length>0?this.renderOptions(e):i.fetch({success:function(t){r.renderOptions(e)}})}else t.isFunction(e)?e(function(e){r.renderOptions(e)}):this.renderOptions(e)},renderOptions:function(e){var r=this.$el,i;t.isString(e)?i=e:t.isArray(e)?i=this._arrayToHtml(e):e instanceof n.Collection&&(i=this._collectionToHtml(e)),r.html(i),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(e){var t=[];e.each(function(e){t.push({val:e.id,label:e.toString()})});var n=this._arrayToHtml(t);return n},_arrayToHtml:function(e){var n=[];return t.each(e,function(e){if(t.isObject(e)){var r=e.val||e.val===0?e.val:"";n.push('<option value="'+r+'">'+e.label+"</option>")}else n.push("<option>"+e+"</option>")}),n.join("")}}),s.Radio=s.Select.extend({tagName:"ul",className:"bbf-radio",events:{"change input[type=radio]":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 e=this;setTimeout(function(){if(e.$("input[type=radio]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(e){this.$("input[type=radio]").val([e])},focus:function(){if(this.hasFocus)return;var e=this.$("input[type=radio]:checked");if(e[0]){e.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(e){var n=[],r=this;return t.each(e,function(e,i){var s="<li>";if(t.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="radio" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="radio" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",n.push(s)}),n.join("")}}),s.Checkboxes=s.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 e=this;setTimeout(function(){if(e.$("input[type=checkbox]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){var t=[];return this.$("input[type=checkbox]:checked").each(function(){t.push(e(this).val())}),t},setValue:function(e){t.isArray(e)||(e=[e]),this.$("input[type=checkbox]").val(e)},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(e){var n=[],r=this;return t.each(e,function(e,i){var s="<li>";if(t.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="checkbox" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="checkbox" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",n.push(s)}),n.join("")}}),s.Object=s.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(e){this.value={},s.Base.prototype.initialize.call(this,e);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new r({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(e){this.value=e,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(),n.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){var e=t.toArray(arguments);e[1]=this,this.trigger.apply(this,e)},this)}}),s.NestedModel=s.Object.extend({initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!e.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var e=this.value||{},t=this.key,n=this.schema.model,i=e.constructor===n?e:new n(e);return this.form=new r({model:i,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var e=this.form.commit();return e?(this.$el.addClass("error"),e):s.Object.prototype.commit.call(this)}}),s.Date=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e);var n=s.Date,r=new Date;this.options=t.extend({monthNames:n.monthNames,showMonthNames:n.showMonthNames},e),this.schema=t.extend({yearStart:r.getFullYear()-100,yearEnd:r.getFullYear()},e.schema||{}),this.value&&!t.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var i=new Date;i.setSeconds(0),i.setMilliseconds(0),this.value=i}},render:function(){var n=this.options,i=this.schema,s=t.map(t.range(1,32),function(e){return'<option value="'+e+'">'+e+"</option>"}),o=t.map(t.range(0,12),function(e){var t=n.showMonthNames?n.monthNames[e]:e+1;return'<option value="'+e+'">'+t+"</option>"}),u=i.yearStart<i.yearEnd?t.range(i.yearStart,i.yearEnd+1):t.range(i.yearStart,i.yearEnd-1,-1),a=t.map(u,function(e){return'<option value="'+e+'">'+e+"</option>"}),f=e(r.templates.date({dates:s.join(""),months:o.join(""),years:a.join("")}));return this.$date=f.find('select[data-type="date"]'),this.$month=f.find('select[data-type="month"]'),this.$year=f.find('select[data-type="year"]'),this.$hidden=e('<input type="hidden" name="'+this.key+'" />'),f.append(this.$hidden),this.setValue(this.value),this.setElement(f),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.$year.val(),t=this.$month.val(),n=this.$date.val();return!e||!t||!n?null:new Date(e,t,n)},setValue:function(e){this.$date.val(e.getDate()),this.$month.val(e.getMonth()),this.$year.val(e.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 e=this.getValue();t.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),s.DateTime=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e),this.options=t.extend({DateEditor:s.DateTime.DateEditor},e),this.schema=t.extend({minsInterval:15},e.schema||{}),this.dateEditor=new this.options.DateEditor(e),this.value=this.dateEditor.value},render:function(){function n(e){return e<10?"0"+e:e}var i=this.schema,s=t.map(t.range(0,24),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),o=t.map(t.range(0,60,i.minsInterval),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),u=e(r.templates.dateTime({date:'<b class="bbf-tmp"></b>',hours:s.join(),mins:o.join()}));return u.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=u.find('select[data-type="hour"]'),this.$min=u.find('select[data-type="min"]'),this.$hidden=u.find('input[type="hidden"]'),this.setValue(this.value),this.setElement(u),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.dateEditor.getValue(),t=this.$hour.val(),n=this.$min.val();return!e||!t||!n?null:(e.setHours(t),e.setMinutes(n),e)},setValue:function(e){t.isDate(e)||(e=new Date(e)),this.dateEditor.setValue(e),this.$hour.val(e.getHours()),this.$min.val(e.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 e=this.getValue();t.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)},remove:function(){this.dateEditor.remove(),s.Base.prototype.remove.call(this)}},{DateEditor:s.Date}),s}(),r.setTemplates=r.helpers.setTemplates,r.setTemplateCompiler=r.helpers.setTemplateCompiler,r.templates={},r.setTemplates({form:' <form class="bbf-form">{{fieldsets}}</form> ',fieldset:" <fieldset> <legend>{{legend}}</legend> <ul>{{fields}}</ul> </fieldset> ",field:' <li class="bbf-field field-{{key}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',nestedField:' <li class="bbf-field bbf-nested-field field-{{key}}" title="{{title}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',list:' <div class="bbf-list"> <ul>{{items}}</ul> <div class="bbf-actions"><button type="button" data-action="add">Add</div> </div> ',listItem:' <li> <button type="button" data-action="remove" class="bbf-remove">&times;</button> <div class="bbf-editor-container">{{editor}}</div> </li> ',date:' <div class="bbf-date"> <select data-type="date" class="bbf-date">{{dates}}</select> <select data-type="month" class="bbf-month">{{months}}</select> <select data-type="year" class="bbf-year">{{years}}</select> </div> ',dateTime:' <div class="bbf-datetime"> <div class="bbf-date-container">{{date}}</div> <select data-type="hour">{{hours}}</select> : <select data-type="min">{{mins}}</select> </div> ',"list.Modal":' <div class="bbf-list-modal"> {{summary}} </div> '},{error:"bbf-error"}),r.VERSION="0.10.1",n.Form=r,r})
+define(["jquery","underscore","backbone"],function(e,t,n){var r=function(){return n.View.extend({hasFocus:!1,initialize:function(e){if(!r.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(e.schema)return e.schema;var n=e.model;if(!n)throw new Error("Could not find schema");return t.isFunction(n.schema)?n.schema():n.schema}(),e=t.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},e);if(!e.fieldsets){var n=e.fields||t.keys(this.schema);e.fieldsets=[{fields:n}]}this.options=e,this.model=e.model,this.data=e.data,this.fields={}},renderingContext:function(){return{fieldsets:'<b class="bbf-tmp"></b>'}},render:function(){var n=this,i=this.options,s=r.templates[i.template],o=e(s(n.renderingContext())),u=e(".bbf-tmp",o);return t.each(i.fieldsets,function(e){u.append(n.renderFieldset(e))}),u.children().unwrap(),this.setElement(o),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(n){var i=this,s=r.templates[this.options.fieldsetTemplate],o=this.schema,u=r.helpers.getNested;t.isArray(n)&&(n={fields:n});var a=e(s(t.extend({},n,{legend:'<b class="bbf-tmp-legend"></b>',fields:'<b class="bbf-tmp-fields"></b>'})));n.legend?a.find(".bbf-tmp-legend").replaceWith(n.legend):a.find(".bbf-tmp-legend").parent().remove();var f=e(".bbf-tmp-fields",a);return t.each(n.fields,function(e){var n=function(){if(o[e])return o[e];var t=e.replace(/\./g,".subSchema.");return u(o,t)}();if(!n)throw"Field '"+e+"' not found in schema";var r=i.fields[e]=i.createField(e,n),s=r.render().el;i.trigger(e+":render",i,r),i.trigger("field:render",i,r),r.editor.on("all",function(n){var r=t.toArray(arguments);r[0]=e+":"+n,r.splice(1,0,this),this.trigger.apply(this,r)},i),r.editor.on("change",function(){this.trigger("change",this),this.trigger("field:change",this,r)},i),r.editor.on("focus",function(){this.trigger("field:focus",this,r);if(this.hasFocus)return;this.trigger("focus",this)},i),r.editor.on("blur",function(){this.trigger("field:blur",this,r);if(!this.hasFocus)return;var e=this;setTimeout(function(){if(t.find(e.fields,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},i),n.type!=="Hidden"&&f.append(s),i.trigger(e+":show",i,r),i.trigger("field:show",i,r)}),f=f.children().unwrap(),a},createField:function(e,t){t.template=t.template||this.options.fieldTemplate;var r={form:this,key:e,schema:t,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};this.model?r.model=this.model:this.data?r.value=this.data[e]:r.value=null;var i=this.Field||n.Form.Field;return new i(r)},validate:function(){var e=this,n=this.fields,r=this.model,i={};t.each(n,function(e){var t=e.validate();t&&(i[e.key]=t)});if(r&&r.validate){var s=r.validate(this.getValue());if(s){var o=t.isObject(s)&&!t.isArray(s);o||(i._others=i._others||[],i._others.push(s)),o&&t.each(s,function(t,n){if(e.fields[n]&&!i[n])e.fields[n].setError(t),i[n]=t;else{i._others=i._others||[];var r={};r[n]=t,i._others.push(r)}})}}return t.isEmpty(i)?null:i},commit:function(){var e=this.validate();if(e)return e;var t;this.model.set(this.getValue(),{error:function(e,n){t=n}});if(t)return t},getValue:function(e){if(e)return this.fields[e].getValue();var n={};return t.each(this.fields,function(e){n[e.key]=e.getValue()}),n},setValue:function(e,t){var n={};typeof e=="string"?n[e]=t:n=e;var r;for(r in this.schema)n[r]!==undefined&&this.fields[r].setValue(n[r])},focus:function(){if(this.hasFocus)return;var e=this.options.fieldsets[0];if(e){var n;t.isArray(e)?n=e[0]:n=e.fields[0],n&&this.fields[n].editor.focus()}},blur:function(){if(!this.hasFocus)return;var e=t.find(this.fields,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){var e=this.fields;for(var t in e)e[t].remove();n.View.prototype.remove.call(this)},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),n.View.prototype.trigger.apply(this,arguments)}})}();return r.helpers=function(){var e={};return e.getNested=function(e,t){var n=t.split("."),r=e;for(var i=0,s=n.length;i<s;i++)r=r[n[i]];return r},e.keyToTitle=function(e){return e=e.replace(/([A-Z])/g," $1"),e=e.replace(/^./,function(e){return e.toUpperCase()}),e},e.compileTemplate=function(e){var n=t.templateSettings.interpolate;t.templateSettings.interpolate=/\{\{(.+?)\}\}/g;var r=t.template(e);return t.templateSettings.interpolate=n,r},e.createTemplate=function(t,n){var r=e.compileTemplate(t);return n?r(n):r},e.setTemplateCompiler=function(t){e.compileTemplate=t},e.setTemplates=function(n,i){var s=e.createTemplate;r.templates=r.templates||{},r.classNames=r.classNames||{},t.each(n,function(e,n,i){t.isString(e)&&(e=s(e)),r.templates[n]=e}),t.extend(r.classNames,i)},e.createEditor=function(e,n){var i;return t.isString(e)?i=r.editors[e]:i=e,new i(n)},e.triggerCancellableEvent=function(e,t,n,r){if(!e._callbacks||!e._callbacks[t])return r();var i=e._callbacks[t].next;if(!i)return r();var s=i.callback,o=i.context||this;n.push(r),s.apply(o,n)},e.getValidator=function(e){var n=r.validators;if(t.isRegExp(e))return n.regexp({regexp:e});if(t.isString(e)){if(!n[e])throw new Error('Validator "'+e+'" not found');return n[e]()}if(t.isFunction(e))return e;if(t.isObject(e)&&e.type){var i=e;return n[i.type](i)}throw new Error("Invalid validator: "+e)},e}(),r.validators=function(){var e={};return e.errMessages={required:"Required",regexp:"Invalid",email:"Invalid email address",url:"Invalid URL",match:'Must match field "{{field}}"'},e.required=function(e){return e=t.extend({type:"required",message:this.errMessages.required},e),function(n){e.value=n;var i={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return i}},e.regexp=function(e){if(!e.regexp)throw new Error('Missing required "regexp" option for "regexp" validator');return e=t.extend({type:"regexp",message:this.errMessages.regexp},e),function(n){e.value=n;var i={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(!e.regexp.test(n))return i}},e.email=function(n){return n=t.extend({type:"email",message:this.errMessages.email,regexp:/^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/},n),e.regexp(n)},e.url=function(n){return n=t.extend({type:"url",message:this.errMessages.url,regexp:/^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i},n),e.regexp(n)},e.match=function(e){if(!e.field)throw new Error('Missing required "field" options for "match" validator');return e=t.extend({type:"match",message:this.errMessages.match},e),function(n,i){e.value=n;var s={type:e.type,message:r.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(n!==i[e.field])return s}},e}(),r.Field=function(){var i=r.helpers,s=r.templates;return n.View.extend({initialize:function(e){e=e||{},this.form=e.form,this.key=e.key,this.value=e.value,this.model=e.model,t.isString(e.schema)&&(e.schema={type:e.schema}),this.schema=t.extend({type:"Text",title:i.keyToTitle(this.key),template:"field"},e.schema)},renderingContext:function(e,t){return{key:this.key,title:e.title,id:t.id,type:e.type,editor:'<b class="bbf-tmp-editor"></b>',help:'<b class="bbf-tmp-help"></b>',error:'<b class="bbf-tmp-error"></b>'}},render:function(){var t=this.schema,n=r.templates,s={form:this.form,key:this.key,schema:t,idPrefix:this.options.idPrefix,id:this.getId()};this.model?s.model=this.model:s.value=this.value;var o=this.editor=i.createEditor(t.type,s),u=e(n[t.template](this.renderingContext(t,o)));return t.title===!1&&u.find('label[for="'+o.id+'"]').first().remove(),u.find(".bbf-tmp-editor").replaceWith(o.render().el),this.$help=e(".bbf-tmp-help",u).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.$error=e(e(".bbf-tmp-error",u).parent()[0]),this.$error&&this.$error.empty(),this.schema.fieldClass&&u.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&u.attr(this.schema.fieldAttrs),this.setElement(u),this},getId:function(){var e=this.options.idPrefix,n=this.key;return n=n.replace(/\./g,"_"),t.isString(e)||t.isNumber(e)?e+n:t.isNull(e)?n:this.model?this.model.cid+"_"+n:n},validate:function(){var e=this.editor.validate();return e?this.setError(e.message):this.clearError(),e},setError:function(e){if(this.editor.hasNestedForm)return;var t=r.classNames.error;this.$el.addClass(t),this.$error?this.$error.html(e):this.$help&&this.$help.html(e)},clearError:function(){var e=r.classNames.error;this.$el.removeClass(e);if(this.$error)this.$error.empty();else if(this.$help){this.$help.empty();var t=this.schema.help;t&&this.$help.html(t)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),n.View.prototype.remove.call(this)}})}(),r.editors=function(){var i=r.helpers,s={};return s.Base=n.View.extend({defaultValue:null,hasFocus:!1,initialize:function(e){var e=e||{};if(e.model){if(!e.key)throw"Missing option: 'key'";this.model=e.model,this.value=this.model.get(e.key)}else e.value&&(this.value=e.value);this.value===undefined&&(this.value=this.defaultValue),this.key=e.key,this.form=e.form,this.schema=e.schema||{},this.validators=e.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 e=this.key||"";return e.replace(/\./g,"_")},commit:function(){var e=this.validate();if(e)return e;this.model.set(this.key,this.getValue(),{error:function(t,n){e=n}});if(e)return e},validate:function(){var e=this,n=this.$el,i=null,s=this.getValue(),o=this.form?this.form.getValue():{},u=this.validators,a=r.helpers.getValidator;return u&&t.every(u,function(t){return i=a(t).call(e,s,o),i?!1:!0}),i},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),n.View.prototype.trigger.apply(this,arguments)}}),s.Text=s.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(e){var t=this;setTimeout(function(){t.determineChange()},0)},select:function(e){this.trigger("select",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);var t=this.schema,n="text";t&&t.editorAttrs&&t.editorAttrs.type&&(n=t.editorAttrs.type),t&&t.dataType&&(n=t.dataType),this.$el.attr("type",n)},render:function(){return this.setValue(this.value),this},determineChange:function(e){var t=this.$el.val(),n=t!==this.previousValue;n&&(this.previousValue=t,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),s.Number=s.Text.extend({defaultValue:0,events:t.extend({},s.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(e){var t=this,n=function(){setTimeout(function(){t.determineChange()},0)};if(e.charCode===0){n();return}var r=this.$el.val()+String.fromCharCode(e.charCode),i=/^[0-9]*\.?[0-9]*?$/.test(r);i?n():e.preventDefault()},getValue:function(){var e=this.$el.val();return e===""?null:parseFloat(e,10)},setValue:function(e){e=function(){return t.isNumber(e)?e:t.isString(e)&&e!==""?parseFloat(e,10):null}(),t.isNaN(e)&&(e=null),s.Text.prototype.setValue.call(this,e)}}),s.Password=s.Text.extend({initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","password")}}),s.TextArea=s.Text.extend({tagName:"textarea"}),s.Checkbox=s.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(e){e&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),s.Hidden=s.Base.extend({defaultValue:"",initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){},blur:function(){}}),s.Select=s.Base.extend({tagName:"select",events:{change:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(e){var r=this;if(e instanceof n.Collection){var i=e;i.length>0?this.renderOptions(e):i.fetch({success:function(t){r.renderOptions(e)}})}else t.isFunction(e)?e.call(r,function(e){r.renderOptions(e)}):this.renderOptions(e)},renderOptions:function(e){var r=this.$el,i;t.isString(e)?i=e:t.isArray(e)?i=this._arrayToHtml(e):e instanceof n.Collection&&(i=this._collectionToHtml(e)),r.html(i),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(e){var t=[];e.each(function(e){t.push({val:e.id,label:e.toString()})});var n=this._arrayToHtml(t);return n},_arrayToHtml:function(e){var n=[];return t.each(e,function(e){if(t.isObject(e)){var r=e.val||e.val===0?e.val:"";n.push('<option value="'+r+'">'+e.label+"</option>")}else n.push("<option>"+e+"</option>")}),n.join("")}}),s.Radio=s.Select.extend({tagName:"ul",className:"bbf-radio",events:{"change input[type=radio]":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 e=this;setTimeout(function(){if(e.$("input[type=radio]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(e){this.$("input[type=radio]").val([e])},focus:function(){if(this.hasFocus)return;var e=this.$("input[type=radio]:checked");if(e[0]){e.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(e){var n=[],r=this;return t.each(e,function(e,i){var s="<li>";if(t.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="radio" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="radio" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",n.push(s)}),n.join("")}}),s.Checkboxes=s.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 e=this;setTimeout(function(){if(e.$("input[type=checkbox]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){var t=[];return this.$("input[type=checkbox]:checked").each(function(){t.push(e(this).val())}),t},setValue:function(e){t.isArray(e)||(e=[e]),this.$("input[type=checkbox]").val(e)},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(e){var n=[],r=this;return t.each(e,function(e,i){var s="<li>";if(t.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="checkbox" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="checkbox" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",n.push(s)}),n.join("")}}),s.Object=s.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(e){this.value={},s.Base.prototype.initialize.call(this,e);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new r({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(e){this.value=e,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(),n.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){var e=t.toArray(arguments);e[1]=this,this.trigger.apply(this,e)},this)}}),s.NestedModel=s.Object.extend({initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!e.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var e=this.value||{},t=this.key,n=this.schema.model,i=e.constructor===n?e:new n(e);return this.form=new r({model:i,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var e=this.form.commit();return e?(this.$el.addClass("error"),e):s.Object.prototype.commit.call(this)}}),s.Date=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e);var n=s.Date,r=new Date;this.options=t.extend({monthNames:n.monthNames,showMonthNames:n.showMonthNames},e),this.schema=t.extend({yearStart:r.getFullYear()-100,yearEnd:r.getFullYear()},e.schema||{}),this.value&&!t.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var i=new Date;i.setSeconds(0),i.setMilliseconds(0),this.value=i}},render:function(){var n=this.options,i=this.schema,s=t.map(t.range(1,32),function(e){return'<option value="'+e+'">'+e+"</option>"}),o=t.map(t.range(0,12),function(e){var t=n.showMonthNames?n.monthNames[e]:e+1;return'<option value="'+e+'">'+t+"</option>"}),u=i.yearStart<i.yearEnd?t.range(i.yearStart,i.yearEnd+1):t.range(i.yearStart,i.yearEnd-1,-1),a=t.map(u,function(e){return'<option value="'+e+'">'+e+"</option>"}),f=e(r.templates.date({dates:s.join(""),months:o.join(""),years:a.join("")}));return this.$date=f.find('select[data-type="date"]'),this.$month=f.find('select[data-type="month"]'),this.$year=f.find('select[data-type="year"]'),this.$hidden=e('<input type="hidden" name="'+this.key+'" />'),f.append(this.$hidden),this.setValue(this.value),this.setElement(f),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.$year.val(),t=this.$month.val(),n=this.$date.val();return!e||!t||!n?null:new Date(e,t,n)},setValue:function(e){this.$date.val(e.getDate()),this.$month.val(e.getMonth()),this.$year.val(e.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 e=this.getValue();t.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),s.DateTime=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e),this.options=t.extend({DateEditor:s.DateTime.DateEditor},e),this.schema=t.extend({minsInterval:15},e.schema||{}),this.dateEditor=new this.options.DateEditor(e),this.value=this.dateEditor.value},render:function(){function n(e){return e<10?"0"+e:e}var i=this.schema,s=t.map(t.range(0,24),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),o=t.map(t.range(0,60,i.minsInterval),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),u=e(r.templates.dateTime({date:'<b class="bbf-tmp"></b>',hours:s.join(),mins:o.join()}));return u.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=u.find('select[data-type="hour"]'),this.$min=u.find('select[data-type="min"]'),this.$hidden=u.find('input[type="hidden"]'),this.setValue(this.value),this.setElement(u),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.dateEditor.getValue(),t=this.$hour.val(),n=this.$min.val();return!e||!t||!n?null:(e.setHours(t),e.setMinutes(n),e)},setValue:function(e){t.isDate(e)||(e=new Date(e)),this.dateEditor.setValue(e),this.$hour.val(e.getHours()),this.$min.val(e.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 e=this.getValue();t.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)},remove:function(){this.dateEditor.remove(),s.Base.prototype.remove.call(this)}},{DateEditor:s.Date}),s}(),r.setTemplates=r.helpers.setTemplates,r.setTemplateCompiler=r.helpers.setTemplateCompiler,r.templates={},r.setTemplates({form:' <form class="bbf-form">{{fieldsets}}</form> ',fieldset:" <fieldset> <legend>{{legend}}</legend> <ul>{{fields}}</ul> </fieldset> ",field:' <li class="bbf-field field-{{key}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',nestedField:' <li class="bbf-field bbf-nested-field field-{{key}}" title="{{title}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',list:' <div class="bbf-list"> <ul>{{items}}</ul> <div class="bbf-actions"><button type="button" data-action="add">Add</div> </div> ',listItem:' <li> <button type="button" data-action="remove" class="bbf-remove">&times;</button> <div class="bbf-editor-container">{{editor}}</div> </li> ',date:' <div class="bbf-date"> <select data-type="date" class="bbf-date">{{dates}}</select> <select data-type="month" class="bbf-month">{{months}}</select> <select data-type="year" class="bbf-year">{{years}}</select> </div> ',dateTime:' <div class="bbf-datetime"> <div class="bbf-date-container">{{date}}</div> <select data-type="hour">{{hours}}</select> : <select data-type="min">{{mins}}</select> </div> ',"list.Modal":' <div class="bbf-list-modal"> {{summary}} </div> '},{error:"bbf-error"}),r.VERSION="0.10.1",n.Form=r,r})
View
33 distribution/backbone-forms.js
@@ -89,6 +89,15 @@ var Form = (function() {
},
/**
+ * The rendering context for the form template
+ */
+ renderingContext: function() {
+ return {
+ fieldsets: '<b class="bbf-tmp"></b>'
+ };
+ },
+
+ /**
* Renders the form and all fields
*/
render: function() {
@@ -97,9 +106,7 @@ var Form = (function() {
template = Form.templates[options.template];
//Create el from template
- var $form = $(template({
- fieldsets: '<b class="bbf-tmp"></b>'
- }));
+ var $form = $(template(self.renderingContext()));
//Render fieldsets
var $fieldsetContainer = $('.bbf-tmp', $form);
@@ -176,6 +183,8 @@ var Form = (function() {
//Render the fields with editors, apart from Hidden fields
var fieldEl = field.render().el;
+ self.trigger(key + ':render', self, field);
+ self.trigger('field:render', self, field);
field.editor.on('all', function(event) {
// args = ["change", editor]
@@ -188,14 +197,17 @@ var Form = (function() {
}, self);
field.editor.on('change', function() {
- this.trigger('change', self);
+ this.trigger('change', this);
+ this.trigger('field:change', this, field);
}, self);
field.editor.on('focus', function() {
+ this.trigger('field:focus', this, field);
if (this.hasFocus) return;
this.trigger('focus', this);
}, self);
field.editor.on('blur', function() {
+ this.trigger('field:blur', this, field);
if (!this.hasFocus) return;
var self = this;
setTimeout(function() {
@@ -207,6 +219,9 @@ var Form = (function() {
if (itemSchema.type !== 'Hidden') {
$fieldsContainer.append(fieldEl);
}
+
+ self.trigger(key + ':show', self, field);
+ self.trigger('field:show', self, field);
});
$fieldsContainer = $fieldsContainer.children().unwrap();
@@ -241,7 +256,8 @@ var Form = (function() {
options.value = null;
}
- return new Form.Field(options);
+ var Field = this.Field || Backbone.Form.Field;
+ return new Field(options);
},
/**
@@ -1107,7 +1123,8 @@ Form.editors = (function() {
* @return {String}
*/
validate: function() {
- var $el = this.$el,
+ var self = this,
+ $el = this.$el,
error = null,
value = this.getValue(),
formValues = this.form ? this.form.getValue() : {},
@@ -1117,7 +1134,7 @@ Form.editors = (function() {
if (validators) {
//Run through validators until an error is found
_.every(validators, function(validator) {
- error = getValidator(validator)(value, formValues);
+ error = getValidator(validator).call(self, value, formValues);
return error ? false : true;
});
@@ -1481,7 +1498,7 @@ Form.editors = (function() {
//If a function was passed, run it to get the options
else if (_.isFunction(options)) {
- options(function(result) {
+ options.call(self, function(result) {
self.renderOptions(result);
});
}
View
2  distribution/backbone-forms.min.js
@@ -1 +1 @@
-(function(e){if(typeof exports!="undefined"&&typeof require!="undefined")var t=e.jQuery||e.Zepto||e.ender||require("jquery"),n=e._||require("underscore"),r=e.Backbone||require("backbone");else var t=e.jQuery,n=e._,r=e.Backbone;var i=function(){return r.View.extend({hasFocus:!1,initialize:function(e){if(!i.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(e.schema)return e.schema;var t=e.model;if(!t)throw new Error("Could not find schema");return n.isFunction(t.schema)?t.schema():t.schema}(),e=n.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},e);if(!e.fieldsets){var t=e.fields||n.keys(this.schema);e.fieldsets=[{fields:t}]}this.options=e,this.model=e.model,this.data=e.data,this.fields={}},render:function(){var e=this,r=this.options,s=i.templates[r.template],o=t(s({fieldsets:'<b class="bbf-tmp"></b>'})),u=t(".bbf-tmp",o);return n.each(r.fieldsets,function(t){u.append(e.renderFieldset(t))}),u.children().unwrap(),this.setElement(o),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(e){var r=this,s=i.templates[this.options.fieldsetTemplate],o=this.schema,u=i.helpers.getNested;n.isArray(e)&&(e={fields:e});var a=t(s(n.extend({},e,{legend:'<b class="bbf-tmp-legend"></b>',fields:'<b class="bbf-tmp-fields"></b>'})));e.legend?a.find(".bbf-tmp-legend").replaceWith(e.legend):a.find(".bbf-tmp-legend").parent().remove();var f=t(".bbf-tmp-fields",a);return n.each(e.fields,function(e){var t=function(){if(o[e])return o[e];var t=e.replace(/\./g,".subSchema.");return u(o,t)}();if(!t)throw"Field '"+e+"' not found in schema";var i=r.fields[e]=r.createField(e,t),s=i.render().el;i.editor.on("all",function(t){var r=n.toArray(arguments);r[0]=e+":"+t,r.splice(1,0,this),this.trigger.apply(this,r)},r),i.editor.on("change",function(){this.trigger("change",r)},r),i.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},r),i.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(n.find(e.fields,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},r),t.type!=="Hidden"&&f.append(s)}),f=f.children().unwrap(),a},createField:function(e,t){t.template=t.template||this.options.fieldTemplate;var n={form:this,key:e,schema:t,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};return this.model?n.model=this.model:this.data?n.value=this.data[e]:n.value=null,new i.Field(n)},validate:function(){var e=this,t=this.fields,r=this.model,i={};n.each(t,function(e){var t=e.validate();t&&(i[e.key]=t)});if(r&&r.validate){var s=r.validate(this.getValue());if(s){var o=n.isObject(s)&&!n.isArray(s);o||(i._others=i._others||[],i._others.push(s)),o&&n.each(s,function(t,n){if(e.fields[n]&&!i[n])e.fields[n].setError(t),i[n]=t;else{i._others=i._others||[];var r={};r[n]=t,i._others.push(r)}})}}return n.isEmpty(i)?null:i},commit:function(){var e=this.validate();if(e)return e;var t;this.model.set(this.getValue(),{error:function(e,n){t=n}});if(t)return t},getValue:function(e){if(e)return this.fields[e].getValue();var t={};return n.each(this.fields,function(e){t[e.key]=e.getValue()}),t},setValue:function(e,t){var n={};typeof e=="string"?n[e]=t:n=e;var r;for(r in this.schema)n[r]!==undefined&&this.fields[r].setValue(n[r])},focus:function(){if(this.hasFocus)return;var e=this.options.fieldsets[0];if(e){var t;n.isArray(e)?t=e[0]:t=e.fields[0],t&&this.fields[t].editor.focus()}},blur:function(){if(!this.hasFocus)return;var e=n.find(this.fields,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){var e=this.fields;for(var t in e)e[t].remove();r.View.prototype.remove.call(this)},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),r.View.prototype.trigger.apply(this,arguments)}})}();i.helpers=function(){var e={};return e.getNested=function(e,t){var n=t.split("."),r=e;for(var i=0,s=n.length;i<s;i++)r=r[n[i]];return r},e.keyToTitle=function(e){return e=e.replace(/([A-Z])/g," $1"),e=e.replace(/^./,function(e){return e.toUpperCase()}),e},e.compileTemplate=function(e){var t=n.templateSettings.interpolate;n.templateSettings.interpolate=/\{\{(.+?)\}\}/g;var r=n.template(e);return n.templateSettings.interpolate=t,r},e.createTemplate=function(t,n){var r=e.compileTemplate(t);return n?r(n):r},e.setTemplateCompiler=function(t){e.compileTemplate=t},e.setTemplates=function(t,r){var s=e.createTemplate;i.templates=i.templates||{},i.classNames=i.classNames||{},n.each(t,function(e,t,r){n.isString(e)&&(e=s(e)),i.templates[t]=e}),n.extend(i.classNames,r)},e.createEditor=function(e,t){var r;return n.isString(e)?r=i.editors[e]:r=e,new r(t)},e.triggerCancellableEvent=function(e,t,n,r){if(!e._callbacks||!e._callbacks[t])return r();var i=e._callbacks[t].next;if(!i)return r();var s=i.callback,o=i.context||this;n.push(r),s.apply(o,n)},e.getValidator=function(e){var t=i.validators;if(n.isRegExp(e))return t.regexp({regexp:e});if(n.isString(e)){if(!t[e])throw new Error('Validator "'+e+'" not found');return t[e]()}if(n.isFunction(e))return e;if(n.isObject(e)&&e.type){var r=e;return t[r.type](r)}throw new Error("Invalid validator: "+e)},e}(),i.validators=function(){var e={};return e.errMessages={required:"Required",regexp:"Invalid",email:"Invalid email address",url:"Invalid URL",match:'Must match field "{{field}}"'},e.required=function(e){return e=n.extend({type:"required",message:this.errMessages.required},e),function(n){e.value=n;var r={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return r}},e.regexp=function(e){if(!e.regexp)throw new Error('Missing required "regexp" option for "regexp" validator');return e=n.extend({type:"regexp",message:this.errMessages.regexp},e),function(n){e.value=n;var r={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(!e.regexp.test(n))return r}},e.email=function(t){return t=n.extend({type:"email",message:this.errMessages.email,regexp:/^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/},t),e.regexp(t)},e.url=function(t){return t=n.extend({type:"url",message:this.errMessages.url,regexp:/^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i},t),e.regexp(t)},e.match=function(e){if(!e.field)throw new Error('Missing required "field" options for "match" validator');return e=n.extend({type:"match",message:this.errMessages.match},e),function(n,r){e.value=n;var s={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(n!==r[e.field])return s}},e}(),i.Field=function(){var e=i.helpers,s=i.templates;return r.View.extend({initialize:function(t){t=t||{},this.form=t.form,this.key=t.key,this.value=t.value,this.model=t.model,n.isString(t.schema)&&(t.schema={type:t.schema}),this.schema=n.extend({type:"Text",title:e.keyToTitle(this.key),template:"field"},t.schema)},renderingContext:function(e,t){return{key:this.key,title:e.title,id:t.id,type:e.type,editor:'<b class="bbf-tmp-editor"></b>',help:'<b class="bbf-tmp-help"></b>',error:'<b class="bbf-tmp-error"></b>'}},render:function(){var n=this.schema,r=i.templates,s={form:this.form,key:this.key,schema:n,idPrefix:this.options.idPrefix,id:this.getId()};this.model?s.model=this.model:s.value=this.value;var o=this.editor=e.createEditor(n.type,s),u=t(r[n.template](this.renderingContext(n,o)));return n.title===!1&&u.find('label[for="'+o.id+'"]').first().remove(),u.find(".bbf-tmp-editor").replaceWith(o.render().el),this.$help=t(".bbf-tmp-help",u).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.$error=t(t(".bbf-tmp-error",u).parent()[0]),this.$error&&this.$error.empty(),this.schema.fieldClass&&u.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&u.attr(this.schema.fieldAttrs),this.setElement(u),this},getId:function(){var e=this.options.idPrefix,t=this.key;return t=t.replace(/\./g,"_"),n.isString(e)||n.isNumber(e)?e+t:n.isNull(e)?t:this.model?this.model.cid+"_"+t:t},validate:function(){var e=this.editor.validate();return e?this.setError(e.message):this.clearError(),e},setError:function(e){if(this.editor.hasNestedForm)return;var t=i.classNames.error;this.$el.addClass(t),this.$error?this.$error.html(e):this.$help&&this.$help.html(e)},clearError:function(){var e=i.classNames.error;this.$el.removeClass(e);if(this.$error)this.$error.empty();else if(this.$help){this.$help.empty();var t=this.schema.help;t&&this.$help.html(t)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),r.View.prototype.remove.call(this)}})}(),i.editors=function(){var e=i.helpers,s={};return s.Base=r.View.extend({defaultValue:null,hasFocus:!1,initialize:function(e){var e=e||{};if(e.model){if(!e.key)throw"Missing option: 'key'";this.model=e.model,this.value=this.model.get(e.key)}else e.value&&(this.value=e.value);this.value===undefined&&(this.value=this.defaultValue),this.key=e.key,this.form=e.form,this.schema=e.schema||{},this.validators=e.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 e=this.key||"";return e.replace(/\./g,"_")},commit:function(){var e=this.validate();if(e)return e;this.model.set(this.key,this.getValue(),{error:function(t,n){e=n}});if(e)return e},validate:function(){var e=this.$el,t=null,r=this.getValue(),s=this.form?this.form.getValue():{},o=this.validators,u=i.helpers.getValidator;return o&&n.every(o,function(e){return t=u(e)(r,s),t?!1:!0}),t},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),r.View.prototype.trigger.apply(this,arguments)}}),s.Text=s.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(e){var t=this;setTimeout(function(){t.determineChange()},0)},select:function(e){this.trigger("select",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);var t=this.schema,n="text";t&&t.editorAttrs&&t.editorAttrs.type&&(n=t.editorAttrs.type),t&&t.dataType&&(n=t.dataType),this.$el.attr("type",n)},render:function(){return this.setValue(this.value),this},determineChange:function(e){var t=this.$el.val(),n=t!==this.previousValue;n&&(this.previousValue=t,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),s.Number=s.Text.extend({defaultValue:0,events:n.extend({},s.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(e){var t=this,n=function(){setTimeout(function(){t.determineChange()},0)};if(e.charCode===0){n();return}var r=this.$el.val()+String.fromCharCode(e.charCode),i=/^[0-9]*\.?[0-9]*?$/.test(r);i?n():e.preventDefault()},getValue:function(){var e=this.$el.val();return e===""?null:parseFloat(e,10)},setValue:function(e){e=function(){return n.isNumber(e)?e:n.isString(e)&&e!==""?parseFloat(e,10):null}(),n.isNaN(e)&&(e=null),s.Text.prototype.setValue.call(this,e)}}),s.Password=s.Text.extend({initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","password")}}),s.TextArea=s.Text.extend({tagName:"textarea"}),s.Checkbox=s.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(e){e&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),s.Hidden=s.Base.extend({defaultValue:"",initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){},blur:function(){}}),s.Select=s.Base.extend({tagName:"select",events:{change:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(e){var t=this;if(e instanceof r.Collection){var i=e;i.length>0?this.renderOptions(e):i.fetch({success:function(n){t.renderOptions(e)}})}else n.isFunction(e)?e(function(e){t.renderOptions(e)}):this.renderOptions(e)},renderOptions:function(e){var t=this.$el,i;n.isString(e)?i=e:n.isArray(e)?i=this._arrayToHtml(e):e instanceof r.Collection&&(i=this._collectionToHtml(e)),t.html(i),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(e){var t=[];e.each(function(e){t.push({val:e.id,label:e.toString()})});var n=this._arrayToHtml(t);return n},_arrayToHtml:function(e){var t=[];return n.each(e,function(e){if(n.isObject(e)){var r=e.val||e.val===0?e.val:"";t.push('<option value="'+r+'">'+e.label+"</option>")}else t.push("<option>"+e+"</option>")}),t.join("")}}),s.Radio=s.Select.extend({tagName:"ul",className:"bbf-radio",events:{"change input[type=radio]":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 e=this;setTimeout(function(){if(e.$("input[type=radio]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(e){this.$("input[type=radio]").val([e])},focus:function(){if(this.hasFocus)return;var e=this.$("input[type=radio]:checked");if(e[0]){e.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(e){var t=[],r=this;return n.each(e,function(e,i){var s="<li>";if(n.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="radio" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="radio" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",t.push(s)}),t.join("")}}),s.Checkboxes=s.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 e=this;setTimeout(function(){if(e.$("input[type=checkbox]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){var e=[];return this.$("input[type=checkbox]:checked").each(function(){e.push(t(this).val())}),e},setValue:function(e){n.isArray(e)||(e=[e]),this.$("input[type=checkbox]").val(e)},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(e){var t=[],r=this;return n.each(e,function(e,i){var s="<li>";if(n.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="checkbox" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="checkbox" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",t.push(s)}),t.join("")}}),s.Object=s.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(e){this.value={},s.Base.prototype.initialize.call(this,e);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new i({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(e){this.value=e,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(),r.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){var e=n.toArray(arguments);e[1]=this,this.trigger.apply(this,e)},this)}}),s.NestedModel=s.Object.extend({initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!e.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var e=this.value||{},t=this.key,n=this.schema.model,r=e.constructor===n?e:new n(e);return this.form=new i({model:r,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var e=this.form.commit();return e?(this.$el.addClass("error"),e):s.Object.prototype.commit.call(this)}}),s.Date=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e);var t=s.Date,r=new Date;this.options=n.extend({monthNames:t.monthNames,showMonthNames:t.showMonthNames},e),this.schema=n.extend({yearStart:r.getFullYear()-100,yearEnd:r.getFullYear()},e.schema||{}),this.value&&!n.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var i=new Date;i.setSeconds(0),i.setMilliseconds(0),this.value=i}},render:function(){var e=this.options,r=this.schema,s=n.map(n.range(1,32),function(e){return'<option value="'+e+'">'+e+"</option>"}),o=n.map(n.range(0,12),function(t){var n=e.showMonthNames?e.monthNames[t]:t+1;return'<option value="'+t+'">'+n+"</option>"}),u=r.yearStart<r.yearEnd?n.range(r.yearStart,r.yearEnd+1):n.range(r.yearStart,r.yearEnd-1,-1),a=n.map(u,function(e){return'<option value="'+e+'">'+e+"</option>"}),f=t(i.templates.date({dates:s.join(""),months:o.join(""),years:a.join("")}));return this.$date=f.find('select[data-type="date"]'),this.$month=f.find('select[data-type="month"]'),this.$year=f.find('select[data-type="year"]'),this.$hidden=t('<input type="hidden" name="'+this.key+'" />'),f.append(this.$hidden),this.setValue(this.value),this.setElement(f),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.$year.val(),t=this.$month.val(),n=this.$date.val();return!e||!t||!n?null:new Date(e,t,n)},setValue:function(e){this.$date.val(e.getDate()),this.$month.val(e.getMonth()),this.$year.val(e.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 e=this.getValue();n.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),s.DateTime=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e),this.options=n.extend({DateEditor:s.DateTime.DateEditor},e),this.schema=n.extend({minsInterval:15},e.schema||{}),this.dateEditor=new this.options.DateEditor(e),this.value=this.dateEditor.value},render:function(){function e(e){return e<10?"0"+e:e}var r=this.schema,s=n.map(n.range(0,24),function(t){return'<option value="'+t+'">'+e(t)+"</option>"}),o=n.map(n.range(0,60,r.minsInterval),function(t){return'<option value="'+t+'">'+e(t)+"</option>"}),u=t(i.templates.dateTime({date:'<b class="bbf-tmp"></b>',hours:s.join(),mins:o.join()}));return u.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=u.find('select[data-type="hour"]'),this.$min=u.find('select[data-type="min"]'),this.$hidden=u.find('input[type="hidden"]'),this.setValue(this.value),this.setElement(u),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.dateEditor.getValue(),t=this.$hour.val(),n=this.$min.val();return!e||!t||!n?null:(e.setHours(t),e.setMinutes(n),e)},setValue:function(e){n.isDate(e)||(e=new Date(e)),this.dateEditor.setValue(e),this.$hour.val(e.getHours()),this.$min.val(e.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 e=this.getValue();n.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)},remove:function(){this.dateEditor.remove(),s.Base.prototype.remove.call(this)}},{DateEditor:s.Date}),s}(),i.setTemplates=i.helpers.setTemplates,i.setTemplateCompiler=i.helpers.setTemplateCompiler,i.templates={},i.setTemplates({form:' <form class="bbf-form">{{fieldsets}}</form> ',fieldset:" <fieldset> <legend>{{legend}}</legend> <ul>{{fields}}</ul> </fieldset> ",field:' <li class="bbf-field field-{{key}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',nestedField:' <li class="bbf-field bbf-nested-field field-{{key}}" title="{{title}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',list:' <div class="bbf-list"> <ul>{{items}}</ul> <div class="bbf-actions"><button type="button" data-action="add">Add</div> </div> ',listItem:' <li> <button type="button" data-action="remove" class="bbf-remove">&times;</button> <div class="bbf-editor-container">{{editor}}</div> </li> ',date:' <div class="bbf-date"> <select data-type="date" class="bbf-date">{{dates}}</select> <select data-type="month" class="bbf-month">{{months}}</select> <select data-type="year" class="bbf-year">{{years}}</select> </div> ',dateTime:' <div class="bbf-datetime"> <div class="bbf-date-container">{{date}}</div> <select data-type="hour">{{hours}}</select> : <select data-type="min">{{mins}}</select> </div> ',"list.Modal":' <div class="bbf-list-modal"> {{summary}} </div> '},{error:"bbf-error"}),i.VERSION="0.10.1",r.Form=i})(this)
+(function(e){if(typeof exports!="undefined"&&typeof require!="undefined")var t=e.jQuery||e.Zepto||e.ender||require("jquery"),n=e._||require("underscore"),r=e.Backbone||require("backbone");else var t=e.jQuery,n=e._,r=e.Backbone;var i=function(){return r.View.extend({hasFocus:!1,initialize:function(e){if(!i.templates.form)throw new Error("Templates not loaded");this.schema=function(){if(e.schema)return e.schema;var t=e.model;if(!t)throw new Error("Could not find schema");return n.isFunction(t.schema)?t.schema():t.schema}(),e=n.extend({template:"form",fieldsetTemplate:"fieldset",fieldTemplate:"field"},e);if(!e.fieldsets){var t=e.fields||n.keys(this.schema);e.fieldsets=[{fields:t}]}this.options=e,this.model=e.model,this.data=e.data,this.fields={}},renderingContext:function(){return{fieldsets:'<b class="bbf-tmp"></b>'}},render:function(){var e=this,r=this.options,s=i.templates[r.template],o=t(s(e.renderingContext())),u=t(".bbf-tmp",o);return n.each(r.fieldsets,function(t){u.append(e.renderFieldset(t))}),u.children().unwrap(),this.setElement(o),this.hasFocus&&this.trigger("blur",this),this},renderFieldset:function(e){var r=this,s=i.templates[this.options.fieldsetTemplate],o=this.schema,u=i.helpers.getNested;n.isArray(e)&&(e={fields:e});var a=t(s(n.extend({},e,{legend:'<b class="bbf-tmp-legend"></b>',fields:'<b class="bbf-tmp-fields"></b>'})));e.legend?a.find(".bbf-tmp-legend").replaceWith(e.legend):a.find(".bbf-tmp-legend").parent().remove();var f=t(".bbf-tmp-fields",a);return n.each(e.fields,function(e){var t=function(){if(o[e])return o[e];var t=e.replace(/\./g,".subSchema.");return u(o,t)}();if(!t)throw"Field '"+e+"' not found in schema";var i=r.fields[e]=r.createField(e,t),s=i.render().el;r.trigger(e+":render",r,i),r.trigger("field:render",r,i),i.editor.on("all",function(t){var r=n.toArray(arguments);r[0]=e+":"+t,r.splice(1,0,this),this.trigger.apply(this,r)},r),i.editor.on("change",function(){this.trigger("change",this),this.trigger("field:change",this,i)},r),i.editor.on("focus",function(){this.trigger("field:focus",this,i);if(this.hasFocus)return;this.trigger("focus",this)},r),i.editor.on("blur",function(){this.trigger("field:blur",this,i);if(!this.hasFocus)return;var e=this;setTimeout(function(){if(n.find(e.fields,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},r),t.type!=="Hidden"&&f.append(s),r.trigger(e+":show",r,i),r.trigger("field:show",r,i)}),f=f.children().unwrap(),a},createField:function(e,t){t.template=t.template||this.options.fieldTemplate;var n={form:this,key:e,schema:t,idPrefix:this.options.idPrefix,template:this.options.fieldTemplate};this.model?n.model=this.model:this.data?n.value=this.data[e]:n.value=null;var i=this.Field||r.Form.Field;return new i(n)},validate:function(){var e=this,t=this.fields,r=this.model,i={};n.each(t,function(e){var t=e.validate();t&&(i[e.key]=t)});if(r&&r.validate){var s=r.validate(this.getValue());if(s){var o=n.isObject(s)&&!n.isArray(s);o||(i._others=i._others||[],i._others.push(s)),o&&n.each(s,function(t,n){if(e.fields[n]&&!i[n])e.fields[n].setError(t),i[n]=t;else{i._others=i._others||[];var r={};r[n]=t,i._others.push(r)}})}}return n.isEmpty(i)?null:i},commit:function(){var e=this.validate();if(e)return e;var t;this.model.set(this.getValue(),{error:function(e,n){t=n}});if(t)return t},getValue:function(e){if(e)return this.fields[e].getValue();var t={};return n.each(this.fields,function(e){t[e.key]=e.getValue()}),t},setValue:function(e,t){var n={};typeof e=="string"?n[e]=t:n=e;var r;for(r in this.schema)n[r]!==undefined&&this.fields[r].setValue(n[r])},focus:function(){if(this.hasFocus)return;var e=this.options.fieldsets[0];if(e){var t;n.isArray(e)?t=e[0]:t=e.fields[0],t&&this.fields[t].editor.focus()}},blur:function(){if(!this.hasFocus)return;var e=n.find(this.fields,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){var e=this.fields;for(var t in e)e[t].remove();r.View.prototype.remove.call(this)},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),r.View.prototype.trigger.apply(this,arguments)}})}();i.helpers=function(){var e={};return e.getNested=function(e,t){var n=t.split("."),r=e;for(var i=0,s=n.length;i<s;i++)r=r[n[i]];return r},e.keyToTitle=function(e){return e=e.replace(/([A-Z])/g," $1"),e=e.replace(/^./,function(e){return e.toUpperCase()}),e},e.compileTemplate=function(e){var t=n.templateSettings.interpolate;n.templateSettings.interpolate=/\{\{(.+?)\}\}/g;var r=n.template(e);return n.templateSettings.interpolate=t,r},e.createTemplate=function(t,n){var r=e.compileTemplate(t);return n?r(n):r},e.setTemplateCompiler=function(t){e.compileTemplate=t},e.setTemplates=function(t,r){var s=e.createTemplate;i.templates=i.templates||{},i.classNames=i.classNames||{},n.each(t,function(e,t,r){n.isString(e)&&(e=s(e)),i.templates[t]=e}),n.extend(i.classNames,r)},e.createEditor=function(e,t){var r;return n.isString(e)?r=i.editors[e]:r=e,new r(t)},e.triggerCancellableEvent=function(e,t,n,r){if(!e._callbacks||!e._callbacks[t])return r();var i=e._callbacks[t].next;if(!i)return r();var s=i.callback,o=i.context||this;n.push(r),s.apply(o,n)},e.getValidator=function(e){var t=i.validators;if(n.isRegExp(e))return t.regexp({regexp:e});if(n.isString(e)){if(!t[e])throw new Error('Validator "'+e+'" not found');return t[e]()}if(n.isFunction(e))return e;if(n.isObject(e)&&e.type){var r=e;return t[r.type](r)}throw new Error("Invalid validator: "+e)},e}(),i.validators=function(){var e={};return e.errMessages={required:"Required",regexp:"Invalid",email:"Invalid email address",url:"Invalid URL",match:'Must match field "{{field}}"'},e.required=function(e){return e=n.extend({type:"required",message:this.errMessages.required},e),function(n){e.value=n;var r={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return r}},e.regexp=function(e){if(!e.regexp)throw new Error('Missing required "regexp" option for "regexp" validator');return e=n.extend({type:"regexp",message:this.errMessages.regexp},e),function(n){e.value=n;var r={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(!e.regexp.test(n))return r}},e.email=function(t){return t=n.extend({type:"email",message:this.errMessages.email,regexp:/^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/},t),e.regexp(t)},e.url=function(t){return t=n.extend({type:"url",message:this.errMessages.url,regexp:/^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i},t),e.regexp(t)},e.match=function(e){if(!e.field)throw new Error('Missing required "field" options for "match" validator');return e=n.extend({type:"match",message:this.errMessages.match},e),function(n,r){e.value=n;var s={type:e.type,message:i.helpers.createTemplate(e.message,e)};if(n===null||n===undefined||n==="")return;if(n!==r[e.field])return s}},e}(),i.Field=function(){var e=i.helpers,s=i.templates;return r.View.extend({initialize:function(t){t=t||{},this.form=t.form,this.key=t.key,this.value=t.value,this.model=t.model,n.isString(t.schema)&&(t.schema={type:t.schema}),this.schema=n.extend({type:"Text",title:e.keyToTitle(this.key),template:"field"},t.schema)},renderingContext:function(e,t){return{key:this.key,title:e.title,id:t.id,type:e.type,editor:'<b class="bbf-tmp-editor"></b>',help:'<b class="bbf-tmp-help"></b>',error:'<b class="bbf-tmp-error"></b>'}},render:function(){var n=this.schema,r=i.templates,s={form:this.form,key:this.key,schema:n,idPrefix:this.options.idPrefix,id:this.getId()};this.model?s.model=this.model:s.value=this.value;var o=this.editor=e.createEditor(n.type,s),u=t(r[n.template](this.renderingContext(n,o)));return n.title===!1&&u.find('label[for="'+o.id+'"]').first().remove(),u.find(".bbf-tmp-editor").replaceWith(o.render().el),this.$help=t(".bbf-tmp-help",u).parent(),this.$help.empty(),this.schema.help&&this.$help.html(this.schema.help),this.$error=t(t(".bbf-tmp-error",u).parent()[0]),this.$error&&this.$error.empty(),this.schema.fieldClass&&u.addClass(this.schema.fieldClass),this.schema.fieldAttrs&&u.attr(this.schema.fieldAttrs),this.setElement(u),this},getId:function(){var e=this.options.idPrefix,t=this.key;return t=t.replace(/\./g,"_"),n.isString(e)||n.isNumber(e)?e+t:n.isNull(e)?t:this.model?this.model.cid+"_"+t:t},validate:function(){var e=this.editor.validate();return e?this.setError(e.message):this.clearError(),e},setError:function(e){if(this.editor.hasNestedForm)return;var t=i.classNames.error;this.$el.addClass(t),this.$error?this.$error.html(e):this.$help&&this.$help.html(e)},clearError:function(){var e=i.classNames.error;this.$el.removeClass(e);if(this.$error)this.$error.empty();else if(this.$help){this.$help.empty();var t=this.schema.help;t&&this.$help.html(t)}},commit:function(){return this.editor.commit()},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),r.View.prototype.remove.call(this)}})}(),i.editors=function(){var e=i.helpers,s={};return s.Base=r.View.extend({defaultValue:null,hasFocus:!1,initialize:function(e){var e=e||{};if(e.model){if(!e.key)throw"Missing option: 'key'";this.model=e.model,this.value=this.model.get(e.key)}else e.value&&(this.value=e.value);this.value===undefined&&(this.value=this.defaultValue),this.key=e.key,this.form=e.form,this.schema=e.schema||{},this.validators=e.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 e=this.key||"";return e.replace(/\./g,"_")},commit:function(){var e=this.validate();if(e)return e;this.model.set(this.key,this.getValue(),{error:function(t,n){e=n}});if(e)return e},validate:function(){var e=this,t=this.$el,r=null,s=this.getValue(),o=this.form?this.form.getValue():{},u=this.validators,a=i.helpers.getValidator;return u&&n.every(u,function(t){return r=a(t).call(e,s,o),r?!1:!0}),r},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),r.View.prototype.trigger.apply(this,arguments)}}),s.Text=s.Base.extend({tagName:"input",defaultValue:"",previousValue:"",events:{keyup:"determineChange",keypress:function(e){var t=this;setTimeout(function(){t.determineChange()},0)},select:function(e){this.trigger("select",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);var t=this.schema,n="text";t&&t.editorAttrs&&t.editorAttrs.type&&(n=t.editorAttrs.type),t&&t.dataType&&(n=t.dataType),this.$el.attr("type",n)},render:function(){return this.setValue(this.value),this},determineChange:function(e){var t=this.$el.val(),n=t!==this.previousValue;n&&(this.previousValue=t,this.trigger("change",this))},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},select:function(){this.$el.select()}}),s.Number=s.Text.extend({defaultValue:0,events:n.extend({},s.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","number"),this.$el.attr("step","any")},onKeyPress:function(e){var t=this,n=function(){setTimeout(function(){t.determineChange()},0)};if(e.charCode===0){n();return}var r=this.$el.val()+String.fromCharCode(e.charCode),i=/^[0-9]*\.?[0-9]*?$/.test(r);i?n():e.preventDefault()},getValue:function(){var e=this.$el.val();return e===""?null:parseFloat(e,10)},setValue:function(e){e=function(){return n.isNumber(e)?e:n.isString(e)&&e!==""?parseFloat(e,10):null}(),n.isNaN(e)&&(e=null),s.Text.prototype.setValue.call(this,e)}}),s.Password=s.Text.extend({initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","password")}}),s.TextArea=s.Text.extend({tagName:"textarea"}),s.Checkbox=s.Base.extend({defaultValue:!1,tagName:"input",events:{click:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e),this.$el.attr("type","checkbox")},render:function(){return this.setValue(this.value),this},getValue:function(){return this.$el.prop("checked")||undefined},setValue:function(e){e&&this.$el.prop("checked",!0)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()}}),s.Hidden=s.Base.extend({defaultValue:"",initialize:function(e){s.Text.prototype.initialize.call(this,e),this.$el.attr("type","hidden")},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){},blur:function(){}}),s.Select=s.Base.extend({tagName:"select",events:{change:function(e){this.trigger("change",this)},focus:function(e){this.trigger("focus",this)},blur:function(e){this.trigger("blur",this)}},initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!this.schema||!this.schema.options)throw"Missing required 'schema.options'"},render:function(){return this.setOptions(this.schema.options),this},setOptions:function(e){var t=this;if(e instanceof r.Collection){var i=e;i.length>0?this.renderOptions(e):i.fetch({success:function(n){t.renderOptions(e)}})}else n.isFunction(e)?e.call(t,function(e){t.renderOptions(e)}):this.renderOptions(e)},renderOptions:function(e){var t=this.$el,i;n.isString(e)?i=e:n.isArray(e)?i=this._arrayToHtml(e):e instanceof r.Collection&&(i=this._collectionToHtml(e)),t.html(i),this.setValue(this.value)},getValue:function(){return this.$el.val()},setValue:function(e){this.$el.val(e)},focus:function(){if(this.hasFocus)return;this.$el.focus()},blur:function(){if(!this.hasFocus)return;this.$el.blur()},_collectionToHtml:function(e){var t=[];e.each(function(e){t.push({val:e.id,label:e.toString()})});var n=this._arrayToHtml(t);return n},_arrayToHtml:function(e){var t=[];return n.each(e,function(e){if(n.isObject(e)){var r=e.val||e.val===0?e.val:"";t.push('<option value="'+r+'">'+e.label+"</option>")}else t.push("<option>"+e+"</option>")}),t.join("")}}),s.Radio=s.Select.extend({tagName:"ul",className:"bbf-radio",events:{"change input[type=radio]":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 e=this;setTimeout(function(){if(e.$("input[type=radio]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){return this.$("input[type=radio]:checked").val()},setValue:function(e){this.$("input[type=radio]").val([e])},focus:function(){if(this.hasFocus)return;var e=this.$("input[type=radio]:checked");if(e[0]){e.focus();return}this.$("input[type=radio]").first().focus()},blur:function(){if(!this.hasFocus)return;this.$("input[type=radio]:focus").blur()},_arrayToHtml:function(e){var t=[],r=this;return n.each(e,function(e,i){var s="<li>";if(n.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="radio" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="radio" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",t.push(s)}),t.join("")}}),s.Checkboxes=s.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 e=this;setTimeout(function(){if(e.$("input[type=checkbox]:focus")[0])return;e.trigger("blur",e)},0)}},getValue:function(){var e=[];return this.$("input[type=checkbox]:checked").each(function(){e.push(t(this).val())}),e},setValue:function(e){n.isArray(e)||(e=[e]),this.$("input[type=checkbox]").val(e)},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(e){var t=[],r=this;return n.each(e,function(e,i){var s="<li>";if(n.isObject(e)){var o=e.val||e.val===0?e.val:"";s+='<input type="checkbox" name="'+r.id+'" value="'+o+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e.label+"</label>"}else s+='<input type="checkbox" name="'+r.id+'" value="'+e+'" id="'+r.id+"-"+i+'" />',s+='<label for="'+r.id+"-"+i+'">'+e+"</label>";s+="</li>",t.push(s)}),t.join("")}}),s.Object=s.Base.extend({hasNestedForm:!0,className:"bbf-object",initialize:function(e){this.value={},s.Base.prototype.initialize.call(this,e);if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){return this.form=new i({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(e){this.value=e,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(),r.View.prototype.remove.call(this)},validate:function(){return this.form.validate()},_observeFormEvents:function(){this.form.on("all",function(){var e=n.toArray(arguments);e[1]=this,this.trigger.apply(this,e)},this)}}),s.NestedModel=s.Object.extend({initialize:function(e){s.Base.prototype.initialize.call(this,e);if(!e.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var e=this.value||{},t=this.key,n=this.schema.model,r=e.constructor===n?e:new n(e);return this.form=new i({model:r,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.form.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var e=this.form.commit();return e?(this.$el.addClass("error"),e):s.Object.prototype.commit.call(this)}}),s.Date=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e);var t=s.Date,r=new Date;this.options=n.extend({monthNames:t.monthNames,showMonthNames:t.showMonthNames},e),this.schema=n.extend({yearStart:r.getFullYear()-100,yearEnd:r.getFullYear()},e.schema||{}),this.value&&!n.isDate(this.value)&&(this.value=new Date(this.value));if(!this.value){var i=new Date;i.setSeconds(0),i.setMilliseconds(0),this.value=i}},render:function(){var e=this.options,r=this.schema,s=n.map(n.range(1,32),function(e){return'<option value="'+e+'">'+e+"</option>"}),o=n.map(n.range(0,12),function(t){var n=e.showMonthNames?e.monthNames[t]:t+1;return'<option value="'+t+'">'+n+"</option>"}),u=r.yearStart<r.yearEnd?n.range(r.yearStart,r.yearEnd+1):n.range(r.yearStart,r.yearEnd-1,-1),a=n.map(u,function(e){return'<option value="'+e+'">'+e+"</option>"}),f=t(i.templates.date({dates:s.join(""),months:o.join(""),years:a.join("")}));return this.$date=f.find('select[data-type="date"]'),this.$month=f.find('select[data-type="month"]'),this.$year=f.find('select[data-type="year"]'),this.$hidden=t('<input type="hidden" name="'+this.key+'" />'),f.append(this.$hidden),this.setValue(this.value),this.setElement(f),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.$year.val(),t=this.$month.val(),n=this.$date.val();return!e||!t||!n?null:new Date(e,t,n)},setValue:function(e){this.$date.val(e.getDate()),this.$month.val(e.getMonth()),this.$year.val(e.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 e=this.getValue();n.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)}},{showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),s.DateTime=s.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 e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},s.Base.prototype.initialize.call(this,e),this.options=n.extend({DateEditor:s.DateTime.DateEditor},e),this.schema=n.extend({minsInterval:15},e.schema||{}),this.dateEditor=new this.options.DateEditor(e),this.value=this.dateEditor.value},render:function(){function e(e){return e<10?"0"+e:e}var r=this.schema,s=n.map(n.range(0,24),function(t){return'<option value="'+t+'">'+e(t)+"</option>"}),o=n.map(n.range(0,60,r.minsInterval),function(t){return'<option value="'+t+'">'+e(t)+"</option>"}),u=t(i.templates.dateTime({date:'<b class="bbf-tmp"></b>',hours:s.join(),mins:o.join()}));return u.find(".bbf-tmp").replaceWith(this.dateEditor.render().el),this.$hour=u.find('select[data-type="hour"]'),this.$min=u.find('select[data-type="min"]'),this.$hidden=u.find('input[type="hidden"]'),this.setValue(this.value),this.setElement(u),this.$el.attr("id",this.id),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){var e=this.dateEditor.getValue(),t=this.$hour.val(),n=this.$min.val();return!e||!t||!n?null:(e.setHours(t),e.setMinutes(n),e)},setValue:function(e){n.isDate(e)||(e=new Date(e)),this.dateEditor.setValue(e),this.$hour.val(e.getHours()),this.$min.val(e.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 e=this.getValue();n.isDate(e)&&(e=e.toISOString()),this.$hidden.val(e)},remove:function(){this.dateEditor.remove(),s.Base.prototype.remove.call(this)}},{DateEditor:s.Date}),s}(),i.setTemplates=i.helpers.setTemplates,i.setTemplateCompiler=i.helpers.setTemplateCompiler,i.templates={},i.setTemplates({form:' <form class="bbf-form">{{fieldsets}}</form> ',fieldset:" <fieldset> <legend>{{legend}}</legend> <ul>{{fields}}</ul> </fieldset> ",field:' <li class="bbf-field field-{{key}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',nestedField:' <li class="bbf-field bbf-nested-field field-{{key}}" title="{{title}}"> <label for="{{id}}">{{title}}</label> <div class="bbf-editor">{{editor}}</div> <div class="bbf-help">{{help}}</div> <div class="bbf-error">{{error}}</div> </li> ',list:' <div class="bbf-list"> <ul>{{items}}</ul> <div class="bbf-actions"><button type="button" data-action="add">Add</div> </div> ',listItem:' <li> <button type="button" data-action="remove" class="bbf-remove">&times;</button> <div class="bbf-editor-container">{{editor}}</div> </li> ',date:' <div class="bbf-date"> <select data-type="date" class="bbf-date">{{dates}}</select> <select data-type="month" class="bbf-month">{{months}}</select> <select data-type="year" class="bbf-year">{{years}}</select> </div> ',dateTime:' <div class="bbf-datetime"> <div class="bbf-date-container">{{date}}</div> <select data-type="hour">{{hours}}</select> : <select data-type="min">{{mins}}</select> </div> ',"list.Modal":' <div class="bbf-list-modal"> {{summary}} </div> '},{error:"bbf-error"}),i.VERSION="0.10.1",r.Form=i})(this)
View
7 src/editors.js
@@ -112,7 +112,8 @@ Form.editors = (function() {
* @return {String}
*/
validate: function() {
- var $el = this.$el,
+ var self = this,
+ $el = this.$el,
error = null,
value = this.getValue(),
formValues = this.form ? this.form.getValue() : {},
@@ -122,7 +123,7 @@ Form.editors = (function() {
if (validators) {
//Run through validators until an error is found
_.every(validators, function(validator) {
- error = getValidator(validator)(value, formValues);
+ error = getValidator(validator).call(self, value, formValues);
return error ? false : true;
});
@@ -486,7 +487,7 @@ Form.editors = (function() {
//If a function was passed, run it to get the options
else if (_.isFunction(options)) {
- options(function(result) {
+ options.call(self, function(result) {
self.renderOptions(result);
});
}
View
26 src/form.js
@@ -62,6 +62,15 @@ var Form = (function() {
},
/**
+ * The rendering context for the form template
+ */
+ renderingContext: function() {
+ return {
+ fieldsets: '<b class="bbf-tmp"></b>'
+ };
+ },
+
+ /**
* Renders the form and all fields
*/
render: function() {
@@ -70,9 +79,7 @@ var Form = (function() {
template = Form.templates[options.template];
//Create el from template
- var $form = $(template({
- fieldsets: '<b class="bbf-tmp"></b>'
- }));
+ var $form = $(template(self.renderingContext()));
//Render fieldsets
var $fieldsetContainer = $('.bbf-tmp', $form);
@@ -149,6 +156,8 @@ var Form = (function() {
//Render the fields with editors, apart from Hidden fields
var fieldEl = field.render().el;
+ self.trigger(key + ':render', self, field);
+ self.trigger('field:render', self, field);
field.editor.on('all', function(event) {
// args = ["change", editor]
@@ -161,14 +170,17 @@ var Form = (function() {
}, self);
field.editor.on('change', function() {
- this.trigger('change', self);
+ this.trigger('change', this);
+ this.trigger('field:change', this, field);
}, self);
field.editor.on('focus', function() {
+ this.trigger('field:focus', this, field);
if (this.hasFocus) return;
this.trigger('focus', this);
}, self);
field.editor.on('blur', function() {
+ this.trigger('field:blur', this, field);
if (!this.hasFocus) return;
var self = this;
setTimeout(function() {
@@ -180,6 +192,9 @@ var Form = (function() {
if (itemSchema.type !== 'Hidden') {
$fieldsContainer.append(fieldEl);
}
+
+ self.trigger(key + ':show', self, field);
+ self.trigger('field:show', self, field);
});
$fieldsContainer = $fieldsContainer.children().unwrap();
@@ -214,7 +229,8 @@ var Form = (function() {
options.value = null;
}
- return new Form.Field(options);
+ var Field = this.Field || Backbone.Form.Field;
+ return new Field(options);
},
/**
View
70 test/form.js
@@ -554,6 +554,61 @@ test("Events bubbling up from editors", function() {
ok(spy.calledWith(form, form.fields.title.editor));
});
+test("'change'/'focus'/'blur' events generically bubble up from editors", function() {
+ var form = new Form({
+ model: new Post
+ }).render();
+
+ var spy = this.sinon.spy();
+
+ form.on('field:change field:focus field:blur', spy);
+
+ form.fields.title.editor.trigger('focus', form.fields.title.editor);
+ form.fields.title.editor.trigger('change', form.fields.title.editor);
+ form.fields.title.editor.trigger('blur', form.fields.title.editor);
+
+ ok(spy.callCount == 3);
+ ok(spy.calledWith(form, form.fields.title));
+});
+
+test("'focus'/'blur' events bubble up from editors symmetrically", function() {
+ var form = new Form({
+ model: new Post
+ }).render();
+ // XXX:eo note these must be in the DOM in order to work
+ // since blur is not triggered on un-rooted fragments
+ $('body').append(form.$el);
+
+ var spy = this.sinon.spy();
+
+ form.on('field:focus field:blur', spy);
+
+ form.fields.title.editor.$el.focus();
+ form.fields.content.editor.$el.focus();
+
+ ok(spy.callCount == 3);
+ ok(spy.calledWith(form, form.fields.title));
+ ok(spy.calledWith(form, form.fields.content));
+
+ form.$el.remove();
+});
+
+test("field 'render'/'show' events generated when form is rendered", function() {
+ var form = new Form({
+ model: new Post
+ });
+
+ var spy = this.sinon.spy();
+
+ form.on('field:render title:show', spy);
+
+ form.render();
+
+ // individual field:render's + title:show
+ ok(spy.callCount == _.keys(form.model.schema).length + 1);
+ ok(spy.calledWith(form, form.fields.title));
+});
+
test('Allows access to field views', function() {
var form = new Form({
model: new Post
@@ -589,6 +644,21 @@ test("Supports picking nested fields from within Objects", function() {
ok(form.fields['author.name.last'].editor instanceof Form.editors.Text);
});
+test("Supports using custom Field types", function() {
+ var MyField = Field.extend({
+ special: true
+ });
+
+ var MyForm = Form.extend({
+ Field: MyField
+ });
+
+ var form = new MyForm({
+ model: new Post
+ }).render();
+
+ ok(form.fields['title'].special);
+});
})(Backbone.Form, Backbone.Form.Field, Backbone.Form.editors);
Something went wrong with that request. Please try again.