http://git-wip-us.apache.org/repos/asf/ambari/blob/39c85bb8/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.js ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.js b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.js new file mode 100644 index 0000000..13dfe62 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.js @@ -0,0 +1,2365 @@ +/** + * Backbone Forms v0.12.0 + * + * NOTE: + * This version is for use with RequireJS + * If using regular <script> tags to include your files, use backbone-forms.min.js + * + * Copyright (c) 2013 Charles Davison, Pow Media Ltd + * + * License and more information at: + * http://github.com/powmedia/backbone-forms + */ +define(['jquery', 'underscore', 'backbone'], function($, _, Backbone) { + + +//================================================================================================== +//FORM +//================================================================================================== + +var Form = Backbone.View.extend({ + + /** + * Constructor + * + * @param {Object} [options.schema] + * @param {Backbone.Model} [options.model] + * @param {Object} [options.data] + * @param {String[]|Object[]} [options.fieldsets] + * @param {String[]} [options.fields] + * @param {String} [options.idPrefix] + * @param {Form.Field} [options.Field] + * @param {Form.Fieldset} [options.Fieldset] + * @param {Function} [options.template] + */ + initialize: function(options) { + var self = this; + + options = options || {}; + + //Find the schema to use + var schema = this.schema = (function() { + //Prefer schema from options + if (options.schema) return _.result(options, 'schema'); + + //Then schema on model + var model = options.model; + if (model && model.schema) { + return (_.isFunction(model.schema)) ? model.schema() : model.schema; + } + + //Then built-in schema + if (self.schema) { + return (_.isFunction(self.schema)) ? self.schema() : self.schema; + } + + //Fallback to empty schema + return {}; + })(); + + //Store important data + _.extend(this, _.pick(options, 'model', 'data', 'idPrefix')); + + //Override defaults + var constructor = this.constructor; + this.template = options.template || constructor.template; + this.Fieldset = options.Fieldset || constructor.Fieldset; + this.Field = options.Field || constructor.Field; + this.NestedField = options.NestedField || constructor.NestedField; + + //Check which fields will be included (defaults to all) + var selectedFields = this.selectedFields = options.fields || _.keys(schema); + + //Create fields + var fields = this.fields = {}; + + _.each(selectedFields, function(key) { + var fieldSchema = schema[key]; + fields[key] = this.createField(key, fieldSchema); + }, this); + + //Create fieldsets + var fieldsetSchema = options.fieldsets || [selectedFields], + fieldsets = this.fieldsets = []; + + _.each(fieldsetSchema, function(itemSchema) { + this.fieldsets.push(this.createFieldset(itemSchema)); + }, this); + }, + + /** + * Creates a Fieldset instance + * + * @param {String[]|Object[]} schema Fieldset schema + * + * @return {Form.Fieldset} + */ + createFieldset: function(schema) { + var options = { + schema: schema, + fields: this.fields + }; + + return new this.Fieldset(options); + }, + + /** + * Creates a Field instance + * + * @param {String} key + * @param {Object} schema Field schema + * + * @return {Form.Field} + */ + createField: function(key, schema) { + var options = { + form: this, + key: key, + schema: schema, + idPrefix: this.idPrefix + }; + + if (this.model) { + options.model = this.model; + } else if (this.data) { + options.value = this.data[key]; + } else { + options.value = null; + } + + var field = new this.Field(options); + + this.listenTo(field.editor, 'all', this.handleEditorEvent); + + return field; + }, + + /** + * Callback for when an editor event is fired. + * Re-triggers events on the form as key:event and triggers additional form-level events + * + * @param {String} event + * @param {Editor} editor + */ + handleEditorEvent: function(event, editor) { + //Re-trigger editor events on the form + var formEvent = editor.key+':'+event; + + this.trigger.call(this, formEvent, this, editor); + + //Trigger additional events + switch (event) { + case 'change': + this.trigger('change', this); + break; + + case 'focus': + if (!this.hasFocus) this.trigger('focus', this); + break; + + case 'blur': + if (this.hasFocus) { + //TODO: Is the timeout etc needed? + var self = this; + setTimeout(function() { + var focusedField = _.find(self.fields, function(field) { + return field.editor.hasFocus; + }); + + if (!focusedField) self.trigger('blur', self); + }, 0); + } + break; + } + }, + + render: function() { + var self = this, + fields = this.fields; + + //Render form + var $form = $($.trim(this.template(_.result(this, 'templateData')))); + + //Render standalone editors + $form.find('[data-editors]').add($form).each(function(i, el) { + var $container = $(el), + selection = $container.attr('data-editors'); + + if (_.isUndefined(selection)) return; + + //Work out which fields to include + var keys = (selection == '*') + ? self.selectedFields || _.keys(fields) + : selection.split(','); + + //Add them + _.each(keys, function(key) { + var field = fields[key]; + + $container.append(field.editor.render().el); + }); + }); + + //Render standalone fields + $form.find('[data-fields]').add($form).each(function(i, el) { + var $container = $(el), + selection = $container.attr('data-fields'); + + if (_.isUndefined(selection)) return; + + //Work out which fields to include + var keys = (selection == '*') + ? self.selectedFields || _.keys(fields) + : selection.split(','); + + //Add them + _.each(keys, function(key) { + var field = fields[key]; + + $container.append(field.render().el); + }); + }); + + //Render fieldsets + $form.find('[data-fieldsets]').add($form).each(function(i, el) { + var $container = $(el), + selection = $container.attr('data-fieldsets'); + + if (_.isUndefined(selection)) return; + + _.each(self.fieldsets, function(fieldset) { + $container.append(fieldset.render().el); + }); + }); + + //Set the main element + this.setElement($form); + + return this; + }, + + /** + * Validate the data + * + * @return {Object} Validation errors + */ + validate: function() { + var self = this, + fields = this.fields, + model = this.model, + errors = {}; + + //Collect errors from schema validation + _.each(fields, function(field) { + var error = field.validate(); + if (error) { + errors[field.key] = error; + } + }); + + //Get errors from default Backbone model validator + if (model && model.validate) { + var modelErrors = model.validate(this.getValue()); + + if (modelErrors) { + var isDictionary = _.isObject(modelErrors) && !_.isArray(modelErrors); + + //If errors are not in object form then just store on the error object + if (!isDictionary) { + errors._others = errors._others || []; + errors._others.push(modelErrors); + } + + //Merge programmatic errors (requires model.validate() to return an object e.g. { fieldKey: 'error' }) + if (isDictionary) { + _.each(modelErrors, function(val, key) { + //Set error on field if there isn't one already + if (fields[key] && !errors[key]) { + fields[key].setError(val); + errors[key] = val; + } + + else { + //Otherwise add to '_others' key + errors._others = errors._others || []; + var tmpErr = {}; + tmpErr[key] = val; + errors._others.push(tmpErr); + } + }); + } + } + } + + return _.isEmpty(errors) ? null : errors; + }, + + /** + * Update the model with all latest values. + * + * @param {Object} [options] Options to pass to Model#set (e.g. { silent: true }) + * + * @return {Object} Validation errors + */ + commit: function(options) { + //Validate + var errors = this.validate(); + if (errors){ + var vFirstField = this.fields[_.keys(errors)[0]]; + if(vFirstField){ + $("html, body").animate({ + scrollTop: vFirstField.$el.position().top-80 + }, 1100, function(){ + if(options.focusFirstField) vFirstField.editor.$el.focus(); + }); + } + return errors; + } + + //Commit + var modelError; + + var setOptions = _.extend({ + error: function(model, e) { + modelError = e; + } + }, options); + + this.model.set(this.getValue(), setOptions); + + if (modelError) return modelError; + }, + + /** + * Get all the field values as an object. + * Use this method when passing data instead of objects + * + * @param {String} [key] Specific field value to get + */ + getValue: function(key) { + //Return only given key if specified + if (key) return this.fields[key].getValue(); + + //Otherwise return entire form + var values = {}; + _.each(this.fields, function(field) { + values[field.key] = field.getValue(); + }); + + return values; + }, + + /** + * Update field values, referenced by key + * + * @param {Object|String} key New values to set, or property to set + * @param val Value to set + */ + setValue: function(prop, val) { + var data = {}; + if (typeof prop === 'string') { + data[prop] = val; + } else { + data = prop; + } + + var key; + for (key in this.schema) { + if (data[key] !== undefined) { + this.fields[key].setValue(data[key]); + } + } + }, + + /** + * Returns the editor for a given field key + * + * @param {String} key + * + * @return {Editor} + */ + getEditor: function(key) { + var field = this.fields[key]; + if (!field) throw 'Field not found: '+key; + + return field.editor; + }, + + /** + * Gives the first editor in the form focus + */ + focus: function() { + if (this.hasFocus) return; + + //Get the first field + var fieldset = this.fieldsets[0], + field = fieldset.getFieldAt(0); + + if (!field) return; + + //Set focus + field.editor.focus(); + }, + + /** + * Removes focus from the currently focused editor + */ + blur: function() { + if (!this.hasFocus) return; + + var focusedField = _.find(this.fields, function(field) { + return field.editor.hasFocus; + }); + + if (focusedField) focusedField.editor.blur(); + }, + + /** + * Manages the hasFocus property + * + * @param {String} event + */ + trigger: function(event) { + if (event === 'focus') { + this.hasFocus = true; + } + else if (event === 'blur') { + this.hasFocus = false; + } + + return Backbone.View.prototype.trigger.apply(this, arguments); + }, + + /** + * Override default remove function in order to remove embedded views + * + * TODO: If editors are included directly with data-editors="x", they need to be removed + * May be best to use XView to manage adding/removing views + */ + remove: function() { + _.each(this.fieldsets, function(fieldset) { + fieldset.remove(); + }); + + _.each(this.fields, function(field) { + field.remove(); + }); + + Backbone.View.prototype.remove.call(this); + } + +}, { + + //STATICS + template: _.template('\ + <form data-fieldsets></form>\ + ', null, this.templateSettings), + + templateSettings: { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }, + + editors: {} + +}); + + +//================================================================================================== +//VALIDATORS +//================================================================================================== + +Form.validators = (function() { + + var validators = {}; + + validators.errMessages = { + required: 'Required', + regexp: 'Invalid', + email: 'Invalid email address', + url: 'Invalid URL', + match: _.template('Must match field "<%= field %>"', null, Form.templateSettings) + }; + + validators.required = function(options) { + options = _.extend({ + type: 'required', + message: this.errMessages.required + }, options); + + return function required(value) { + options.value = value; + + var err = { + type: options.type, + message: _.isFunction(options.message) ? options.message(options) : options.message + }; + + if (value === null || value === undefined || value === false || value === '') return err; + }; + }; + + validators.regexp = function(options) { + if (!options.regexp) throw new Error('Missing required "regexp" option for "regexp" validator'); + + options = _.extend({ + type: 'regexp', + message: this.errMessages.regexp + }, options); + + return function regexp(value) { + options.value = value; + + var err = { + type: options.type, + message: _.isFunction(options.message) ? options.message(options) : options.message + }; + + //Don't check empty values (add a 'required' validator for this) + if (value === null || value === undefined || value === '') return; + + if (!options.regexp.test(value)) return err; + }; + }; + + validators.email = function(options) { + options = _.extend({ + type: 'email', + message: this.errMessages.email, + regexp: /^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/ + }, options); + + return validators.regexp(options); + }; + + validators.url = function(options) { + options = _.extend({ + type: 'url', + message: this.errMessages.url, + regexp: /^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i + }, options); + + return validators.regexp(options); + }; + + validators.match = function(options) { + if (!options.field) throw new Error('Missing required "field" options for "match" validator'); + + options = _.extend({ + type: 'match', + message: this.errMessages.match + }, options); + + return function match(value, attrs) { + options.value = value; + + var err = { + type: options.type, + message: _.isFunction(options.message) ? options.message(options) : options.message + }; + + //Don't check empty values (add a 'required' validator for this) + if (value === null || value === undefined || value === '') return; + + if (value !== attrs[options.field]) return err; + }; + }; + + + return validators; + +})(); + + +//================================================================================================== +//FIELDSET +//================================================================================================== + +Form.Fieldset = Backbone.View.extend({ + + /** + * Constructor + * + * Valid fieldset schemas: + * ['field1', 'field2'] + * { legend: 'Some Fieldset', fields: ['field1', 'field2'] } + * + * @param {String[]|Object[]} options.schema Fieldset schema + * @param {Object} options.fields Form fields + */ + initialize: function(options) { + options = options || {}; + + //Create the full fieldset schema, merging defaults etc. + var schema = this.schema = this.createSchema(options.schema); + + //Store the fields for this fieldset + this.fields = _.pick(options.fields, schema.fields); + + //Override defaults + this.template = options.template || this.constructor.template; + }, + + /** + * Creates the full fieldset schema, normalising, merging defaults etc. + * + * @param {String[]|Object[]} schema + * + * @return {Object} + */ + createSchema: function(schema) { + //Normalise to object + if (_.isArray(schema)) { + schema = { fields: schema }; + } + + //Add null legend to prevent template error + schema.legend = schema.legend || null; + + return schema; + }, + + /** + * Returns the field for a given index + * + * @param {Number} index + * + * @return {Field} + */ + getFieldAt: function(index) { + var key = this.schema.fields[index]; + + return this.fields[key]; + }, + + /** + * Returns data to pass to template + * + * @return {Object} + */ + templateData: function() { + return this.schema; + }, + + /** + * Renders the fieldset and fields + * + * @return {Fieldset} this + */ + render: function() { + var schema = this.schema, + fields = this.fields; + + //Render fieldset + var $fieldset = $($.trim(this.template(_.result(this, 'templateData')))); + + //Render fields + $fieldset.find('[data-fields]').add($fieldset).each(function(i, el) { + var $container = $(el), + selection = $container.attr('data-fields'); + + if (_.isUndefined(selection)) return; + + _.each(fields, function(field) { + $container.append(field.render().el); + }); + }); + + this.setElement($fieldset); + + return this; + }, + + /** + * Remove embedded views then self + */ + remove: function() { + _.each(this.fields, function(field) { + field.remove(); + }); + + Backbone.View.prototype.remove.call(this); + } + +}, { + //STATICS + + template: _.template('\ + <fieldset data-fields>\ + <% if (legend) { %>\ + <legend><%= legend %></legend>\ + <% } %>\ + </fieldset>\ + ', null, Form.templateSettings) + +}); + + +//================================================================================================== +//FIELD +//================================================================================================== + +Form.Field = Backbone.View.extend({ + + /** + * Constructor + * + * @param {Object} options.key + * @param {Object} options.form + * @param {Object} [options.schema] + * @param {Backbone.Model} [options.model] + * @param {Object} [options.value] + * @param {String} [options.idPrefix] + * @param {Function} [options.template] + * @param {Function} [options.errorClassName] + */ + initialize: function(options) { + options = options || {}; + + //Store important data + _.extend(this, _.pick(options, 'form', 'key', 'model', 'value', 'idPrefix')); + + //Override defaults + this.template = options.template || this.constructor.template; + this.errorClassName = options.errorClassName || this.constructor.errorClassName; + + //Create the full field schema, merging defaults etc. + this.schema = this.createSchema(options.schema); + + //Create editor + this.editor = this.createEditor(); + }, + + /** + * Creates the full field schema, merging defaults etc. + * + * @param {Object|String} schema + * + * @return {Object} + */ + createSchema: function(schema) { + if (_.isString(schema)) schema = { type: schema }; + + //Set defaults + schema = _.extend({ + type: 'Text', + title: this.createTitle() + }, schema); + + //Get the real constructor function i.e. if type is a string such as 'Text' + schema.type = (_.isString(schema.type)) ? Form.editors[schema.type] : schema.type; + + return schema; + }, + + /** + * Creates the editor specified in the schema; either an editor string name or + * a constructor function + * + * @return {View} + */ + createEditor: function() { + var options = _.extend( + _.pick(this, 'schema', 'form', 'key', 'model', 'value'), + { id: this.createEditorId() } + ); + + var constructorFn = this.schema.type; + + return new constructorFn(options); + }, + + /** + * Creates the ID that will be assigned to the editor + * + * @return {String} + */ + createEditorId: function() { + var prefix = this.idPrefix, + id = this.key; + + //Replace periods with underscores (e.g. for when using paths) + id = id.replace(/\./g, '_'); + + //If a specific ID prefix is set, use it + if (_.isString(prefix) || _.isNumber(prefix)) return prefix + id; + if (_.isNull(prefix)) return id; + + //Otherwise, if there is a model use it's CID to avoid conflicts when multiple forms are on the page + if (this.model) return this.model.cid + '_' + id; + + return id; + }, + + /** + * Create the default field title (label text) from the key name. + * (Converts 'camelCase' to 'Camel Case') + * + * @return {String} + */ + createTitle: function() { + var str = this.key; + + //Add spaces + str = str.replace(/([A-Z])/g, ' $1'); + + //Uppercase first character + str = str.replace(/^./, function(str) { return str.toUpperCase(); }); + + return str; + }, + + /** + * Returns the data to be passed to the template + * + * @return {Object} + */ + templateData: function() { + var schema = this.schema; + + return { + help: schema.help || '', + title: schema.title, + fieldAttrs: schema.fieldAttrs, + editorAttrs: schema.editorAttrs, + key: this.key, + editorId: this.editor.id + }; + }, + + /** + * Render the field and editor + * + * @return {Field} self + */ + render: function() { + var schema = this.schema, + editor = this.editor; + + //Render field + var $field = $($.trim(this.template(_.result(this, 'templateData')))); + + if (schema.fieldClass) $field.addClass(schema.fieldClass); + if (schema.fieldAttrs) $field.attr(schema.fieldAttrs); + + //Render editor + $field.find('[data-editor]').add($field).each(function(i, el) { + var $container = $(el), + selection = $container.attr('data-editor'); + + if (_.isUndefined(selection)) return; + + $container.append(editor.render().el); + }); + + this.setElement($field); + + return this; + }, + + /** + * Check the validity of the field + * + * @return {String} + */ + validate: function() { + var error = this.editor.validate(); + + if (error) { + this.setError(error.message); + } else { + this.clearError(); + } + + return error; + }, + + /** + * Set the field into an error state, adding the error class and setting the error message + * + * @param {String} msg Error message + */ + setError: function(msg) { + //Nested form editors (e.g. Object) set their errors internally + if (this.editor.hasNestedForm) return; + + //Add error CSS class + this.$el.addClass(this.errorClassName); + + //Set error message + this.$('[data-error]').html(msg); + }, + + /** + * Clear the error state and reset the help message + */ + clearError: function() { + //Remove error CSS class + this.$el.removeClass(this.errorClassName); + + //Clear error message + this.$('[data-error]').empty(); + }, + + /** + * Update the model with the new value from the editor + * + * @return {Mixed} + */ + commit: function() { + return this.editor.commit(); + }, + + /** + * Get the value from the editor + * + * @return {Mixed} + */ + getValue: function() { + return this.editor.getValue(); + }, + + /** + * Set/change the value of the editor + * + * @param {Mixed} value + */ + setValue: function(value) { + this.editor.setValue(value); + }, + + /** + * Give the editor focus + */ + focus: function() { + this.editor.focus(); + }, + + /** + * Remove focus from the editor + */ + blur: function() { + this.editor.blur(); + }, + + /** + * Remove the field and editor views + */ + remove: function() { + this.editor.remove(); + + Backbone.View.prototype.remove.call(this); + } + +}, { + //STATICS + + template: _.template('\ + <div>\ + <label for="<%= editorId %>"><%= title %></label>\ + <div>\ + <span data-editor></span>\ + <div data-error></div>\ + <div><%= help %></div>\ + </div>\ + </div>\ + ', null, Form.templateSettings), + + /** + * CSS class name added to the field when there is a validation error + */ + errorClassName: 'error' + +}); + + +//================================================================================================== +//NESTEDFIELD +//================================================================================================== + +Form.NestedField = Form.Field.extend({ + + template: _.template($.trim('\ + <div>\ + <span data-editor></span>\ + <% if (help) { %>\ + <div><%= help %></div>\ + <% } %>\ + <div data-error></div>\ + </div>\ + '), null, Form.templateSettings) + +}); + +/** + * Base editor (interface). To be extended, not used directly + * + * @param {Object} options + * @param {String} [options.id] Editor ID + * @param {Model} [options.model] Use instead of value, and use commit() + * @param {String} [options.key] The model attribute key. Required when using 'model' + * @param {Mixed} [options.value] When not using a model. If neither provided, defaultValue will be used + * @param {Object} [options.schema] Field schema; may be required by some editors + * @param {Object} [options.validators] Validators; falls back to those stored on schema + * @param {Object} [options.form] The form + */ +Form.Editor = Form.editors.Base = Backbone.View.extend({ + + defaultValue: null, + + hasFocus: false, + + initialize: function(options) { + var options = options || {}; + + //Set initial value + if (options.model) { + if (!options.key) throw "Missing option: 'key'"; + + this.model = options.model; + + this.value = this.model.get(options.key); + } + else if (options.value) { + this.value = options.value; + } + + if (this.value === undefined) this.value = this.defaultValue; + + //Store important data + _.extend(this, _.pick(options, 'key', 'form')); + + var schema = this.schema = options.schema || {}; + + this.validators = options.validators || schema.validators; + + //Main attributes + this.$el.attr('id', this.id); + this.$el.attr('name', this.getName()); + if (schema.editorClass) this.$el.addClass(schema.editorClass); + if (schema.editorAttrs) this.$el.attr(schema.editorAttrs); + }, + + /** + * Get the value for the form input 'name' attribute + * + * @return {String} + * + * @api private + */ + getName: function() { + var key = this.key || ''; + + //Replace periods with underscores (e.g. for when using paths) + return key.replace(/\./g, '_'); + }, + + /** + * Get editor value + * Extend and override this method to reflect changes in the DOM + * + * @return {Mixed} + */ + getValue: function() { + return this.value; + }, + + /** + * Set editor value + * Extend and override this method to reflect changes in the DOM + * + * @param {Mixed} value + */ + setValue: function(value) { + this.value = value; + }, + + /** + * Give the editor focus + * Extend and override this method + */ + focus: function() { + throw 'Not implemented'; + }, + + /** + * Remove focus from the editor + * Extend and override this method + */ + blur: function() { + throw 'Not implemented'; + }, + + /** + * Update the model with the current value + * + * @param {Object} [options] Options to pass to model.set() + * @param {Boolean} [options.validate] Set to true to trigger built-in model validation + * + * @return {Mixed} error + */ + commit: function(options) { + var error = this.validate(); + if (error) return error; + + this.listenTo(this.model, 'invalid', function(model, e) { + error = e; + }); + this.model.set(this.key, this.getValue(), options); + + if (error) return error; + }, + + /** + * Check validity + * + * @return {Object|Undefined} + */ + validate: function() { + var $el = this.$el, + error = null, + value = this.getValue(), + formValues = this.form ? this.form.getValue() : {}, + validators = this.validators, + getValidator = this.getValidator; + + if (validators) { + //Run through validators until an error is found + _.every(validators, function(validator) { + error = getValidator(validator)(value, formValues); + + return error ? false : true; + }); + } + + return error; + }, + + /** + * Set this.hasFocus, or call parent trigger() + * + * @param {String} event + */ + trigger: function(event) { + if (event === 'focus') { + this.hasFocus = true; + } + else if (event === 'blur') { + this.hasFocus = false; + } + + return Backbone.View.prototype.trigger.apply(this, arguments); + }, + + /** + * Returns a validation function based on the type defined in the schema + * + * @param {RegExp|String|Function} validator + * @return {Function} + */ + getValidator: function(validator) { + var validators = Form.validators; + + //Convert regular expressions to validators + if (_.isRegExp(validator)) { + return validators.regexp({ regexp: validator }); + } + + //Use a built-in validator if given a string + if (_.isString(validator)) { + if (!validators[validator]) throw new Error('Validator "'+validator+'" not found'); + + return validators[validator](); + } + + //Functions can be used directly + if (_.isFunction(validator)) return validator; + + //Use a customised built-in validator if given an object + if (_.isObject(validator) && validator.type) { + var config = validator; + + return validators[config.type](config); + } + + //Unkown validator type + throw new Error('Invalid validator: ' + validator); + } +}); + +/** + * Text + * + * Text input with focus, blur and change events + */ +Form.editors.Text = Form.Editor.extend({ + + tagName: 'input', + + defaultValue: '', + + previousValue: '', + + events: { + 'keyup': 'determineChange', + 'keypress': function(event) { + var self = this; + setTimeout(function() { + self.determineChange(); + }, 0); + }, + 'select': function(event) { + this.trigger('select', this); + }, + 'focus': function(event) { + this.trigger('focus', this); + }, + 'blur': function(event) { + var trimmedVal = $.trim(this.$el.val()); + this.$el.val(trimmedVal); + this.trigger('blur', this); + } + }, + + initialize: function(options) { + Form.editors.Base.prototype.initialize.call(this, options); + + var schema = this.schema; + + //Allow customising text type (email, phone etc.) for HTML5 browsers + var type = 'text'; + + if (schema && schema.editorAttrs && schema.editorAttrs.type) type = schema.editorAttrs.type; + if (schema && schema.dataType) type = schema.dataType; + + this.$el.attr('type', type); + }, + + /** + * Adds the editor to the DOM + */ + render: function() { + this.setValue(this.value); + + return this; + }, + + determineChange: function(event) { + var currentValue = this.$el.val(); + var changed = (currentValue !== this.previousValue); + + if (changed) { + this.previousValue = currentValue; + + this.trigger('change', this); + } + }, + + /** + * Returns the current editor value + * @return {String} + */ + getValue: function() { + return this.$el.val(); + }, + + /** + * Sets the value of the form element + * @param {String} + */ + setValue: function(value) { + this.$el.val(value); + }, + + focus: function() { + if (this.hasFocus) return; + + this.$el.focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$el.blur(); + }, + + select: function() { + this.$el.select(); + } + +}); + +/** + * TextArea editor + */ +Form.editors.TextArea = Form.editors.Text.extend({ + + tagName: 'textarea' + +}); + +/** + * Password editor + */ +Form.editors.Password = Form.editors.Text.extend({ + + initialize: function(options) { + Form.editors.Text.prototype.initialize.call(this, options); + + this.$el.attr('type', 'password'); + } + +}); + +/** + * NUMBER + * + * Normal text input that only allows a number. Letters etc. are not entered. + */ +Form.editors.Number = Form.editors.Text.extend({ + + defaultValue: 0, + + events: _.extend({}, Form.editors.Text.prototype.events, { + 'keypress': 'onKeyPress' + }), + + initialize: function(options) { + Form.editors.Text.prototype.initialize.call(this, options); + + this.$el.attr('type', 'number'); + this.$el.attr('step', 'any'); + }, + + /** + * Check value is numeric + */ + onKeyPress: function(event) { + var self = this, + delayedDetermineChange = function() { + setTimeout(function() { + self.determineChange(); + }, 0); + }; + + //Allow backspace + if (event.charCode === 0) { + delayedDetermineChange(); + return; + } + + //Get the whole new value so that we can prevent things like double decimals points etc. + var newVal = this.$el.val() + String.fromCharCode(event.charCode); + + var numeric = /^[0-9]*\.?[0-9]*?$/.test(newVal); + + if (numeric) { + delayedDetermineChange(); + } + else { + event.preventDefault(); + } + }, + + getValue: function() { + var value = this.$el.val(); + + return value === "" ? null : parseFloat(value, 10); + }, + + setValue: function(value) { + value = (function() { + if (_.isNumber(value)) return value; + + if (_.isString(value) && value !== '') return parseFloat(value, 10); + + return null; + })(); + + if (_.isNaN(value)) value = null; + + Form.editors.Text.prototype.setValue.call(this, value); + } + +}); + +/** + * Hidden editor + */ +Form.editors.Hidden = Form.editors.Base.extend({ + + defaultValue: '', + + initialize: function(options) { + Form.editors.Text.prototype.initialize.call(this, options); + + this.$el.attr('type', 'hidden'); + }, + + focus: function() { + + }, + + blur: function() { + + } + +}); + +/** + * Checkbox editor + * + * Creates a single checkbox, i.e. boolean value + */ +Form.editors.Checkbox = Form.editors.Base.extend({ + + defaultValue: false, + + tagName: 'input', + + events: { + 'click': function(event) { + this.trigger('change', this); + }, + 'focus': function(event) { + this.trigger('focus', this); + }, + 'blur': function(event) { + this.trigger('blur', this); + } + }, + + initialize: function(options) { + Form.editors.Base.prototype.initialize.call(this, options); + + this.$el.attr('type', 'checkbox'); + }, + + /** + * Adds the editor to the DOM + */ + render: function() { + this.setValue(this.value); + + return this; + }, + + getValue: function() { + return this.$el.prop('checked'); + }, + + setValue: function(value) { + if (value) { + this.$el.prop('checked', true); + } + }, + + focus: function() { + if (this.hasFocus) return; + + this.$el.focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$el.blur(); + } + +}); + +/** + * Select editor + * + * Renders a <select> with given options + * + * Requires an 'options' value on the schema. + * Can be an array of options, a function that calls back with the array of options, a string of HTML + * or a Backbone collection. If a collection, the models must implement a toString() method + */ +Form.editors.Select = Form.editors.Base.extend({ + + tagName: 'select', + + events: { + 'change': function(event) { + this.trigger('change', this); + }, + 'focus': function(event) { + this.trigger('focus', this); + }, + 'blur': function(event) { + this.trigger('blur', this); + } + }, + + initialize: function(options) { + Form.editors.Base.prototype.initialize.call(this, options); + + if (!this.schema || !this.schema.options) throw "Missing required 'schema.options'"; + }, + + render: function() { + this.setOptions(this.schema.options); + + return this; + }, + + /** + * Sets the options that populate the <select> + * + * @param {Mixed} options + */ + setOptions: function(options) { + var self = this; + + //If a collection was passed, check if it needs fetching + if (options instanceof Backbone.Collection) { + var collection = options; + + //Don't do the fetch if it's already populated + if (collection.length > 0) { + this.renderOptions(options); + } else { + collection.fetch({ + success: function(collection) { + self.renderOptions(options); + } + }); + } + } + + //If a function was passed, run it to get the options + else if (_.isFunction(options)) { + options(function(result) { + self.renderOptions(result); + }, self); + } + + //Otherwise, ready to go straight to renderOptions + else { + this.renderOptions(options); + } + }, + + /** + * Adds the <option> html to the DOM + * @param {Mixed} Options as a simple array e.g. ['option1', 'option2'] + * or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}] + * or as a string of <option> HTML to insert into the <select> + */ + renderOptions: function(options) { + var $select = this.$el, + html; + + html = this._getOptionsHtml(options); + + //Insert options + $select.html(html); + + //Select correct option + this.setValue(this.value); + }, + + _getOptionsHtml: function(options) { + var html; + //Accept string of HTML + if (_.isString(options)) { + html = options; + } + + //Or array + else if (_.isArray(options)) { + html = this._arrayToHtml(options); + } + + //Or Backbone collection + else if (options instanceof Backbone.Collection) { + html = this._collectionToHtml(options); + } + + else if (_.isFunction(options)) { + var newOptions; + + options(function(opts) { + newOptions = opts; + }, this); + + html = this._getOptionsHtml(newOptions); + } + + return html; + }, + + getValue: function() { + return this.$el.val(); + }, + + setValue: function(value) { + this.$el.val(value); + }, + + focus: function() { + if (this.hasFocus) return; + + this.$el.focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$el.blur(); + }, + + /** + * Transforms a collection into HTML ready to use in the renderOptions method + * @param {Backbone.Collection} + * @return {String} + */ + _collectionToHtml: function(collection) { + //Convert collection to array first + var array = []; + collection.each(function(model) { + array.push({ val: model.id, label: model.toString() }); + }); + + //Now convert to HTML + var html = this._arrayToHtml(array); + + return html; + }, + + /** + * Create the <option> HTML + * @param {Array} Options as a simple array e.g. ['option1', 'option2'] + * or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}] + * @return {String} HTML + */ + _arrayToHtml: function(array) { + var html = []; + + //Generate HTML + _.each(array, function(option) { + if (_.isObject(option)) { + if (option.group) { + html.push('<optgroup label="'+option.group+'">'); + html.push(this._getOptionsHtml(option.options)) + html.push('</optgroup>'); + } else { + var val = (option.val || option.val === 0) ? option.val : ''; + html.push('<option value="'+val+'">'+option.label+'</option>'); + } + } + else { + html.push('<option>'+option+'</option>'); + } + }, this); + + return html.join(''); + } + +}); + +/** + * Radio editor + * + * Renders a <ul> with given options represented as <li> objects containing radio buttons + * + * Requires an 'options' value on the schema. + * Can be an array of options, a function that calls back with the array of options, a string of HTML + * or a Backbone collection. If a collection, the models must implement a toString() method + */ +Form.editors.Radio = Form.editors.Select.extend({ + + tagName: 'ul', + + 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 self = this; + setTimeout(function() { + if (self.$('input[type=radio]:focus')[0]) return; + self.trigger('blur', self); + }, 0); + } + }, + + getValue: function() { + return this.$('input[type=radio]:checked').val(); + }, + + setValue: function(value) { + this.$('input[type=radio]').val([value]); + }, + + focus: function() { + if (this.hasFocus) return; + + var checked = this.$('input[type=radio]:checked'); + if (checked[0]) { + checked.focus(); + return; + } + + this.$('input[type=radio]').first().focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$('input[type=radio]:focus').blur(); + }, + + /** + * Create the radio list HTML + * @param {Array} Options as a simple array e.g. ['option1', 'option2'] + * or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}] + * @return {String} HTML + */ + _arrayToHtml: function (array) { + var html = []; + var self = this; + + _.each(array, function(option, index) { + var itemHtml = '<li>'; + if (_.isObject(option)) { + var val = (option.val || option.val === 0) ? option.val : ''; + itemHtml += ('<input type="radio" name="'+self.id+'" value="'+val+'" id="'+self.id+'-'+index+'" />'); + itemHtml += ('<label for="'+self.id+'-'+index+'">'+option.label+'</label>'); + } + else { + itemHtml += ('<input type="radio" name="'+self.id+'" value="'+option+'" id="'+self.id+'-'+index+'" />'); + itemHtml += ('<label for="'+self.id+'-'+index+'">'+option+'</label>'); + } + itemHtml += '</li>'; + html.push(itemHtml); + }); + + return html.join(''); + } + +}); + +/** + * Checkboxes editor + * + * Renders a <ul> with given options represented as <li> objects containing checkboxes + * + * Requires an 'options' value on the schema. + * Can be an array of options, a function that calls back with the array of options, a string of HTML + * or a Backbone collection. If a collection, the models must implement a toString() method + */ +Form.editors.Checkboxes = Form.editors.Select.extend({ + + tagName: 'ul', + + events: { + 'click input[type=checkbox]': function() { + this.trigger('change', this); + }, + 'focus input[type=checkbox]': function() { + if (this.hasFocus) return; + this.trigger('focus', this); + }, + 'blur input[type=checkbox]': function() { + if (!this.hasFocus) return; + var self = this; + setTimeout(function() { + if (self.$('input[type=checkbox]:focus')[0]) return; + self.trigger('blur', self); + }, 0); + } + }, + + getValue: function() { + var values = []; + this.$('input[type=checkbox]:checked').each(function() { + values.push($(this).val()); + }); + return values; + }, + + setValue: function(values) { + if (!_.isArray(values)) values = [values]; + this.$('input[type=checkbox]').val(values); + }, + + focus: function() { + if (this.hasFocus) return; + + this.$('input[type=checkbox]').first().focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$('input[type=checkbox]:focus').blur(); + }, + + /** + * Create the checkbox list HTML + * @param {Array} Options as a simple array e.g. ['option1', 'option2'] + * or as an array of objects e.g. [{val: 543, label: 'Title for object 543'}] + * @return {String} HTML + */ + _arrayToHtml: function (array) { + var html = []; + var self = this; + + _.each(array, function(option, index) { + var itemHtml = '<li>'; + if (_.isObject(option)) { + var val = (option.val || option.val === 0) ? option.val : ''; + itemHtml += ('<input type="checkbox" name="'+self.id+'" value="'+val+'" id="'+self.id+'-'+index+'" />'); + itemHtml += ('<label for="'+self.id+'-'+index+'">'+option.label+'</label>'); + } + else { + itemHtml += ('<input type="checkbox" name="'+self.id+'" value="'+option+'" id="'+self.id+'-'+index+'" />'); + itemHtml += ('<label for="'+self.id+'-'+index+'">'+option+'</label>'); + } + itemHtml += '</li>'; + html.push(itemHtml); + }); + + return html.join(''); + } + +}); + +/** + * Object editor + * + * Creates a child form. For editing Javascript objects + * + * @param {Object} options + * @param {Form} options.form The form this editor belongs to; used to determine the constructor for the nested form + * @param {Object} options.schema The schema for the object + * @param {Object} options.schema.subSchema The schema for the nested form + */ +Form.editors.Object = Form.editors.Base.extend({ + //Prevent error classes being set on the main control; they are internally on the individual fields + hasNestedForm: true, + + initialize: function(options) { + //Set default value for the instance so it's not a shared object + this.value = {}; + + //Init + Form.editors.Base.prototype.initialize.call(this, options); + + //Check required options + if (!this.form) throw 'Missing required option "form"'; + if (!this.schema.subSchema) throw new Error("Missing required 'schema.subSchema' option for Object editor"); + }, + + render: function() { + //Get the constructor for creating the nested form; i.e. the same constructor as used by the parent form + var NestedForm = this.form.constructor; + + //Create the nested form + this.nestedForm = new NestedForm({ + schema: this.schema.subSchema, + data: this.value, + idPrefix: this.id + '_', + Field: NestedForm.NestedField + }); + + this._observeFormEvents(); + + this.$el.html(this.nestedForm.render().el); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + getValue: function() { + if (this.nestedForm) return this.nestedForm.getValue(); + + return this.value; + }, + + setValue: function(value) { + this.value = value; + + this.render(); + }, + + focus: function() { + if (this.hasFocus) return; + + this.nestedForm.focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.nestedForm.blur(); + }, + + remove: function() { + this.nestedForm.remove(); + + Backbone.View.prototype.remove.call(this); + }, + + validate: function() { + return this.nestedForm.validate(); + }, + + _observeFormEvents: function() { + if (!this.nestedForm) return; + + this.nestedForm.on('all', function() { + // args = ["key:change", form, fieldEditor] + var args = _.toArray(arguments); + args[1] = this; + // args = ["key:change", this=objectEditor, fieldEditor] + + this.trigger.apply(this, args); + }, this); + } + +}); + +/** + * NestedModel editor + * + * Creates a child form. For editing nested Backbone models + * + * Special options: + * schema.model: Embedded model constructor + */ +Form.editors.NestedModel = Form.editors.Object.extend({ + initialize: function(options) { + Form.editors.Base.prototype.initialize.call(this, options); + + if (!this.form) throw 'Missing required option "form"'; + if (!options.schema.model) throw 'Missing required "schema.model" option for NestedModel editor'; + }, + + render: function() { + //Get the constructor for creating the nested form; i.e. the same constructor as used by the parent form + var NestedForm = this.form.constructor; + + var data = this.value || {}, + key = this.key, + nestedModel = this.schema.model; + + //Wrap the data in a model if it isn't already a model instance + var modelInstance = (data.constructor === nestedModel) ? data : new nestedModel(data); + + this.nestedForm = new NestedForm({ + model: modelInstance, + idPrefix: this.id + '_', + fieldTemplate: 'nestedField' + }); + + this._observeFormEvents(); + + //Render form + this.$el.html(this.nestedForm.render().el); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + /** + * Update the embedded model, checking for nested validation errors and pass them up + * Then update the main model if all OK + * + * @return {Error|null} Validation error or null + */ + commit: function() { + var error = this.nestedForm.commit(); + if (error) { + this.$el.addClass('error'); + return error; + } + + return Form.editors.Object.prototype.commit.call(this); + } + +}); + +/** + * Date editor + * + * Schema options + * @param {Number|String} [options.schema.yearStart] First year in list. Default: 100 years ago + * @param {Number|String} [options.schema.yearEnd] Last year in list. Default: current year + * + * Config options (if not set, defaults to options stored on the main Date class) + * @param {Boolean} [options.showMonthNames] Use month names instead of numbers. Default: true + * @param {String[]} [options.monthNames] Month names. Default: Full English names + */ +Form.editors.Date = Form.editors.Base.extend({ + + events: { + 'change select': function() { + this.updateHidden(); + this.trigger('change', this); + }, + 'focus select': function() { + if (this.hasFocus) return; + this.trigger('focus', this); + }, + 'blur select': function() { + if (!this.hasFocus) return; + var self = this; + setTimeout(function() { + if (self.$('select:focus')[0]) return; + self.trigger('blur', self); + }, 0); + } + }, + + initialize: function(options) { + options = options || {}; + + Form.editors.Base.prototype.initialize.call(this, options); + + var Self = Form.editors.Date, + today = new Date(); + + //Option defaults + this.options = _.extend({ + monthNames: Self.monthNames, + showMonthNames: Self.showMonthNames + }, options); + + //Schema defaults + this.schema = _.extend({ + yearStart: today.getFullYear() - 100, + yearEnd: today.getFullYear() + }, options.schema || {}); + + //Cast to Date + if (this.value && !_.isDate(this.value)) { + this.value = new Date(this.value); + } + + //Set default date + if (!this.value) { + var date = new Date(); + date.setSeconds(0); + date.setMilliseconds(0); + + this.value = date; + } + + //Template + this.template = options.template || this.constructor.template; + }, + + render: function() { + var options = this.options, + schema = this.schema; + + var datesOptions = _.map(_.range(1, 32), function(date) { + return '<option value="'+date+'">' + date + '</option>'; + }); + + var monthsOptions = _.map(_.range(0, 12), function(month) { + var value = (options.showMonthNames) + ? options.monthNames[month] + : (month + 1); + + return '<option value="'+month+'">' + value + '</option>'; + }); + + var yearRange = (schema.yearStart < schema.yearEnd) + ? _.range(schema.yearStart, schema.yearEnd + 1) + : _.range(schema.yearStart, schema.yearEnd - 1, -1); + + var yearsOptions = _.map(yearRange, function(year) { + return '<option value="'+year+'">' + year + '</option>'; + }); + + //Render the selects + var $el = $($.trim(this.template({ + dates: datesOptions.join(''), + months: monthsOptions.join(''), + years: yearsOptions.join('') + }))); + + //Store references to selects + this.$date = $el.find('[data-type="date"]'); + this.$month = $el.find('[data-type="month"]'); + this.$year = $el.find('[data-type="year"]'); + + //Create the hidden field to store values in case POSTed to server + this.$hidden = $('<input type="hidden" name="'+this.key+'" />'); + $el.append(this.$hidden); + + //Set value on this and hidden field + this.setValue(this.value); + + //Remove the wrapper tag + this.setElement($el); + this.$el.attr('id', this.id); + this.$el.attr('name', this.getName()); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + /** + * @return {Date} Selected date + */ + getValue: function() { + var year = this.$year.val(), + month = this.$month.val(), + date = this.$date.val(); + + if (!year || !month || !date) return null; + + return new Date(year, month, date); + }, + + /** + * @param {Date} date + */ + setValue: function(date) { + this.$date.val(date.getDate()); + this.$month.val(date.getMonth()); + this.$year.val(date.getFullYear()); + + this.updateHidden(); + }, + + focus: function() { + if (this.hasFocus) return; + + this.$('select').first().focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$('select:focus').blur(); + }, + + /** + * Update the hidden input which is maintained for when submitting a form + * via a normal browser POST + */ + updateHidden: function() { + var val = this.getValue(); + + if (_.isDate(val)) val = val.toISOString(); + + this.$hidden.val(val); + } + +}, { + //STATICS + template: _.template('\ + <div>\ + <select data-type="date"><%= dates %></select>\ + <select data-type="month"><%= months %></select>\ + <select data-type="year"><%= years %></select>\ + </div>\ + ', null, Form.templateSettings), + + //Whether to show month names instead of numbers + showMonthNames: true, + + //Month names to use if showMonthNames is true + //Replace for localisation, e.g. Form.editors.Date.monthNames = ['Janvier', 'Fevrier'...] + monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] +}); + +/** + * DateTime editor + * + * @param {Editor} [options.DateEditor] Date editor view to use (not definition) + * @param {Number} [options.schema.minsInterval] Interval between minutes. Default: 15 + */ +Form.editors.DateTime = Form.editors.Base.extend({ + + events: { + 'change select': function() { + this.updateHidden(); + this.trigger('change', this); + }, + 'focus select': function() { + if (this.hasFocus) return; + this.trigger('focus', this); + }, + 'blur select': function() { + if (!this.hasFocus) return; + var self = this; + setTimeout(function() { + if (self.$('select:focus')[0]) return; + self.trigger('blur', self); + }, 0); + } + }, + + initialize: function(options) { + options = options || {}; + + Form.editors.Base.prototype.initialize.call(this, options); + + //Option defaults + this.options = _.extend({ + DateEditor: Form.editors.DateTime.DateEditor + }, options); + + //Schema defaults + this.schema = _.extend({ + minsInterval: 15 + }, options.schema || {}); + + //Create embedded date editor + this.dateEditor = new this.options.DateEditor(options); + + this.value = this.dateEditor.value; + + //Template + this.template = options.template || this.constructor.template; + }, + + render: function() { + function pad(n) { + return n < 10 ? '0' + n : n; + } + + var schema = this.schema; + + //Create options + var hoursOptions = _.map(_.range(0, 24), function(hour) { + return '<option value="'+hour+'">' + pad(hour) + '</option>'; + }); + + var minsOptions = _.map(_.range(0, 60, schema.minsInterval), function(min) { + return '<option value="'+min+'">' + pad(min) + '</option>'; + }); + + //Render time selects + var $el = $($.trim(this.template({ + hours: hoursOptions.join(), + mins: minsOptions.join() + }))); + + //Include the date editor + $el.find('[data-date]').append(this.dateEditor.render().el); + + //Store references to selects + this.$hour = $el.find('select[data-type="hour"]'); + this.$min = $el.find('select[data-type="min"]'); + + //Get the hidden date field to store values in case POSTed to server + this.$hidden = $el.find('input[type="hidden"]'); + + //Set time + this.setValue(this.value); + + this.setElement($el); + this.$el.attr('id', this.id); + this.$el.attr('name', this.getName()); + + if (this.hasFocus) this.trigger('blur', this); + + return this; + }, + + /** + * @return {Date} Selected datetime + */ + getValue: function() { + var date = this.dateEditor.getValue(); + + var hour = this.$hour.val(), + min = this.$min.val(); + + if (!date || !hour || !min) return null; + + date.setHours(hour); + date.setMinutes(min); + + return date; + }, + + /** + * @param {Date} + */ + setValue: function(date) { + if (!_.isDate(date)) date = new Date(date); + + this.dateEditor.setValue(date); + + this.$hour.val(date.getHours()); + this.$min.val(date.getMinutes()); + + this.updateHidden(); + }, + + focus: function() { + if (this.hasFocus) return; + + this.$('select').first().focus(); + }, + + blur: function() { + if (!this.hasFocus) return; + + this.$('select:focus').blur(); + }, + + /** + * Update the hidden input which is maintained for when submitting a form + * via a normal browser POST + */ + updateHidden: function() { + var val = this.getValue(); + if (_.isDate(val)) val = val.toISOString(); + + this.$hidden.val(val); + }, + + /** + * Remove the Date editor before removing self + */ + remove: function() { + this.dateEditor.remove(); + + Form.editors.Base.prototype.remove.call(this); + } + +}, { + //STATICS + template: _.template('\ + <div class="bbf-datetime">\ + <div class="bbf-date-container" data-date></div>\ + <select data-type="hour"><%= hours %></select>\ + :\ + <select data-type="min"><%= mins %></select>\ + </div>\ + ', null, Form.templateSettings), + + //The date editor to use (constructor function, not instance) + DateEditor: Form.editors.Date +}); + + + + //Metadata + Form.VERSION = '0.12.0'; + + //Exports + Backbone.Form = Form; + + return Form; +});
http://git-wip-us.apache.org/repos/asf/ambari/blob/39c85bb8/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.min.js ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.min.js b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.min.js new file mode 100644 index 0000000..36e34bf --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/backbone-forms.min.js @@ -0,0 +1 @@ +define(["jquery","underscore","backbone"],function(e,t,n){var r=n.View.extend({initialize:function(e){var n=this;e=e||{};var r=this.schema=function(){if(e.schema)return t.result(e,"schema");var r=e.model;return r&&r.schema?t.isFunction(r.schema)?r.schema():r.schema:n.schema?t.isFunction(n.schema)?n.schema():n.schema:{}}();t.extend(this,t.pick(e,"model","data","idPrefix"));var i=this.constructor;this.template=e.template||i.template,this.Fieldset=e.Fieldset||i.Fieldset,this.Field=e.Field||i.Field,this.NestedField=e.NestedField||i.NestedField;var s=this.selectedFields=e.fields||t.keys(r),o=this.fields={};t.each(s,function(e){var t=r[e];o[e]=this.createField(e,t)},this);var u=e.fieldsets||[s],a=this.fieldsets=[];t.each(u,function(e){this.fieldsets.push(this.createFieldset(e))},this)},createFieldset:function(e){var t={schema:e,fields:this.fields};return new this.Fieldset(t)},createField:function(e,t){var n={form:this,key:e,schema:t,idPrefix:this.idPrefix};this.model?n.model=this.model:th is.data?n.value=this.data[e]:n.value=null;var r=new this.Field(n);return this.listenTo(r.editor,"all",this.handleEditorEvent),r},handleEditorEvent:function(e,n){var r=n.key+":"+e;this.trigger.call(this,r,this,n);switch(e){case"change":this.trigger("change",this);break;case"focus":this.hasFocus||this.trigger("focus",this);break;case"blur":if(this.hasFocus){var i=this;setTimeout(function(){var e=t.find(i.fields,function(e){return e.editor.hasFocus});e||i.trigger("blur",i)},0)}}},render:function(){var n=this,r=this.fields,i=e(e.trim(this.template(t.result(this,"templateData"))));return i.find("[data-editors]").add(i).each(function(i,s){var o=e(s),u=o.attr("data-editors");if(t.isUndefined(u))return;var a=u=="*"?n.selectedFields||t.keys(r):u.split(",");t.each(a,function(e){var t=r[e];o.append(t.editor.render().el)})}),i.find("[data-fields]").add(i).each(function(i,s){var o=e(s),u=o.attr("data-fields");if(t.isUndefined(u))return;var a=u=="*"?n.selectedFields||t.keys(r):u.split(",");t.each (a,function(e){var t=r[e];o.append(t.render().el)})}),i.find("[data-fieldsets]").add(i).each(function(r,i){var s=e(i),o=s.attr("data-fieldsets");if(t.isUndefined(o))return;t.each(n.fieldsets,function(e){s.append(e.render().el)})}),this.setElement(i),this},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(e,t){if(n[t]&&!i[t])n[t].setError(e),i[t]=e;else{i._others=i._others||[];var r={};r[t]=e,i._others.push(r)}})}}return t.isEmpty(i)?null:i},commit:function(e){var n=this.validate();if(n)return n;var r,i=t.extend({error:function(e,t){r=t}},e);this.model.set(this.getValue(),i);if(r)return r},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={};type of e=="string"?n[e]=t:n=e;var r;for(r in this.schema)n[r]!==undefined&&this.fields[r].setValue(n[r])},getEditor:function(e){var t=this.fields[e];if(!t)throw"Field not found: "+e;return t.editor},focus:function(){if(this.hasFocus)return;var e=this.fieldsets[0],t=e.getFieldAt(0);if(!t)return;t.editor.focus()},blur:function(){if(!this.hasFocus)return;var e=t.find(this.fields,function(e){return e.editor.hasFocus});e&&e.editor.blur()},trigger:function(e){return e==="focus"?this.hasFocus=!0:e==="blur"&&(this.hasFocus=!1),n.View.prototype.trigger.apply(this,arguments)},remove:function(){t.each(this.fieldsets,function(e){e.remove()}),t.each(this.fields,function(e){e.remove()}),n.View.prototype.remove.call(this)}},{template:t.template(" <form data-fieldsets></form> ",null,this.templateSettings),templateSettings:{evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},editors:{}});return r.validators=function(){var e={};return e.errMessages={required:"Required ",regexp:"Invalid",email:"Invalid email address",url:"Invalid URL",match:t.template('Must match field "<%= field %>"',null,r.templateSettings)},e.required=function(e){return e=t.extend({type:"required",message:this.errMessages.required},e),function(r){e.value=r;var i={type:e.type,message:t.isFunction(e.message)?e.message(e):e.message};if(r===null||r===undefined||r===!1||r==="")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(r){e.value=r;var i={type:e.type,message:t.isFunction(e.message)?e.message(e):e.message};if(r===null||r===undefined||r==="")return;if(!e.regexp.test(r))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.errMessag es.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(r,i){e.value=r;var s={type:e.type,message:t.isFunction(e.message)?e.message(e):e.message};if(r===null||r===undefined||r==="")return;if(r!==i[e.field])return s}},e}(),r.Fieldset=n.View.extend({initialize:function(e){e=e||{};var n=this.schema=this.createSchema(e.schema);this.fields=t.pick(e.fields,n.fields),this.template=e.template||this.constructor.template},createSchema:function(e){return t.isArray(e)&&(e={fields:e}),e.legend=e.legend||null,e},getFieldAt:function(e){var t=this.schema.fields[e];return this.fields[t]},templateData:function(){return this.schema},render:function(){var n=this.schema,r=this.fields,i=e(e.trim(this.template(t.result(this,"templateData"))));return i.find("[data-fields ]").add(i).each(function(n,i){var s=e(i),o=s.attr("data-fields");if(t.isUndefined(o))return;t.each(r,function(e){s.append(e.render().el)})}),this.setElement(i),this},remove:function(){t.each(this.fields,function(e){e.remove()}),n.View.prototype.remove.call(this)}},{template:t.template(" <fieldset data-fields> <% if (legend) { %> <legend><%= legend %></legend> <% } %> </fieldset> ",null,r.templateSettings)}),r.Field=n.View.extend({initialize:function(e){e=e||{},t.extend(this,t.pick(e,"form","key","model","value","idPrefix")),this.template=e.template||this.constructor.template,this.errorClassName=e.errorClassName||this.constructor.errorClassName,this.schema=this.createSchema(e.schema),this.editor=this.createEditor()},createSchema:function(e){return t.isString(e)&&(e={type:e}),e=t.extend({type:"Text",title:this.createTitle()},e),e.type=t.isString(e.type)?r.editors[e.type]:e.type,e},createEditor:function(){var e=t.extend(t.pick(this,"schema","form","key","model", "value"),{id:this.createEditorId()}),n=this.schema.type;return new n(e)},createEditorId:function(){var e=this.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},createTitle:function(){var e=this.key;return e=e.replace(/([A-Z])/g," $1"),e=e.replace(/^./,function(e){return e.toUpperCase()}),e},templateData:function(){var e=this.schema;return{help:e.help||"",title:e.title,fieldAttrs:e.fieldAttrs,editorAttrs:e.editorAttrs,key:this.key,editorId:this.editor.id}},render:function(){var n=this.schema,r=this.editor,i=e(e.trim(this.template(t.result(this,"templateData"))));return n.fieldClass&&i.addClass(n.fieldClass),n.fieldAttrs&&i.attr(n.fieldAttrs),i.find("[data-editor]").add(i).each(function(n,i){var s=e(i),o=s.attr("data-editor");if(t.isUndefined(o))return;s.append(r.render().el)}),this.setElement(i),this},validate:function(){var e=this.editor.validate();return e?this.setError(e.message):this.clearError(),e} ,setError:function(e){if(this.editor.hasNestedForm)return;this.$el.addClass(this.errorClassName),this.$("[data-error]").html(e)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$("[data-error]").empty()},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)}},{template:t.template(' <div> <label for="<%= editorId %>"><%= title %></label> <div> <span data-editor></span> <div data-error></div> <div><%= help %></div> </div> </div> ',null,r.templateSettings),errorClassName:"error"}),r.NestedField=r.Field.extend({template:t.template(e.trim(" <div> <span data-editor></span> <% if (help) { %> <div><%= help %></div> <% } %> <div data-error></div> </div> "),null, r.templateSettings)}),r.Editor=r.editors.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),t.extend(this,t.pick(e,"key","form"));var n=this.schema=e.schema||{};this.validators=e.validators||n.validators,this.$el.attr("id",this.id),this.$el.attr("name",this.getName()),n.editorClass&&this.$el.addClass(n.editorClass),n.editorAttrs&&this.$el.attr(n.editorAttrs)},getName:function(){var e=this.key||"";return e.replace(/\./g,"_")},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){throw"Not implemented"},blur:function(){throw"Not implemented"},commit:function(e){var t=this.validate();if(t)return t;this.listenTo(this.model,"invalid",function(e,n){t=n}),this.model.set(this.key,this.getValue(),e);if(t)return t},validate:function( ){var e=this.$el,n=null,r=this.getValue(),i=this.form?this.form.getValue():{},s=this.validators,o=this.getValidator;return s&&t.every(s,function(e){return n=o(e)(r,i),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)},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)}}),r.editors.Text=r.Editor.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){r.editors.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()}}),r.editors.TextArea=r.editors.Text.extend({tagName:"textarea"}),r.editors.Password=r.editors.Text.extend({initialize:function(e){r.editors.Text.prototype.initialize.call(this,e),this.$el.attr("type","password")}}),r.editors.Number=r.editors.Text.extend({defaultValue:0,events:t.extend({},r.editors.Text.prototype.events,{keypress:"onKeyPress"}),initialize:function(e){r.editors.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),r.editors.Text.prototype.setValue.call(this,e)}}),r.editors.Hidden=r.editors.Base.extend({defaultValue:"",initialize:function(e){r.editors.Text.prototype.initialize.call(this,e),this.$el.attr("type","hidden")},focus:function(){},blur:function(){}}),r.editors.Checkbox=r.editors.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) {r.editors.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")},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()}}),r.editors.Select=r.editors.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){r.editors.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)},r):this.renderOptions(e)},renderOptions:function(e){var t=this.$el,n;n=this._getOptionsHtml(e),t.html(n),this.setValue(this.value)},_getOptionsHtml:function(e){var r;if(t.isString(e))r=e;else if(t.isArray(e))r=this._arrayToHtml(e);else if(e instanceof n.Collection)r=this._collectionToHtml(e);else if(t.isFunction(e)){var i;e(function(e){i=e},this),r=this._getOptionsHtml(i)}return r},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))if(e.group)n.push('<optgroup label="'+e.group+'">'),n.push(this._getOptionsHtml(e.options)),n.push("</optgroup>");else{var r=e.val||e.val===0?e.val:"";n.push('<option value="'+r+'">'+e.label+"</op tion>")}else n.push("<option>"+e+"</option>")},this),n.join("")}}),r.editors.Radio=r.editors.Select.extend({tagName:"ul",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("")}}),r.editors.Checkboxes=r.editors.Select.extend({tagName:"ul",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("")}}),r.editors.Object=r.editors.Base.extend({hasNestedForm:!0,initialize:function(e){this.value={},r.editors.Base.prototype.initialize.call(this,e);if(!this.form)throw'Missing required option "form"';if(!this.schema.subSchema)throw new Error("Missing required 'schema.subSchema' option for Object editor")},render:function(){var e=this.form.constructor;return this.nestedForm=new e({schema:this.schema.subSchema,data:this.value,idPrefix:this.id+"_",Field:e.NestedField}),this._observeFormEvents(),this.$el.html(this.nestedForm.render().el ),this.hasFocus&&this.trigger("blur",this),this},getValue:function(){return this.nestedForm?this.nestedForm.getValue():this.value},setValue:function(e){this.value=e,this.render()},focus:function(){if(this.hasFocus)return;this.nestedForm.focus()},blur:function(){if(!this.hasFocus)return;this.nestedForm.blur()},remove:function(){this.nestedForm.remove(),n.View.prototype.remove.call(this)},validate:function(){return this.nestedForm.validate()},_observeFormEvents:function(){if(!this.nestedForm)return;this.nestedForm.on("all",function(){var e=t.toArray(arguments);e[1]=this,this.trigger.apply(this,e)},this)}}),r.editors.NestedModel=r.editors.Object.extend({initialize:function(e){r.editors.Base.prototype.initialize.call(this,e);if(!this.form)throw'Missing required option "form"';if(!e.schema.model)throw'Missing required "schema.model" option for NestedModel editor'},render:function(){var e=this.form.constructor,t=this.value||{},n=this.key,r=this.schema.model,i=t.constructor===r?t:new r(t); return this.nestedForm=new e({model:i,idPrefix:this.id+"_",fieldTemplate:"nestedField"}),this._observeFormEvents(),this.$el.html(this.nestedForm.render().el),this.hasFocus&&this.trigger("blur",this),this},commit:function(){var e=this.nestedForm.commit();return e?(this.$el.addClass("error"),e):r.editors.Object.prototype.commit.call(this)}}),r.editors.Date=r.editors.Base.extend({events:{"change select":function(){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},r.editors.Base.prototype.initialize.call(this,e);var n=r.editors.Date,i=new Date;this.options=t.extend({monthNames:n.monthNames,showMonthNames:n.showMonthNames},e),this.schema=t.extend({yearStart:i.getFullYear()-100,yearEnd:i.getFullYear()},e.schema||{}),this.value&&!t.isDate(this. value)&&(this.value=new Date(this.value));if(!this.value){var s=new Date;s.setSeconds(0),s.setMilliseconds(0),this.value=s}this.template=e.template||this.constructor.template},render:function(){var n=this.options,r=this.schema,i=t.map(t.range(1,32),function(e){return'<option value="'+e+'">'+e+"</option>"}),s=t.map(t.range(0,12),function(e){var t=n.showMonthNames?n.monthNames[e]:e+1;return'<option value="'+e+'">'+t+"</option>"}),o=r.yearStart<r.yearEnd?t.range(r.yearStart,r.yearEnd+1):t.range(r.yearStart,r.yearEnd-1,-1),u=t.map(o,function(e){return'<option value="'+e+'">'+e+"</option>"}),a=e(e.trim(this.template({dates:i.join(""),months:s.join(""),years:u.join("")})));return this.$date=a.find('[data-type="date"]'),this.$month=a.find('[data-type="month"]'),this.$year=a.find('[data-type="year"]'),this.$hidden=e('<input type="hidden" name="'+this.key+'" />'),a.append(this.$hidden),this.setValue(this.value),this.setElement(a),this.$el.attr("id",this.id),this.$el.attr("name",this.getName( )),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)}},{template:t.template(' <div> <select data-type="date"><%= dates %></select> <select data-type="month"><%= months %></select> <select data-type="year"><%= years %></select> </div> ',null,r.templateSettings),showMonthNames:!0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"]}),r.editors.DateTime=r.editors.Base.extend({events:{"change select":function( ){this.updateHidden(),this.trigger("change",this)},"focus select":function(){if(this.hasFocus)return;this.trigger("focus",this)},"blur select":function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(e.$("select:focus")[0])return;e.trigger("blur",e)},0)}},initialize:function(e){e=e||{},r.editors.Base.prototype.initialize.call(this,e),this.options=t.extend({DateEditor:r.editors.DateTime.DateEditor},e),this.schema=t.extend({minsInterval:15},e.schema||{}),this.dateEditor=new this.options.DateEditor(e),this.value=this.dateEditor.value,this.template=e.template||this.constructor.template},render:function(){function n(e){return e<10?"0"+e:e}var r=this.schema,i=t.map(t.range(0,24),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),s=t.map(t.range(0,60,r.minsInterval),function(e){return'<option value="'+e+'">'+n(e)+"</option>"}),o=e(e.trim(this.template({hours:i.join(),mins:s.join()})));return o.find("[data-date]").append(this.dateEditor.render().el),this.$hour=o.fi nd('select[data-type="hour"]'),this.$min=o.find('select[data-type="min"]'),this.$hidden=o.find('input[type="hidden"]'),this.setValue(this.value),this.setElement(o),this.$el.attr("id",this.id),this.$el.attr("name",this.getName()),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(),r.editors.Base.prototype.remove.call(this)}},{template:t.template(' <div class="bbf-datetime"> <div class="bbf-date-co ntainer" data-date></div> <select data-type="hour"><%= hours %></select> : <select data-type="min"><%= mins %></select> </div> ',null,r.templateSettings),DateEditor:r.editors.Date}),r.VERSION="0.12.0",n.Form=r,r}) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/39c85bb8/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/bootstrap.js ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/bootstrap.js b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/bootstrap.js new file mode 100644 index 0000000..2761755 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-portal/src/main/webapp/libs/bower/backbone-forms/js/bootstrap.js @@ -0,0 +1,66 @@ +/** + * Include this template file after backbone-forms.amd.js to override the default templates + * + * 'data-*' attributes control where elements are placed + */ +define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Backbone) { + var Form = Backbone.Form; + + + /** + * Bootstrap templates for Backbone Forms + */ + Form.template = _.template('\ + <form class="form-horizontal" data-fieldsets></form>\ + '); + + + Form.Fieldset.template = _.template('\ + <fieldset data-fields>\ + <% if (legend) { %>\ + <legend><%= legend %></legend>\ + <% } %>\ + </fieldset>\ + '); + + + Form.Field.template = _.template('\ + <div class="control-group field-<%= key %>">\ + <label class="control-label" for="<%= editorId %>"><%= title %></label>\ + <div class="controls">\ + <span data-editor></span>\ + <div class="help-inline" data-error></div>\ + <div class="help-block"><%= help %></div>\ + </div>\ + </div>\ + '); + + + Form.NestedField.template = _.template('\ + <div class="field-<%= key %>">\ + <div title="<%= title %>" class="input-xlarge">\ + <span data-editor></span>\ + <div class="help-inline" data-error></div>\ + </div>\ + <div class="help-block"><%= help %></div>\ + </div>\ + '); + + + Form.editors.List.template = _.template('\ + <div class="bbf-list">\ + <ul class="unstyled clearfix" data-items></ul>\ + <button class="btn bbf-add" data-action="add">Add</button>\ + </div>\ + '); + + + Form.editors.List.Item.template = _.template('\ + <li class="clearfix">\ + <div class="pull-left" data-editor></div>\ + <button type="button" class="btn bbf-del" data-action="remove">×</button>\ + </li>\ + '); + + +});
