Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: asdluki/fir
base: e76829765e
...
head fork: asdluki/fir
compare: 0e64c59ce2
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 6 files changed
  • 0 commit comments
  • 1 contributor
View
55 assets/www/index.html
@@ -8,13 +8,6 @@
<link rel="stylesheet" href="css/jquery.mobile-1.1.0.min.css" type="text/css" media="screen" title="no title"/>
<link rel="stylesheet" href="css/master.css" type="text/css" media="screen" title="no title">
- <script type="text/javascript" charset="utf-8" src="js/cordova-1.6.1.js"></script>
- <script type="text/javascript" charset="utf-8" src="js/jquery-1.7.2.min.js"></script>
- <script type="text/javascript" charset="utf-8" src="js/jquery.mobile-1.1.0.min.js"></script>
-
- <script type="text/javascript" charset="utf-8" src="js/underscore-min.js"></script>
- <script type="text/javascript" charset="utf-8" src="js/backbone-min.js"></script>
-
</head>
<body>
@@ -97,7 +90,7 @@
</div>
</div>
<div data-role="content" class="ui-content" role="main" data-theme="f">
- <form id="addContractorForm" action="#addContractorForm" method="POST">
+ <form id="contractorForm" action="#addContractorForm" method="POST">
<div data-role="fieldcontain">
<label for="name">Nazwa firmy:</label>
<input type="text" name="name" id="name" value="" placeholder="Nazwa firmy" />
@@ -224,6 +217,38 @@
</li>
</script>
+
+<script type="text/template" id="formTamplate">
+ <form id="own" action="" method="">
+ {{fieldsets}}
+ </form>
+</script>
+
+<script type="text/template" id="formTamplatefieldset">
+ {{fields}}
+</script>
+
+<script type="text/template" id="formTamplatefield">
+ <div data-role="fieldcontain">
+ <label for="name">{{title}}></label>
+ <input type="text" name="{{title}}" value="{{title}}" placeholder="{{title}}" />
+ <div class="bbf-editor bbf-editor{{type}}">{{editor}}</div>\
+ <div class="bbf-help">{{help}}</div>
+ </div>
+</script>
+
+
+<script type="text/javascript" charset="utf-8" src="js/cordova-1.6.1.js"></script>
+<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.2.min.js"></script>
+<script type="text/javascript" charset="utf-8" src="js/jquery.mobile-1.1.0.min.js"></script>
+
+<script type="text/javascript" charset="utf-8" src="js/underscore-min.js"></script>
+<script type="text/javascript" charset="utf-8" src="js/backbone-min.js"></script>
+
+<script type="text/javascript" charset="utf-8" src="js/backbone-forms.js"></script>
+<script type="text/javascript" charset="utf-8" src="js/jquery-ui-editors.js"></script>
+
+
<script type="text/javascript" charset="utf-8" src="js/fir-core.js"></script>
<script type="text/javascript" charset="utf-8" src="js/fir-model.js"></script>
<script type="text/javascript" charset="utf-8" src="js/fir-view.js"></script>
@@ -235,8 +260,18 @@
jQuery(document).ready(function() {
- var router = new FIR.routers();
- Backbone.history.start();
+ var templates = {
+ form : $('#formTamplate').html(),
+ fieldset: $('#formTamplatefieldset').html(),
+ field : $('#formTamplatefield').html()
+ };
+ //console.log(templates);
+ Backbone.Form.setTemplates(templates);
+
+
+ var router = new FIR.routers();
+ Backbone.history.start();
+
document.addEventListener('deviceready', function () {
try{
View
1,455 assets/www/js/backbone-forms.js
@@ -0,0 +1,1455 @@
+/**
+ * Backbone Forms v0.7.3
+ *
+ * Copyright (c) 2012 Charles Davison, Pow Media Ltd
+ *
+ * License and more information at:
+ * http://github.com/powmedia/backbone-forms
+ */
+;(function($) {
+
+ //==================================================================================================
+ //TEMPLATES
+ //==================================================================================================
+
+ var templates = {
+ form: '\
+ <form class="bbf-form">{{fieldsets}}</form>\
+ ',
+
+ fieldset: '\
+ <fieldset>\
+ {{legend}}\
+ <ul>{{fields}}</ul>\
+ </fieldset>\
+ ',
+
+ field: '\
+ <li class="bbf-field bbf-field{{type}}">\
+ <label for="{{id}}">{{title}}</label>\
+ <div class="bbf-editor bbf-editor{{type}}">{{editor}}</div>\
+ <div class="bbf-help">{{help}}</div>\
+ </li>\
+ '
+ };
+
+ var classNames = {
+ error: 'bbf-error'
+ };
+
+
+
+ //==================================================================================================
+ //HELPERS
+ //==================================================================================================
+
+ //Support paths for nested attributes e.g. 'user.name'
+ function getNested(obj, path) {
+ var fields = path.split(".");
+ var result = obj;
+ for (var i = 0, n = fields.length; i < n; i++) {
+ result = result[fields[i]];
+ }
+ return result;
+ }
+
+ var helpers = {};
+
+ /**
+ * This function is used to transform the key from a schema into the title used in a label.
+ * (If a specific title is provided it will be used instead).
+ *
+ * By default this converts a camelCase string into words, i.e. Camel Case
+ * If you have a different naming convention for schema keys, replace this function.
+ *
+ * @param {String} Key
+ * @return {String} Title
+ */
+ helpers.keyToTitle = function(str) {
+ //Add spaces
+ str = str.replace(/([A-Z])/g, ' $1');
+
+ //Uppercase first character
+ str = str.replace(/^./, function(str) { return str.toUpperCase(); });
+
+ return str;
+ };
+
+ /**
+ * Helper to create a template with the {{mustache}} style tags. Template settings are reset
+ * to user's settings when done to avoid conflicts.
+ * @param {String} Template string
+ * @param {Object} Optional; values to replace in template
+ * @return {Template} Compiled template
+ */
+ helpers.createTemplate = function(str, context) {
+ //Store user's template options
+ var _interpolateBackup = _.templateSettings.interpolate;
+
+ //Set custom template settings
+ _.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
+
+ var template = _.template(str);
+
+ //Reset to users' template settings
+ _.templateSettings.interpolate = _interpolateBackup;
+
+ if (!context) {
+ return template;
+ } else {
+ return template(context);
+ }
+ };
+
+
+ /**
+ * Sets the templates to be used.
+ *
+ * If the templates passed in are strings, they will be compiled, expecting Mustache style tags,
+ * i.e. <div>{{varName}}</div>
+ *
+ * You can also pass in previously compiled Underscore templates, in which case you can use any style
+ * tags.
+ *
+ * @param {Object} templates
+ * @param {Object} classNames
+ */
+ helpers.setTemplates = function(templates, classNames) {
+ var createTemplate = helpers.createTemplate;
+
+ Form.templates = Form.templates || {};
+ Form.classNames = Form.classNames || {};
+
+ //Set templates, compiling them if necessary
+ _.each(templates, function(template, key, index) {
+ if (_.isString(template)) template = createTemplate(template);
+
+ Form.templates[key] = template;
+ });
+
+ //Set class names
+ _.extend(Form.classNames, classNames);
+ };
+
+
+ /**
+ * Return the editor constructor for a given schema 'type'.
+ * Accepts strings for the default editors, or the reference to the constructor function
+ * for custom editors
+ *
+ * @param {String|Function} The schema type e.g. 'Text', 'Select', or the editor constructor e.g. editors.Date
+ * @param {Object} Options to pass to editor, including required 'key', 'schema'
+ * @return {Mixed} An instance of the mapped editor
+ */
+ helpers.createEditor = function(schemaType, options) {
+ var constructorFn;
+
+ if (_.isString(schemaType))
+ constructorFn = editors[schemaType];
+ else
+ constructorFn = schemaType;
+
+ return new constructorFn(options);
+ };
+
+ /**
+ * Triggers an event that can be cancelled. Requires the user to invoke a callback. If false
+ * is passed to the callback, the action does not run.
+ *
+ * NOTE: This helper uses private Backbone apis so can break when Backbone is upgraded
+ *
+ * @param {Mixed} Instance of Backbone model, view, collection to trigger event on
+ * @param {String} Event name
+ * @param {Array} Arguments to pass to the event handlers
+ * @param {Function} Callback to run after the event handler has run.
+ * If any of them passed false or error, this callback won't run
+ */
+ helpers.triggerCancellableEvent = function(subject, event, args, callback) {
+ //Return if there are no event listeners
+ if (!subject._callbacks || !subject._callbacks[event]) return callback();
+
+ var next = subject._callbacks[event].next;
+ if (!next) return callback();
+
+ var fn = next.callback,
+ context = next.context || this;
+
+ //Add the callback that will be used when done
+ args.push(callback);
+
+ fn.apply(context, args);
+ }
+
+ /**
+ * Returns a validation function based on the type defined in the schema
+ *
+ * @param {RegExp|String|Function} validator
+ * @return {Function}
+ */
+ helpers.getValidator = function(validator) {
+ //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);
+ };
+
+
+
+ //==================================================================================================
+ //VALIDATORS
+ //==================================================================================================
+
+ var validators = {};
+
+ validators.errMessages = {
+ required: 'Required',
+ regexp: 'Invalid',
+ email: 'Invalid email address',
+ url: 'Invalid URL',
+ match: 'Must match field "{{field}}"'
+ }
+
+ 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: helpers.createTemplate(options.message, options)
+ };
+
+ if (value === null || value === undefined || 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: helpers.createTemplate(options.message, options)
+ };
+
+ //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: helpers.createTemplate(options.message, options)
+ };
+
+ //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;
+ }
+ };
+
+
+
+
+ //==================================================================================================
+ //FORM
+ //==================================================================================================
+
+ var Form = Backbone.View.extend({
+
+ //Field views
+ fields: null,
+
+ /**
+ * @param {Object} Options
+ * Required:
+ * schema {Array}
+ * Optional:
+ * model {Backbone.Model} : Use instead of data, and use commit().
+ * data {Array} : Pass this when not using a model. Use getValue() to get out value
+ * fields {Array} : Keys of fields to include in the form, in display order (default: all fields)
+ * fieldsets {Array} : Allows choosing and ordering fields within fieldsets.
+ * idPrefix {String} : Prefix for editor IDs. If undefined, the model's CID is used.
+ * template {String} : Template to use. Default to 'form'.
+ */
+ initialize: function(options) {
+ //Get the schema
+ this.schema = (function() {
+ if (options.schema) return options.schema;
+
+ var model = options.model;
+ if (!model) throw new Error('Could not find schema');
+
+ if (_.isFunction(model.schema)) return model.schema();
+
+ return model.schema;
+ })();
+
+ //Handle other options
+ this.model = options.model;
+ this.data = options.data;
+ this.fieldsToRender = options.fields || _.keys(this.schema);
+ this.fieldsets = options.fieldsets;
+ this.templateName = options.template || 'form';
+
+ //Stores all Field views
+ this.fields = {};
+ },
+
+ /**
+ * Renders the form and all fields
+ */
+ render: function() {
+ var self = this,
+ fieldsToRender = this.fieldsToRender,
+ fieldsets = this.fieldsets,
+ templates = Form.templates;
+ alert(this.templateName);
+ //Create el from template
+ var $form = $(templates[this.templateName]({
+ fieldsets: '<div class="bbf-placeholder"></div>'
+ }));
+
+ //Get a reference to where fieldsets should go and remove the placeholder
+ var $fieldsetContainer = $('.bbf-placeholder', $form).parent();
+ $fieldsetContainer.html('');
+
+ if (fieldsets) {
+ //TODO: Update handling of fieldsets
+ _.each(fieldsets, function (fs) {
+ if (_(fs).isArray()) {
+ fs = {'fields': fs};
+ }
+
+ //Concatenating HTML as strings won't work so we need to insert field elements into a placeholder
+ var $fieldset = $(templates.fieldset({
+ legend: (fs.legend) ? '<legend>' + fs.legend + '</legend>' : '',
+ fields: '<div class="bbf-placeholder"></div>'
+ }));
+
+ var $fieldsContainer = $('.bbf-placeholder', $fieldset).parent();
+ $fieldsContainer.html('');
+
+ self.renderFields(fs.fields, $fieldsContainer);
+
+ $fieldsetContainer.append($fieldset);
+ });
+ } else {
+ //Concatenating HTML as strings won't work so we need to insert field elements into a placeholder
+ var $fieldset = $(templates.fieldset({
+ legend: '',
+ fields: '<div class="bbf-placeholder"></div>'
+ }));
+
+ var $fieldsContainer = $('.bbf-placeholder', $fieldset).parent();
+ $fieldsContainer.html('');
+
+ this.renderFields(fieldsToRender, $fieldsContainer);
+
+ $fieldsetContainer.append($fieldset);
+ }
+
+ this.setElement($form);
+
+ return this;
+ },
+
+ /**
+ * Render a list of fields. Returns the rendered Field object.
+ * @param {Array} Fields to render
+ * @param {jQuery} Wrapped DOM element where field elemends will go
+ */
+ renderFields: function (fieldsToRender, $container) {
+ var self = this,
+ schema = this.schema,
+ model = this.model,
+ data = this.data,
+ fields = this.fields;
+
+ //Create form fields
+ _.each(fieldsToRender, function(key) {
+ //Get nested schema
+ var itemSchema = (function() {
+ var path = key.replace(/\./g, '.subSchema.');
+ return getNested(schema, path);
+ })();
+
+ if (!itemSchema) throw "Field '"+key+"' not found in schema";
+
+ var options = {
+ form: self,
+ key: key,
+ schema: itemSchema,
+ idPrefix: self.options.idPrefix
+ };
+
+ if (model) {
+ options.model = model;
+ } else if (data) {
+ options.value = data[key];
+ } else {
+ options.value = null;
+ }
+
+ var field = new Field(options);
+
+ //Render the fields with editors, apart from Hidden fields
+ if (itemSchema.type == 'Hidden') {
+ field.editor = helpers.createEditor('Hidden', options);
+ } else {
+ $container.append(field.render().el);
+ }
+
+ fields[key] = field;
+ });
+ },
+
+ /**
+ * 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 (self.fields[key] && !errors[key]) {
+ self.fields[key].setError(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.
+ *
+ * @return {Object} Validation errors
+ */
+ commit: function() {
+ //Validate
+ var errors = this.validate();
+ if (errors) return errors;
+
+ //Commit
+ var modelError;
+ this.model.set(this.getValue(), {
+ error: function(model, e) {
+ modelError = e;
+ }
+ });
+
+ if (modelError) return modelError;
+ },
+
+ /**
+ * Get all the field values as an object.
+ * Use this method when passing data instead of objects
+ *
+ * @param {String} To get a specific field value pass the key name
+ */
+ 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} New values to set
+ */
+ setValue: function(data) {
+ for (var key in data) {
+ this.fields[key].setValue(data[key]);
+ }
+ },
+
+ /**
+ * Override default remove function in order to remove embedded views
+ */
+ remove: function() {
+ var fields = this.fields;
+
+ for (var key in fields) {
+ fields[key].remove();
+ }
+
+ Backbone.View.prototype.remove.call(this);
+ }
+ });
+
+
+
+ //==================================================================================================
+ //FIELD
+ //==================================================================================================
+
+ var Field = Backbone.View.extend({
+
+ /**
+ * @param {Object} Options
+ * Required:
+ * key {String} : The model attribute key
+ * Optional:
+ * schema {Object} : Schema for the field
+ * value {Mixed} : Pass value when not using a model. Use getValue() to get out value
+ * model {Backbone.Model} : Use instead of value, and use commit().
+ * idPrefix {String} : Prefix to add to the editor DOM element's ID
+ */
+ initialize: function(options) {
+ this.form = options.form;
+ this.key = options.key;
+ this.value = options.value;
+ this.model = options.model;
+
+ //Get schema
+ var schema = this.schema = (function() {
+ //Handle schema type shorthand where the editor name is passed instead of a schema config object
+ if (_.isString(options.schema)) return { type: options.schema };
+
+ return options.schema || {};
+ })();
+
+ //Set schema defaults
+ if (!schema.type) schema.type = 'Text';
+ if (!schema.title) schema.title = helpers.keyToTitle(this.key);
+ if (!schema.template) schema.template = 'field';
+ },
+
+ render: function() {
+ var schema = this.schema,
+ templates = Form.templates;
+
+ //Standard options that will go to all editors
+ var options = {
+ form: this.form,
+ key: this.key,
+ schema: schema,
+ idPrefix: this.options.idPrefix,
+ id: this.generateId()
+ };
+
+ //Decide on data delivery type to pass to editors
+ if (this.model)
+ options.model = this.model;
+ else
+ options.value = this.value;
+
+ //Decide on the editor to use
+ var editor = this.editor = helpers.createEditor(schema.type, options);
+
+ //Create the element
+ var $field = $(templates[schema.template]({
+ key: this.key,
+ title: schema.title,
+ id: editor.id,
+ type: schema.type,
+ editor: '<span class="bbf-placeholder-editor"></span>',
+ help: '<span class="bbf-placeholder-help"></span>'
+ }));
+
+ //Render editor
+ var $editorContainer = $('.bbf-placeholder-editor', $field).parent();
+ $editorContainer.empty();
+ $editorContainer.append(editor.render().el);
+
+ //Set help text
+ this.$help = $('.bbf-placeholder-help', $field).parent();
+ this.$help.empty();
+ if (this.schema.help) this.$help.html(this.schema.help);
+
+ //Add custom CSS class names
+ if (this.schema.fieldClass) $field.addClass(this.schema.fieldClass);
+
+ //Add custom attributes
+ if (this.schema.fieldAttrs) $field.attr(this.schema.fieldAttrs);
+
+ this.setElement($field);
+
+ return this;
+ },
+
+ /**
+ * Creates the ID that will be assigned to the editor
+ * @return {String}
+ */
+ generateId: function() {
+ var prefix = this.options.idPrefix,
+ id = this.key;
+
+ //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;
+ },
+
+ /**
+ * 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} errMsg
+ */
+ setError: function(errMsg) {
+ //Object and NestedModel types set their own errors internally
+ if (this.editor.hasNestedForm) return;
+
+ var errClass = Form.classNames.error;
+
+ this.$el.addClass(errClass);
+
+ if (this.$help) this.$help.html(errMsg);
+ },
+
+ /**
+ * Clear the error state and reset the help message
+ */
+ clearError: function() {
+ var errClass = Form.classNames.error;
+
+ this.$el.removeClass(errClass);
+
+ // some fields (e.g., Hidden), may not have a help el
+ if (this.$help) {
+ this.$help.empty();
+
+ //Reset help text if available
+ var helpMsg = this.schema.help;
+ if (helpMsg) this.$help.html(helpMsg);
+ }
+ },
+
+ /**
+ * Update the model with the new value from the editor
+ */
+ 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
+ */
+ setValue: function(value) {
+ this.editor.setValue(value);
+ },
+
+ logValue: function() {
+ if (!console || !console.log) return;
+
+ console.log(this.getValue());
+ },
+
+ remove: function() {
+ this.editor.remove();
+
+ Backbone.View.prototype.remove.call(this);
+ }
+
+ });
+
+
+
+ //========================================================================
+ //EDITORS
+ //========================================================================
+
+ var editors = {};
+
+ /**
+ * Base editor (interface). To be extended, not used directly
+ *
+ * @param {Object} Options
+ * Optional:
+ * model {Backbone.Model} : Use instead of value, and use commit().
+ * key {String} : The model attribute key. Required when using 'model'
+ * value {String} : When not using a model. If neither provided, defaultValue will be used.
+ * schema {Object} : May be required by some editors
+ */
+ editors.Base = Backbone.View.extend({
+
+ defaultValue: null,
+
+ initialize: function(options) {
+ var options = options || {};
+
+ if (options.model) {
+ if (!options.key) throw "Missing option: 'key'";
+
+ this.model = options.model;
+ this.key = options.key;
+
+ this.value = this.model.get(this.key);
+ }
+ else if (options.value) {
+ this.value = options.value;
+ }
+
+ if (this.value === undefined) this.value = this.defaultValue;
+
+ this.form = options.form;
+ this.schema = options.schema || {};
+ this.validators = options.validators || this.schema.validators;
+
+ if (this.key) this.$el.attr('name', this.key);
+
+ //Add custom CSS class names
+ if (this.schema.editorClass) this.$el.addClass(this.schema.editorClass);
+
+ //Add custom attributes
+ if (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.';
+ },
+
+ /**
+ * Update the model with the current value
+ * NOTE: The method is defined on the editors so that they can be used independently of fields
+ *
+ * @return {Mixed} error
+ */
+ commit: function() {
+ var error = this.validate();
+ if (error) return error;
+
+ this.model.set(this.key, this.getValue(), {
+ error: function(model, e) {
+ error = e;
+ }
+ });
+
+ if (error) return error;
+ },
+
+ /**
+ * Check validity
+ * NOTE: The method is defined on the editors so that they can be used independently of fields
+ *
+ * @return {String}
+ */
+ validate: function() {
+ var $el = this.$el,
+ error = null,
+ value = this.getValue(),
+ formValues = this.form ? this.form.getValue() : {},
+ validators = this.validators;
+
+ if (validators) {
+ _.each(validators, function(validator) {
+ if (!error) {
+ error = helpers.getValidator(validator)(value, formValues);
+ }
+ });
+ }
+
+ return error;
+ }
+ });
+
+
+ //TEXT
+ editors.Text = editors.Base.extend({
+
+ tagName: 'input',
+
+ defaultValue: '',
+
+ initialize: function(options) {
+ 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;
+ },
+
+ /**
+ * 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);
+ }
+
+ });
+
+
+ /**
+ * NUMBER
+ * Normal text input that only allows a number. Letters etc. are not entered
+ */
+ editors.Number = editors.Text.extend({
+
+ defaultValue: 0,
+
+ events: {
+ 'keypress': 'onKeyPress'
+ },
+
+ initialize: function(options) {
+ editors.Text.prototype.initialize.call(this, options);
+
+ this.$el.attr('type', 'number');
+ },
+
+ /**
+ * Check value is numeric
+ */
+ onKeyPress: function(event) {
+ //Allow backspace
+ if (event.charCode == 0) 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) event.preventDefault();
+ },
+
+ getValue: function() {
+ var value = this.$el.val();
+
+ return value === "" ? null : parseFloat(value, 10);
+ },
+
+ setValue: function(value) {
+ value = value === null ? null : parseFloat(value, 10);
+
+ editors.Text.prototype.setValue.call(this, value);
+ }
+
+ });
+
+
+ //PASSWORD
+ editors.Password = editors.Text.extend({
+
+ initialize: function(options) {
+ editors.Text.prototype.initialize.call(this, options);
+
+ this.$el.attr('type', 'password');
+ }
+
+ });
+
+
+ //TEXTAREA
+ editors.TextArea = editors.Text.extend({
+
+ tagName: 'textarea'
+
+ });
+
+
+ //CHECKBOX
+ editors.Checkbox = editors.Base.extend({
+
+ defaultValue: false,
+
+ tagName: 'input',
+
+ initialize: function(options) {
+ 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.attr('checked') ? true : false;
+ },
+
+ setValue: function(value) {
+ if (value) {
+ this.$el.attr('checked', true);
+ }
+ }
+
+ });
+
+
+ //HIDDEN
+ editors.Hidden = editors.Base.extend({
+
+ defaultValue: '',
+
+ initialize: function(options) {
+ editors.Text.prototype.initialize.call(this, options);
+
+ this.$el.attr('type', 'hidden');
+ },
+
+ getValue: function() {
+ return this.value;
+ },
+
+ setValue: function(value) {
+ this.value = value;
+ }
+
+ });
+
+
+ /**
+ * SELECT
+ *
+ * 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
+ */
+ editors.Select = editors.Base.extend({
+
+ tagName: 'select',
+
+ initialize: function(options) {
+ editors.Base.prototype.initialize.call(this, options);
+
+ if (!this.schema || !this.schema.options) throw "Missing required 'schema.options'";
+ },
+
+ render: function() {
+ var options = this.schema.options,
+ 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) {
+ self.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);
+ });
+ }
+
+ //Otherwise, ready to go straight to renderOptions
+ else {
+ self.renderOptions(options);
+ }
+
+ return this;
+ },
+
+ /**
+ * 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;
+
+ //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)
+ }
+
+ //Insert options
+ $select.html(html);
+
+ //Select correct option
+ this.setValue(this.value);
+ },
+
+ getValue: function() {
+ return this.$el.val();
+ },
+
+ setValue: function(value) {
+ this.$el.val(value);
+ },
+
+ /**
+ * 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)) {
+ var val = option.val ? option.val : '';
+ html.push('<option value="'+val+'">'+option.label+'</option>');
+ }
+ else {
+ html.push('<option>'+option+'</option>');
+ }
+ });
+
+ return html.join('');
+ }
+
+ });
+
+
+
+ /**
+ * RADIO
+ *
+ * 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
+ */
+ editors.Radio = editors.Select.extend({
+
+ tagName: 'ul',
+ className: 'bbf-radio',
+
+ getValue: function() {
+ return this.$el.find('input[type=radio]:checked').val();
+ },
+
+ setValue: function(value) {
+ this.$el.find('input[type=radio][value='+value+']').attr('checked', true);
+ },
+
+ /**
+ * 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 : '';
+ 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
+ * 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
+ */
+ editors.Checkboxes = editors.Select.extend({
+
+ tagName: 'ul',
+ className: 'bbf-checkboxes',
+
+ getValue: function() {
+ var values = [];
+ this.$el.find('input[type=checkbox]:checked').each(function() {
+ values.push($(this).val());
+ });
+ return values;
+ },
+
+ setValue: function(value) {
+ var self = this;
+ _.each(value, function(val) {
+ self.$el.find('input[type=checkbox][value="'+val+'"]').attr('checked', true);
+ });
+ },
+
+ /**
+ * 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 : '';
+ 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
+ *
+ * Creates a child form. For editing Javascript objects
+ *
+ * Special options:
+ * schema.subSchema: Subschema for object.
+ * idPrefix,
+ */
+ editors.Object = editors.Base.extend({
+ //Prevent error classes being set on the main control; they are internally on the individual fields
+ hasNestedForm: true,
+
+ className: 'bbf-object',
+
+ defaultValue: {},
+
+ initialize: function(options) {
+ editors.Base.prototype.initialize.call(this, options);
+
+ if (!this.schema.subSchema)
+ throw "Missing required 'schema.subSchema' option for Object editor";
+ },
+
+ render: function() {
+ var $el = this.$el,
+ data = this.value || {},
+ key = this.key,
+ schema = this.schema,
+ objSchema = schema.subSchema;
+
+ this.form = new Form({
+ schema: objSchema,
+ data: data,
+ idPrefix: this.id + '_'
+ });
+
+ //Render form
+ $el.html(this.form.render().el);
+
+ return this;
+ },
+
+ getValue: function() {
+ return this.form.getValue();
+ },
+
+ setValue: function(value) {
+ this.value = value;
+
+ this.render();
+ },
+
+ remove: function() {
+ this.form.remove();
+
+ Backbone.View.prototype.remove.call(this);
+ },
+
+ validate: function() {
+ return this.form.validate();
+ }
+
+ });
+
+
+
+ /**
+ * NESTED MODEL
+ *
+ * Creates a child form. For editing nested Backbone models
+ *
+ * Special options:
+ * schema.model: Embedded model constructor
+ */
+ editors.NestedModel = editors.Object.extend({
+ initialize: function(options) {
+ editors.Base.prototype.initialize.call(this, options);
+
+ if (!options.schema.model)
+ throw 'Missing required "schema.model" option for NestedModel editor';
+ },
+
+ render: function() {
+ var data = this.value || {},
+ key = this.key,
+ nestedModel = this.schema.model,
+ nestedModelSchema = (nestedModel).prototype.schema;
+
+ //Handle schema functions
+ if (_.isFunction(nestedModelSchema)) nestedModelSchema = nestedModelSchema();
+
+ this.form = new Form({
+ schema: nestedModelSchema,
+ model: new nestedModel(data),
+ idPrefix: this.id + '_'
+ });
+
+ //Render form
+ this.$el.html(this.form.render().el);
+
+ 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.form.commit();
+ if (error) {
+ this.$el.addClass('error');
+ return error;
+ }
+
+ return editors.Object.prototype.commit.call(this);
+ }
+
+ });
+
+
+ //Exports
+ Form.helpers = helpers;
+ Form.Field = Field;
+ Form.editors = editors;
+ Form.validators = validators;
+ Form.setTemplates = helpers.setTemplates;
+ Backbone.Form = Form;
+
+ //Make default templates active
+ Form.setTemplates(templates, classNames);
+
+ //For use in NodeJS
+ if (typeof module != 'undefined') module.exports = Form
+
+}(jQuery));
View
11 assets/www/js/fir-controller.js
@@ -42,6 +42,15 @@
}
};
+ FIR.controllers.addContractor = function()
+ {
+ var
+ ContractorForm = new Backbone.Form({model: new FIR.models.Contractor}),
+ ContractorView = new FIR.views.ContractorFormView( { form : ContractorForm });
+
+ ContractorView.render();
+ };
+
FIR.controllers.addContractorForm = function()
{
@@ -66,6 +75,7 @@
warehose : "warehose",
settings : "settings",
addInvoice : "addInvoice",
+ addContractor : "addContractor",
addContractorForm : "addContractorForm"
},
index: FIR.controllers.index,
@@ -75,6 +85,7 @@
warehose: FIR.controllers.warehose,
settings : FIR.controllers.settings,
addInvoice : FIR.controllers.addInvoice,
+ addContractor : FIR.controllers.addContractor,
addContractorForm : FIR.controllers.addContractorForm
});
View
19 assets/www/js/fir-model.js
@@ -16,7 +16,24 @@
* Model Kontrahent
* @type {FIR.models.Contractor}
*/
- FIR.models.Contractor = Backbone.Model.extend();
+ FIR.models.Contractor = Backbone.Model.extend({
+
+ schema : {
+
+ name : { dataType: 'Text', validators: ['required'] },
+ nip : { dataType: 'Text', validators: ['required'] },
+ rgon : { dataType: 'Text', validators: ['required'] },
+ firstname : { dataType: 'Text', validators: ['required'] },
+ lastname : { dataType: 'Text', validators: ['required'] },
+ streetAddress : { dataType: 'Text', validators: ['required'] },
+ city : { dataType: 'Text', validators: ['required'] },
+ zipCode : { dataType: 'Text', validators: ['required'] },
+ country : { dataType: 'Text', validators: ['required'] },
+ phone : { dataType: 'Text', validators: ['required'] },
+ email : { dataType: 'email', validators: ['required', 'email'] }
+
+ }
+ });
FIR.models.ContractorCollection = Backbone.Collection.extend({
View
27 assets/www/js/fir-view.js
@@ -23,8 +23,6 @@
el: $('#invoiceList'),
- isRender : false,
-
initialize: function() {
this.model.bind("reset", this.render, this);
},
@@ -35,8 +33,6 @@
$(this.el).append(new FIR.views.InvoiceListItemView({model: invoice}).render().el);
}, this);
- this.isRender = true;
-
return this;
}
});
@@ -74,9 +70,30 @@
$(this.el).append(new FIR.views.ContractorListItemView({model: invoice}).render().el);
}, this);
- this.isRender = true;
+ return this;
+ }
+ });
+ /**
+ *
+ * @type {FIR.views.contractorFormView}
+ */
+ FIR.views.ContractorFormView = Backbone.View.extend({
+
+ el: $('#contractorForm'),
+
+ initialize: function() {
+ },
+
+ render: function(eventName) {
+
+ var form = this.options.form.render();
+
+ console.log(form.el);
+
+ $(this.el).append(form.el);
return this;
}
});
+
}());
View
428 assets/www/js/jquery-ui-editors.js
@@ -0,0 +1,428 @@
+;(function() {
+
+ var Form = Backbone.Form,
+ Base = Form.editors.Base,
+ createTemplate = Form.helpers.createTemplate,
+ triggerCancellableEvent = Form.helpers.triggerCancellableEvent,
+ exports = {};
+
+ /**
+ * Additional editors that depend on jQuery UI
+ */
+
+ //DATE
+ exports.Date = Base.extend({
+
+ className: 'bbf-date',
+
+ initialize: function(options) {
+ Base.prototype.initialize.call(this, options);
+
+ //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;
+ }
+ },
+
+ render: function() {
+ var $el = this.$el;
+
+ $el.html('<input>');
+
+ var input = $('input', $el);
+
+ input.datepicker({
+ dateFormat: 'dd/mm/yy',
+ showButtonPanel: true
+ });
+
+ //Make sure setValue of this object is called, not of any objects extending it (e.g. DateTime)
+ exports.Date.prototype.setValue.call(this, this.value);
+
+ return this;
+ },
+
+ /**
+ * @return {Date} Selected date
+ */
+ getValue: function() {
+ var input = $('input', this.el),
+ date = input.datepicker('getDate');
+
+ return date;
+ },
+
+ setValue: function(value) {
+ $('input', this.el).datepicker('setDate', value);
+ }
+
+ });
+
+
+ //DATETIME
+ exports.DateTime = exports.Date.extend({
+
+ className: 'bbf-datetime',
+
+ template: createTemplate('<select>{{hours}}</select> : <select>{{mins}}</select>'),
+
+ render: function() {
+ function pad(n) {
+ return n < 10 ? '0' + n : n
+ }
+
+ //Render the date element first
+ exports.Date.prototype.render.call(this);
+
+ //Setup hour options
+ var hours = _.range(0, 24),
+ hoursOptions = [];
+
+ _.each(hours, function(hour) {
+ hoursOptions.push('<option value="'+hour+'">' + pad(hour) + '</option>');
+ });
+
+ //Setup minute options
+ var minsInterval = this.schema.minsInterval || 15,
+ mins = _.range(0, 60, minsInterval),
+ minsOptions = [];
+
+ _.each(mins, function(min) {
+ minsOptions.push('<option value="'+min+'">' + pad(min) + '</option>');
+ });
+
+ //Render time selects
+ this.$el.append(this.template({
+ hours: hoursOptions.join(),
+ mins: minsOptions.join()
+ }));
+
+ //Store references to selects
+ this.$hours = $('select:eq(0)', this.el);
+ this.$mins = $('select:eq(1)', this.el);
+
+ //Set time
+ this.setValue(this.value);
+
+ return this;
+ },
+
+ /**
+ * @return {Date} Selected datetime
+ */
+ getValue: function() {
+ var input = $('input', this.el),
+ date = input.datepicker('getDate');
+
+ date.setHours(this.$hours.val());
+ date.setMinutes(this.$mins.val());
+ date.setMilliseconds(0);
+
+ return date;
+ },
+
+ setValue: function(date) {
+ exports.Date.prototype.setValue.call(this, date);
+
+ this.$hours.val(date.getHours());
+ this.$mins.val(date.getMinutes());
+ }
+
+ });
+
+
+ //LIST
+ exports.List = Base.extend({
+
+ className: 'bbf-list',
+
+ //Note: The extra div around the <ul> is used to limit the drag area
+ template: createTemplate('\
+ <ul></ul>\
+ <div><button class="bbf-list-add">Add</div>\
+ '),
+
+ itemTemplate: createTemplate('\
+ <li rel="{{id}}">\
+ <span class="bbf-list-text">{{text}}</span>\
+ <div class="bbf-list-actions">\
+ <button class="bbf-list-edit">Edit</button>\
+ <button class="bbf-list-del">Delete</button>\
+ </div>\
+ </li>\
+ '),
+
+ editorTemplate: createTemplate('\
+ <div class="bbf-field">\
+ <div class="bbf-list-editor"></div>\
+ </div>\
+ '),
+
+ events: {
+ 'click .bbf-list-add': 'addNewItem',
+ 'click .bbf-list-edit': 'editItem',
+ 'click .bbf-list-del': 'deleteItem'
+ },
+
+ initialize: function(options) {
+ Base.prototype.initialize.call(this, options);
+
+ if (!this.schema) throw "Missing required option 'schema'";
+
+ this.schema.listType = this.schema.listType || 'Text';
+
+ if (this.schema.listType == 'NestedModel' && !this.schema.model)
+ throw "Missing required option 'schema.model'";
+ },
+
+ render: function() {
+ var $el = this.$el;
+
+ //Main element
+ $el.html(this.template());
+
+ //Create list
+ var self = this,
+ data = this.value || [],
+ schema = this.schema,
+ itemToString = this.itemToString,
+ itemTemplate = this.itemTemplate,
+ listEl = $('ul', $el);
+
+ _.each(data, function(itemData) {
+ var text = itemToString.call(self, itemData);
+
+ //Create DOM element
+ var li = $(itemTemplate({
+ id: itemData.id || '',
+ text: text
+ }));
+
+ //Attach data
+ $.data(li[0], 'data', itemData);
+
+ listEl.append(li);
+ });
+
+ //Make sortable
+ if (schema.sortable !== false) {
+ listEl.sortable({
+ axis: 'y',
+ cursor: 'move',
+ containment: 'parent'
+ });
+
+ $el.addClass('bbf-list-sortable');
+ }
+
+ //jQuery UI buttonize
+ $('button.bbf-list-add', $el).button({
+ text: false,
+ icons: { primary: 'ui-icon-plus' }
+ });
+ $('button.bbf-list-edit', $el).button({
+ text: false,
+ icons: { primary: 'ui-icon-pencil' }
+ });
+ $('button.bbf-list-del', $el).button({
+ text: false,
+ icons: { primary: 'ui-icon-trash' }
+ });
+
+ return this;
+ },
+
+ /**
+ * Formats an item for display in the list
+ * For example objects, dates etc. can have a custom
+ * itemToString method which says how it should be formatted.
+ */
+ itemToString: function(data) {
+ if (!data) return data;
+
+ var schema = this.schema;
+
+ //If there's a specified toString use that
+ if (schema.itemToString) return schema.itemToString(data);
+
+ //Otherwise check if it's NestedModel with it's own toString() method
+ if (this.schema.listType == 'NestedModel') {
+ var model = new (this.schema.model)(data);
+
+ return model.toString();
+ }
+
+ //Last resort, just return the data as is
+ return data;
+ },
+
+ /**
+ * Add a new item to the list if it is completed in the editor
+ */
+ addNewItem: function(event) {
+ event.preventDefault();
+
+ var self = this;
+
+ this.openEditor(null, function(value) {
+ //Fire 'addItem' cancellable event
+ triggerCancellableEvent(self, 'addItem', [value], function() {
+ var text = self.itemToString(value);
+
+ //Create DOM element
+ var li = $(self.itemTemplate({
+ id: value.id || '',
+ text: text
+ }));
+
+ //Store data
+ $.data(li[0], 'data', value);
+
+ $('ul', self.el).append(li);
+
+ //jQuery UI buttonize
+ $('button.bbf-list-edit', this.el).button({
+ text: false,
+ icons: { primary: 'ui-icon-pencil' }
+ });
+ $('button.bbf-list-del', this.el).button({
+ text: false,
+ icons: { primary: 'ui-icon-trash' }
+ });
+ });
+ });
+ },
+
+ /**
+ * Edit an existing item in the list
+ */
+ editItem: function(event) {
+ event.preventDefault();
+
+ var self = this,
+ li = $(event.target).closest('li'),
+ originalValue = $.data(li[0], 'data');
+
+ this.openEditor(originalValue, function(newValue) {
+ //Fire 'editItem' cancellable event
+ triggerCancellableEvent(self, 'editItem', [newValue], function() {
+ //Update display
+ $('.bbf-list-text', li).html(self.itemToString(newValue));
+
+ //Store data
+ $.data(li[0], 'data', newValue);
+ });
+ });
+ },
+
+ deleteItem: function(event) {
+ event.preventDefault();
+
+ var self = this,
+ li = $(event.target).closest('li'),
+ data = $.data(li[0], 'data');
+
+ var confirmDelete = (this.schema.confirmDelete) ? this.schema.confirmDelete : false,
+ confirmMsg = this.schema.confirmDeleteMsg || 'Are you sure?';
+
+ function remove() {
+ triggerCancellableEvent(self, 'removeItem', [data], function() {
+ li.remove();
+ });
+ }
+
+ if (this.schema.confirmDelete) {
+ if (confirm(confirmMsg)) remove();
+ } else {
+ remove();
+ }
+ },
+
+ /**
+ * Opens the sub editor dialog
+ * @param {Mixed} Data (if editing existing list item, null otherwise)
+ * @param {Function} Save callback. receives: value
+ */
+ openEditor: function(data, callback) {
+ var self = this,
+ schema = this.schema,
+ listType = schema.listType || 'Text';
+
+ var editor = Form.helpers.createEditor(listType, {
+ key: '',
+ schema: schema,
+ value: data
+ }).render();
+
+ var container = $(this.editorTemplate());
+ $('.bbf-list-editor', container).html(editor.el);
+
+ var close = function() {
+ $(document).unbind('keydown', handleEnterPressed);
+
+ container.dialog('close');
+
+ editor.remove();
+ container.remove();
+ };
+
+ var saveAndClose = function() {
+ var errs = editor.validate();
+ if (errs) return;
+
+ callback(editor.getValue());
+ close();
+ }
+
+ var handleEnterPressed = function(event) {
+ if (event.keyCode != 13) return;
+
+ saveAndClose();
+ }
+
+ $(container).dialog({
+ resizable: false,
+ modal: true,
+ width: 500,
+ title: data ? 'Edit item' : 'New item',
+ buttons: {
+ 'OK': saveAndClose,
+ 'Cancel': close
+ }
+ });
+
+ //Save and close dialog on Enter keypress
+ $(document).bind('keydown', handleEnterPressed);
+ },
+
+ getValue: function() {
+ var data = [];
+
+ $('li', this.el).each(function(index, li) {
+ data.push($.data(li, 'data'));
+ });
+
+ return data;
+ },
+
+ setValue: function(value) {
+ this.value = value;
+ this.render();
+ }
+
+ });
+
+
+ //Exports
+ _.extend(Form.editors, exports);
+
+})();

No commit comments for this range

Something went wrong with that request. Please try again.