UNPKG

alpaca

Version:

Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide

493 lines (401 loc) 17.8 kB
(function($) { var Alpaca = $.alpaca; Alpaca.Fields.OptionTreeField = Alpaca.Fields.TextField.extend( /** * @lends Alpaca.Fields.OptionTree.prototype */ { /** * @see Alpaca.Fields.ObjectField#getFieldType */ getFieldType: function() { return "optiontree"; }, /** * @private * @see Alpaca.Fields.ObjectField#setup */ setup: function() { var self = this; this.base(); if (!this.options.tree) { this.options.tree = {}; } if (!this.options.tree.selectors) { this.options.tree.selectors = {}; } if (!this.options.tree.order) { this.options.tree.order = []; } // ensure all options have schema for (var k in this.options.tree.selectors) { if (!this.options.tree.selectors[k].schema) { Alpaca.logError("OptionTree selector for: " + k + " is missing schema"); return; } if (!this.options.tree.selectors[k].options) { this.options.tree.selectors[k].options = {}; } } if (!this.options.tree.data) { this.options.tree.data = []; } // walk data for (var i = 0; i < this.options.tree.data.length; i++) { var item = this.options.tree.data[i]; if (item.attributes) { for (var k in item.attributes) { if (!this.options.tree.selectors[k]) { this.options.tree.selectors[k] = {}; } if (!this.options.tree.selectors[k].label) { this.options.tree.selectors[k].options.noneLabel = "Choose..."; } if (!this.options.tree.selectors[k].type) { this.options.tree.selectors[k].options.type = "select"; } } } } // assume the order from the options if not otherwise provided if (!self.options.tree.order) { self.options.tree.order = []; for (var k in self.options.tree.selectors) { self.options.tree.order.push(self.options.tree.selectors[k]); } } if (typeof(self.options.tree.horizontal) === "undefined") { self.options.tree.horizontal = true; } // create a lookup list for option list key/value based on location this.locationValueLists = {}; this.locationValues = {}; for (var i = 0; i < self.options.tree.data.length; i++) { if (self.options.tree.data[i].attributes) { var location = "root"; for (var k in self.options.tree.data[i].attributes) { var v = self.options.tree.data[i].attributes[k]; var array = this.locationValueLists[location]; if (!array) { array = []; this.locationValueLists[location] = array; } var exists = false; for (var x = 0; x < array.length; x++) { if (array[x].value === v) { exists = true; break; } } if (!exists) { array.push({ "text": v, "value": v }); } if (location.length > 0) { location += "~"; } location += k + "=" + v; } this.locationValues[location] = self.options.tree.data[i].value; } } this.currentAttributes = {}; this.controls = {}; }, toLocation: function(attrs) { var location = "root"; for (var k in attrs) { var v = attrs[k]; if (location.length > 0) { location += "~"; } location += k + "=" + v; } return location; }, existsLocationWithPrefix: function(prefix) { var match = false; for (var k in this.locationValueLists) { if (k.indexOf(prefix) > -1) { match = true; break; } } return match; }, /** * @see Alpaca.Field#afterRenderControl */ afterRenderControl: function(model, callback) { var self = this; self.optionTreeHolder = $(self.field).find(".optiontree"); if (self.options.tree.horizontal) { $(self.field).addClass("optiontree-horizontal"); } this.base(model, function() { self.refreshOptionTreeControls(function() { callback(); }); }); }, refreshOptionTreeControls: function(callback) { var self = this; // hide all of the controls for (var k in self.controls) { self.controls[k].hide(); } // find the index of the last option for which we are missing a value var displayUpToIndex = 0; for (var i = 0; i < self.options.tree.order.length; i++) { var selectorId = self.options.tree.order[i]; if (typeof(self.currentAttributes[selectorId]) !== "undefined" && self.currentAttributes[selectorId] !== null && self.currentAttributes[selectorId] !== "") { displayUpToIndex++; } } // walk through order and construct render functions var location = "root"; var fns = []; var displayCount = 0; var i = 0; do { if (i < self.options.tree.order.length) { var selectorId = self.options.tree.order[i]; var hasMatches = (i == self.options.tree.order.length - 1) || (self.existsLocationWithPrefix(location + "~" + selectorId + "=")); if (hasMatches) { if (displayCount <= displayUpToIndex) { if (self.controls[selectorId]) { // show this one self.controls[selectorId].show(); location += "~" + selectorId + "=" + self.currentAttributes[selectorId]; } else { var selector = self.options.tree.selectors[selectorId]; var last = (i + 1 === self.options.tree.order.length); var fn = function(index, selectorId, selector, controls, optionTreeHolder, last) { return function(done) { var alpacaSchema = selector.schema; var alpacaOptions = selector.options; if (!alpacaOptions) { alpacaOptions = {}; } if (!alpacaOptions.type) { alpacaOptions.type = "select"; } if (alpacaOptions.type === "select") { alpacaOptions.dataSource = function(callback) { var currentLocation = self.toLocation(self.currentAttributes); var currentValueList = self.locationValueLists[currentLocation]; callback(currentValueList); }; } // render via alpaca var domEl = $("<div class='optiontree-selector'></div>"); $(domEl).alpaca({ "schema": alpacaSchema, "options": alpacaOptions, "postRender": function(control) { controls[selectorId] = control; // append to the holder element $(optionTreeHolder).append(domEl); control.selectorId = selectorId; // when the value of this control changes, we record it into our // current attribute set control.on("change", function() { var selectorId = this.selectorId; // set our attribute value self.currentAttributes[selectorId] = this.getValue(); // clear out everything past our index value for (var i = 0; i < self.options.tree.order.length; i++) { if (i > index) { var selectorId = self.options.tree.order[i]; delete self.currentAttributes[selectorId]; if (controls[selectorId]) { controls[selectorId].destroy(); delete controls[selectorId]; } } } if (last) { // find the match var val = null; for (var i = 0; i < self.options.tree.data.length; i++) { var match = true; var attrs = self.options.tree.data[i].attributes; for (var k in self.currentAttributes) { if (attrs[k] !== self.currentAttributes[k]) { match = false; break; } } if (match) { val = self.options.tree.data[i].value; } } if (val) { self.setValue(val); } } self.refreshOptionTreeControls(); }); // show by default control.show(); done(); } }); } }(i, selectorId, selector, self.controls, self.optionTreeHolder, last); fns.push(fn); location += "~" + selectorId + "=" + self.currentAttributes[selectorId]; } displayCount++; } else { if (self.controls[selectorId]) { self.controls[selectorId].destroy(); delete self.controls[selectorId]; } } } else { if (self.controls[selectorId]) { self.controls[selectorId].destroy(); delete self.controls[selectorId]; } } } i++; } while (i < self.options.tree.order.length); Alpaca.series(fns, function() { if (callback) { callback(); } }); }, /** * @see Alpaca.Fields.ObjectField#getType */ getType: function() { return "any"; } /* builder_helpers */ , /** * @see Alpaca.Fields.ObjectField#getTitle */ getTitle: function() { return "Option Tree"; }, /** * @see Alpaca.Fields.ObjectField#getDescription */ getDescription: function() { return "Option Tree"; }, /** * @private * @see Alpaca.Fields.ObjectField#getSchemaOfOptions */ getSchemaOfOptions: function() { return Alpaca.merge(this.base(), { "properties": { "tree": { "type": "object", "properties": { "options": { "type": "object" }, "order": { "type": "array", "items": { "type": "string" } }, "data": { "type": "array", "items": { "type": "object", "properties": { "value": { "type": "any" }, "attributes": { "type": "object" } } } }, "horizontal": { "type": "boolean" } } } } }); }, /** * @private * @see Alpaca.Fields.ObjectField#getOptionsForOptions */ getOptionsForOptions: function() { return Alpaca.merge(this.base(), { "fields": { } }); } /* end_builder_helpers */ }); Alpaca.registerFieldClass("optiontree", Alpaca.Fields.OptionTreeField); })(jQuery);