/** List - abstract class for inputs that have source option loaded from js array or via ajax @class list @extends abstractinput **/ (function ($) { var List = function (options) { }; $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); $.extend(List.prototype, { render: function () { var deferred = $.Deferred(); this.error = null; this.sourceData = null; this.prependData = 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) { //if allready loaded just call success if($.isArray(this.sourceData)) { success.call(this); return; } // try parse json in single quotes (for double quotes jquery does automatically) try { this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false); } catch (e) { error.call(this); return; } //loading from url if (typeof this.options.source === 'string') { //try to get from cache if(this.options.sourceCache) { var cacheID = this.options.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 = []; } } //loading sourceData from server $.ajax({ url: this.options.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) }); } else { //options as json/array/function if (typeof this.options.source === 'function') { this.sourceData = this.makeArray(this.options.source()); } else { this.sourceData = this.makeArray(this.options.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)) { //try parse json in single quotes this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); if (typeof this.options.prepend === 'string') { this.options.prepend = {'': this.options.prepend}; } if (typeof this.options.prepend === 'function') { this.prependData = this.makeArray(this.options.prepend()); } else { 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 = [], iterateEl; if(!data || typeof data === 'string') { return null; } if($.isArray(data)) { //array iterateEl = function (k, v) { obj = {value: k, text: v}; if(count++ >= 2) { return false;// exit each if object has more than one value } }; for(var i = 0; i < data.length; i++) { if(typeof data[i] === 'object') { count = 0; $.each(data[i], iterateEl); if(count === 1) { result.push(obj); } else if(count > 1 && data[i].hasOwnProperty('value') && data[i].hasOwnProperty('text')) { result.push(data[i]); } else { //data contains incorrect objects } } else { result.push({value: data[i], text: data[i]}); } } } else { //object $.each(data, function (k, v) { result.push({value: k, text: v}); }); } return result; } }); List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { /** Source data for list. If **array** - it should be in format: `[{value: 1, text: "text1"}, {...}]` 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). @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 and name. Usefull for editable grids. @property sourceCache @type boolean @default true @since 1.2.0 **/ sourceCache: true }); $.fn.editabletypes.list = List; }(window.jQuery));