/** * Dynamic form plugin * @version 2.0.0 * @requires $ jQuery 1.7.2+ * @requires _ Underscore 1.4.2+ */ (function ($) { "use strict"; /** * @param {Object} options * @param {Array.} options.relations */ $.fn.dynamicForm2 = function (options) { return new DynamicForm2(this, options); }; var DynamicForm2 = function () { this.init.apply(this, arguments); }; DynamicForm2.Element = function (options) { if (typeof options == 'undefined') { throw new Error('Element options are undefined'); } this.id = options.id; this.type = options.type; this._name = options.name; this.node = $(options.selector); this.trigger = options.trigger || false; this.target = options.target || false; this.pair = $('#' + this.id + '-label, #' + this.id + '-element'); }; DynamicForm2.Element.prototype.getValue = function () { return this.node.val(); }; DynamicForm2.Element.prototype.getNode = function () { return this.node; }; DynamicForm2.Element.prototype.getName = function () { return this._name; }; DynamicForm2.Element.prototype.getType = function () { return this.node.type; }; DynamicForm2.Element.prototype.toggle = function (display) { this.pair.toggle(display); return this; }; DynamicForm2.Element.prototype.reset = function (display) { this.node.val(''); return this; }; DynamicForm2.Element.prototype.isTrigger = function () { return this.trigger; }; DynamicForm2.Element.prototype.isTarget = function () { return this.target; }; DynamicForm2.RadioElement = function () { DynamicForm2.Element.apply(this, arguments); }; DynamicForm2.RadioElement.prototype = Object.create(DynamicForm2.Element.prototype); DynamicForm2.RadioElement.prototype.getValue = function () { return this.node.filter(':checked').val(); }; DynamicForm2.RadioElement.prototype.reset = function () { return this.node.filter(':checked').prop('checked', false); }; //------------------------------------- DynamicForm2.MultiCheckboxElement = function () { DynamicForm2.Element.apply(this, arguments); }; DynamicForm2.MultiCheckboxElement.prototype = Object.create(DynamicForm2.Element.prototype); DynamicForm2.MultiCheckboxElement.prototype.getValue = function () { var value = []; this.node.filter(':checked').each(function() { value.push(this.value); }); return value; }; DynamicForm2.MultiCheckboxElement.prototype.reset = function () { return this.node.filter(':checked').prop('checked', false); }; //---------------- DynamicForm2.CheckboxElement = function () { DynamicForm2.Element.apply(this, arguments); this.checkedValue = this.node.val(); this.uncheckedValue = $('[name="' + this.node.attr('name') +'"]:hidden').val(); }; DynamicForm2.CheckboxElement.prototype = Object.create(DynamicForm2.Element.prototype); DynamicForm2.CheckboxElement.prototype.getValue = function () { return this.node.is(':checked') ? this.checkedValue : this.uncheckedValue; }; DynamicForm2.CheckboxElement.prototype.reset = function () { return this.node.prop('checked', false); }; DynamicForm2.prototype = { /** * @param {jQuery} plugin * @param {Object} options * @param {Array.} options.relations * @param {Array.} options.elements * @return {*} */ init: function (plugin, options) { this.plugin = plugin; this.options = options; /** @type {Array.} */ this.relations = []; this.elements = {}; this.setElements(options.elements || {}); this.setRelations(options.relations); this.dynamicElements = this.findElements(this.relations); this.refreshFn = _.bind(this.refresh, this); this.initHandlers(this.relations); this.refresh(); return this; }, initHandlers: function (relations) { var i, j, relation, rule, element; for (i = 0; i < relations.length; ++i) { relation = relations[i]; element = this.getElement(relation.element); element.getNode().on(relation.event, _.bind(this.refresh, this, relation)); for (j = 0; j < relation.rules.length; ++j) { rule = relation.rules[j]; if (rule.subRelations.length) { this.initHandlers(rule.subRelations); } } } return this; }, setRelations: function (relations) { var relation, i, length; for (i = 0, length = relations.length; i < length; ++i) { relation = relations[i]; relation.plugin = this.plugin; this.relations.push(new Relation(relation)); } return this; }, setElements: function (elements) { this.elements = {}; for (var i = 0 ; i< elements.length; i++) { var element = elements[i]; this.elements[element.name] = this.createElement(element); } return this; }, getElement: function (name) { return this.elements[name]; }, createElement: function (options) { if (!options.type) { throw new Error('Element Type is undefined'); } var className = options.type.charAt(0).toUpperCase() + options.type.substr(1) + 'Element'; if (DynamicForm2[className]) { return new DynamicForm2[className](options); } return new DynamicForm2.Element(options); }, findElements: function (relations) { var elements = [], i, j, relation, rule; for (i = 0; i < relations.length; ++i) { relation = relations[i]; for (j = 0; j < relation.rules.length; ++j) { rule = relation.rules[j]; [].push.apply(elements, rule.elements); if (rule.subRelations.length) { [].push.apply(elements, this.findElements(rule.subRelations)); } } } return elements; }, findHiddenElements: function () { return _.difference(this.dynamicElements, this.findVisibleElements(this.relations)); }, findVisibleElements: function (relations) { var elements = [], i, j, relation, rule, element; for (i = 0; i < relations.length; ++i) { relation = relations[i]; for (j = 0; j < relation.rules.length; ++j) { rule = relation.rules[j]; element = this.getElement(relation.element); if (rule.match(element.getValue())) { [].push.apply(elements, rule.elements); if (rule.subRelations.length) { [].push.apply(elements, this.findVisibleElements(rule.subRelations)); } } } } return elements; }, initDependedElements: function (/* Relation */ relation) { var i, j, rule, name, element, relationElementValue = this.getElement(relation.element).getValue(); for (i = 0; i < relation.rules.length; ++i) { rule = relation.rules[i]; if (rule.match(relationElementValue)) { continue; } for (j = 0; j < rule.elements.length; ++j) { name = rule.elements[j]; element = this.getElement(name); if (element.isTrigger()) { element.reset(); this.initDependedElements(this.findRelationByElement(name)); } } } return this; }, findRelationByElement: function (name) { var result; this.walkRelations(function (relation) { if (relation.element == name) { result = relation; return false; } }); return result; }, walkRelations: function (callback, relations) { relations = relations || this.relations; var i, j, relation, rule; for (i = 0; i < relations.length; ++i) { relation = relations[i]; if (false === callback.call(this, relation)) { return false; } for (j = 0; j < relation.rules.length; ++j) { rule = relation.rules[j]; if (!rule.subRelations.length) { continue; } if (false === this.walkRelations(callback, rule.subRelations)) { return false; } } } return true; }, refresh: function (relation) { var i, name, hiddenElements; if (relation) { this.initDependedElements(relation); } hiddenElements = this.findHiddenElements(); for (i = 0; i < this.dynamicElements.length; ++i) { name = this.dynamicElements[i]; this.toggleElement(name, -1 == hiddenElements.indexOf(name)); } return this; }, toggleElement: function (name, display) { var element = this.getElement(name); element.toggle(display); if (!display) { element.reset(); } element.getNode().trigger({ type: 'dynamicForm2Toggle', dynamicForm: { display: !!display } }); } }; /** * * @param {Object} options * @param {String} options.event * @param {Array.} options.rules * @return {Relation} * @constructor */ var Relation = function (options) { /** @type {String} */ this.element = options.element; /** @type {String} */ this.event = String(options.event || 'change'); /** @type {Array.} */ this.rules = []; this.setRules(options.rules); }; Relation.prototype = { setRules: function (rules) { var i, length; for (i = 0, length = rules.length; i < length; ++i) { this.rules.push(new Rule(rules[i])); } return this; } }; /** * @param {Object} options * @param {String} options.value * @param {Array.} [options.subRelations] * @constructor */ var Rule = function (options) { /** @type {String}|{Array} */ this.value = options.value; /** @type {Array} */ this.elements = options.elements || []; /** @type {Array.} */ this.subRelations = []; this.setSubRelations((options.subRelations) ? options.subRelations : []); return this; }; Rule.prototype = { setSubRelations: function (relations) { var i, length; for (i = 0, length = relations.length; i < length; ++i) { this.subRelations.push(new Relation(relations[i])); } return this; }, match: function (targetValue) { switch ($.type(this.value)) { case 'array': return this.value.indexOf(targetValue) != -1; default: return (this.value == targetValue); } } }; $.fn.dynamicForm2.Relation = Relation; $.fn.dynamicForm2.Rule = Rule; })(jQuery);