EVOLUTION-MANAGER
Edit File: list.js
/** List - abstract class for inputs that have source option loaded from js array or via ajax @class list @extends abstractinput **/ (function ($) { "use strict"; var List = function (options) { }; $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); $.extend(List.prototype, { render: function () { var deferred = $.Deferred(); this.error = null; this.onSourceReady(function () { this.renderList(); deferred.resolve(); }, function () { this.error = this.options.sourceError; deferred.resolve(); }); return deferred.promise(); }, html2value: function (html) { return null; //can't set value by text }, value2html: function (value, element, display, response) { var deferred = $.Deferred(), success = function () { if(typeof display === 'function') { //custom display method display.call(element, value, this.sourceData, response); } else { this.value2htmlFinal(value, element); } deferred.resolve(); }; //for null value just call success without loading source if(value === null) { success.call(this); } else { this.onSourceReady(success, function () { deferred.resolve(); }); } return deferred.promise(); }, // ------------- additional functions ------------ onSourceReady: function (success, error) { //run source if it function var source; if ($.isFunction(this.options.source)) { source = this.options.source.call(this.options.scope); this.sourceData = null; //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed } else { source = this.options.source; } //if allready loaded just call success if(this.options.sourceCache && $.isArray(this.sourceData)) { success.call(this); return; } //try parse json in single quotes (for double quotes jquery does automatically) try { source = $.fn.editableutils.tryParseJson(source, false); } catch (e) { error.call(this); return; } //loading from url if (typeof source === 'string') { //try to get sourceData from cache if(this.options.sourceCache) { var cacheID = source, cache; if (!$(document).data(cacheID)) { $(document).data(cacheID, {}); } cache = $(document).data(cacheID); //check for cached data if (cache.loading === false && cache.sourceData) { //take source from cache this.sourceData = cache.sourceData; this.doPrepend(); success.call(this); return; } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later cache.callbacks.push($.proxy(function () { this.sourceData = cache.sourceData; this.doPrepend(); success.call(this); }, this)); //also collecting error callbacks cache.err_callbacks.push($.proxy(error, this)); return; } else { //no cache yet, activate it cache.loading = true; cache.callbacks = []; cache.err_callbacks = []; } } //ajaxOptions for source. Can be overwritten bt options.sourceOptions var ajaxOptions = $.extend({ url: source, type: 'get', cache: false, dataType: 'json', success: $.proxy(function (data) { if(cache) { cache.loading = false; } this.sourceData = this.makeArray(data); if($.isArray(this.sourceData)) { if(cache) { //store result in cache cache.sourceData = this.sourceData; //run success callbacks for other fields waiting for this source $.each(cache.callbacks, function () { this.call(); }); } this.doPrepend(); success.call(this); } else { error.call(this); if(cache) { //run error callbacks for other fields waiting for this source $.each(cache.err_callbacks, function () { this.call(); }); } } }, this), error: $.proxy(function () { error.call(this); if(cache) { cache.loading = false; //run error callbacks for other fields $.each(cache.err_callbacks, function () { this.call(); }); } }, this) }, this.options.sourceOptions); //loading sourceData from server $.ajax(ajaxOptions); } else { //options as json/array this.sourceData = this.makeArray(source); if($.isArray(this.sourceData)) { this.doPrepend(); success.call(this); } else { error.call(this); } } }, doPrepend: function () { if(this.options.prepend === null || this.options.prepend === undefined) { return; } if(!$.isArray(this.prependData)) { //run prepend if it is function (once) if ($.isFunction(this.options.prepend)) { this.options.prepend = this.options.prepend.call(this.options.scope); } //try parse json in single quotes this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); //convert prepend from string to object if (typeof this.options.prepend === 'string') { this.options.prepend = {'': this.options.prepend}; } this.prependData = this.makeArray(this.options.prepend); } if($.isArray(this.prependData) && $.isArray(this.sourceData)) { this.sourceData = this.prependData.concat(this.sourceData); } }, /* renders input list */ renderList: function() { // this method should be overwritten in child class }, /* set element's html by value */ value2htmlFinal: function(value, element) { // this method should be overwritten in child class }, /** * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}] */ makeArray: function(data) { var count, obj, result = [], item, iterateItem; if(!data || typeof data === 'string') { return null; } if($.isArray(data)) { //array /* function to iterate inside item of array if item is object. Caclulates count of keys in item and store in obj. */ iterateItem = function (k, v) { obj = {value: k, text: v}; if(count++ >= 2) { return false;// exit from `each` if item has more than one key. } }; for(var i = 0; i < data.length; i++) { item = data[i]; if(typeof item === 'object') { count = 0; //count of keys inside item $.each(item, iterateItem); //case: [{val1: 'text1'}, {val2: 'text2} ...] if(count === 1) { result.push(obj); //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...] } else if(count > 1) { //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text') if(item.children) { item.children = this.makeArray(item.children); } result.push(item); } } else { //case: ['text1', 'text2' ...] result.push({value: item, text: item}); } } } else { //case: {val1: 'text1', val2: 'text2, ...} $.each(data, function (k, v) { result.push({value: k, text: v}); }); } return result; }, option: function(key, value) { this.options[key] = value; if(key === 'source') { this.sourceData = null; } if(key === 'prepend') { this.prependData = null; } } }); List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { /** Source data for list. If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]` For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order. If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option. If **function**, it should return data in format above (since 1.4.0). Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only). `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]` @property source @type string | array | object | function @default null **/ source: null, /** Data automatically prepended to the beginning of dropdown list. @property prepend @type string | array | object | function @default false **/ prepend: false, /** Error message when list cannot be loaded (e.g. ajax error) @property sourceError @type string @default Error when loading list **/ sourceError: 'Error when loading list', /** if <code>true</code> and source is **string url** - results will be cached for fields with the same source. Usefull for editable column in grid to prevent extra requests. @property sourceCache @type boolean @default true @since 1.2.0 **/ sourceCache: true, /** Additional ajax options to be used in $.ajax() when loading list from server. Useful to send extra parameters (`data` key) or change request method (`type` key). @property sourceOptions @type object|function @default null @since 1.5.0 **/ sourceOptions: null }); $.fn.editabletypes.list = List; }(window.jQuery));