diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 90c417d..da494b4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,10 @@ X-editable changelog ============================= - + +Version 1.0.2 wip +---------------------------- +[enh] updated docs: inputs dropdown menu, global templates section (vitalets) + Version 1.0.1 Nov 22, 2012 ---------------------------- diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index 8a975b0..d93042a 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -17,7 +17,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. EditableForm.prototype = { constructor: EditableForm, - initInput: function() { + initInput: function() { //called once var TypeConstructor, typeOptions; //create input of specified type @@ -247,7 +247,18 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. option: function(key, value) { this.options[key] = value; - } + if(key === 'value') { + this.setValue(value); + } + }, + + setValue: function(value, convertStr) { + if(convertStr) { + this.value = this.input.str2value(value); + } else { + this.value = value; + } + } }; /* diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js new file mode 100644 index 0000000..158d509 --- /dev/null +++ b/src/inputs/checklist.js @@ -0,0 +1,150 @@ +/** +Checklist input. Internally value stored as array. + +@class checklist +@extends list +@final +@example +<a href="#" id="status" data-type="checklist" data-pk="1" data-url="/post" data-original-title="Select options"></a> +<script> +$(function(){ + $('#options').editable({ + value: [2, 3], + source: [ + {value: 1, text: 'Active'}, + {value: 2, text: 'Blocked'}, + {value: 3, text: 'Deleted'} + ] + } + }); +}); +</script> +**/ +(function ($) { + + var Checklist = function (options) { + this.init('checklist', options, Checklist.defaults); + }; + + $.fn.editableform.utils.inherit(Checklist, $.fn.editableform.types.list); + + $.extend(Checklist.prototype, { + renderList: function() { + var $label, $div; + if(!$.isArray(this.sourceData)) { + return; + } + + for(var i=0; i<this.sourceData.length; i++) { + $label = $('<label>').text(' '+this.sourceData[i].text) + .prepend($('<input>', { + type: 'checkbox', + value: this.sourceData[i].value, + name: this.options.name + })); + + $('<div>').append($label).appendTo(this.$input); + } + }, + + value2str: function(value) { + return $.isArray(value) ? value.join($.trim(this.options.separator)) : ''; + }, + + //parse separated string + str2value: function(str) { + var reg, value = null; + if(typeof str === 'string' && str.length) { + reg = new RegExp('\s*'+$.trim(this.options.separator)+'\s*'); + value = str.split(reg); + } else if($.isArray(str)) { + value = str; + } + return value; + }, + + //set checked on required checkboxes + value2input: function(value) { + var $checks = this.$input.find('input[type="checkbox"]'); + $checks.removeAttr('checked'); + if($.isArray(value) && value.length) { + $checks.each(function(i, el) { + if($.inArray($(el).val(), value) !== -1) { + $(el).attr('checked', 'checked'); + } + }); + } + }, + + input2value: function() { + var checked = []; + this.$input.find('input:checked').each(function(i, el) { + checked.push($(el).val()); + }); + return checked; + }, + + //collect text of checked boxes + value2htmlFinal: function(value, element) { + var selected = [], html = ''; + if($.isArray(value) && value.length <= this.options.limit) { + for(var i=0; i<value.length; i++){ + item = this.itemByVal(value[i]); + if(item) { + selected.push($('<div>').text(item.text).html()); + } + } + html = selected.join(this.options.viewseparator); + } else { + html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length); + } + $(element).html(html); + } + }); + + Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { + /** + @property tpl + @default <div></div> + **/ + tpl:'<div></div>', + + /** + Separator of values in string when sending to server + + @property separator + @type string + @default ', ' + **/ + separator: ',', + /** + Separator of text when display as element content. + + @property viewseparator + @type string + @default ', ' + **/ + viewseparator: '<br>', + /** + Maximum number of items shown as element content. + If checked more items - <code>limitText</code> will be shown. + + @property limit + @type integer + @default 4 + **/ + limit: 4, + /** + Text shown when count of checked items is greater than <code>limit</code> parameter. + You can use <code>{checked}</code> and <code>count</code> placeholders. + + @property limitText + @type string + @default 'Checked {checked} options of {count}' + **/ + limitText: 'Checked {checked} options of {count}' + }); + + $.fn.editableform.types.checklist = Checklist; + +}(window.jQuery)); \ No newline at end of file diff --git a/src/inputs/date/date.js b/src/inputs/date/date.js index 38680e8..5081db2 100644 --- a/src/inputs/date/date.js +++ b/src/inputs/date/date.js @@ -5,6 +5,7 @@ For localization you can include js file from here: https://github.com/eternicod @class date @extends abstract +@final @example <a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a> <script> diff --git a/src/inputs/dateui/dateui.js b/src/inputs/dateui/dateui.js index 53ee858..38cc2d3 100644 --- a/src/inputs/dateui/dateui.js +++ b/src/inputs/dateui/dateui.js @@ -5,6 +5,7 @@ Do not use it together with bootstrap-datepicker. @class dateui @extends abstract +@final @example <a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a> <script> diff --git a/src/inputs/list.js b/src/inputs/list.js new file mode 100644 index 0000000..8dba070 --- /dev/null +++ b/src/inputs/list.js @@ -0,0 +1,255 @@ +/** +List - abstract class for inputs that have source option loaded from js array or via ajax + +@class list +@extends abstract +**/ +(function ($) { + + var List = function (options) { + + }; + + $.fn.editableform.utils.inherit(List, $.fn.editableform.types.abstract); + + $.extend(List.prototype, { + render: function () { + List.superclass.render.call(this); + 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) { + var deferred = $.Deferred(); + this.onSourceReady(function () { + this.value2htmlFinal(value, element); + deferred.resolve(); + }, function () { + List.superclass.value2html(this.options.sourceError, element); + 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.editableform.utils.tryParseJson(this.options.source, false); + } catch (e) { + error.call(this); + return; + } + + //loading from url + if (typeof this.options.source === 'string') { + var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''), + 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; + 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; + 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, + data: {name: this.options.name}, + dataType: 'json', + success: $.proxy(function (data) { + cache.loading = false; + // this.options.source = data; + this.sourceData = this.makeArray(data); + if($.isArray(this.sourceData)) { + this.doPrepend(); + //store result in cache + cache.sourceData = this.sourceData; + success.call(this); + $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields + } else { + error.call(this); + $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields + } + }, this), + error: $.proxy(function () { + cache.loading = false; + error.call(this); + $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields + }, this) + }); + } else { //options as json/array + 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.editableform.utils.tryParseJson(this.options.prepend, true); + 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 = [], 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: i, text: data[i]}); + } + } + } else { //object + $.each(data, function (k, v) { + result.push({value: k, text: v}); + }); + } + return result; + }, + + //search for item by particular value + itemByVal: function(val) { + if($.isArray(this.sourceData)) { + for(i=0; i<this.sourceData.length; i++){ + /*jshint eqeqeq: false*/ + if(this.sourceData[i].value == val) { + /*jshint eqeqeq: true*/ + return this.sourceData[i]; + } + } + } + } + + }); + + List.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { + /** + Source data for dropdown list. If string - considered ajax url to load items. Otherwise should be an array. + Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br> + For compability it also supports format <code>{value1: text1, value2: text2 ...}</code> but it does not guarantee elements order. + + @property source + @type string|array|object + @default null + **/ + source:null, + /** + Data automatically prepended to the begining of dropdown list. + + @property prepend + @type string|array|object + @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' + }); + + $.fn.editableform.types.list = List; + +}(window.jQuery)); \ No newline at end of file diff --git a/src/inputs/select.js b/src/inputs/select.js index 8cda196..89b4265 100644 --- a/src/inputs/select.js +++ b/src/inputs/select.js @@ -2,7 +2,8 @@ Select (dropdown) input @class select -@extends abstract +@extends list +@final @example <a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-original-title="Select status"></a> <script> @@ -25,160 +26,10 @@ $(function(){ this.init('select', options, Select.defaults); }; - $.fn.editableform.utils.inherit(Select, $.fn.editableform.types.abstract); + $.fn.editableform.utils.inherit(Select, $.fn.editableform.types.list); $.extend(Select.prototype, { - render: function () { - Select.superclass.render.call(this); - var deferred = $.Deferred(); - this.error = null; - this.sourceData = null; - this.prependData = null; - this.onSourceReady(function () { - this.renderOptions(); - deferred.resolve(); - }, function () { - this.error = this.options.sourceError; - deferred.resolve(); - }); - - return deferred.promise(); - }, - - html2value: function (html) { - return null; //it's not good idea to set value by text for SELECT. Better set NULL - }, - - value2html: function (value, element) { - var deferred = $.Deferred(); - this.onSourceReady(function () { - var i, text = ''; - if($.isArray(this.sourceData)) { - for(i=0; i<this.sourceData.length; i++){ - /*jshint eqeqeq: false*/ - if(this.sourceData[i].value == value) { - /*jshint eqeqeq: true*/ - text = this.sourceData[i].text; - break; - } - } - } - Select.superclass.value2html(text, element); - deferred.resolve(); - }, function () { - Select.superclass.value2html(this.options.sourceError, element); - 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.editableform.utils.tryParseJson(this.options.source, false); - } catch (e) { - error.call(this); - return; - } - - //loading from url - if (typeof this.options.source === 'string') { - var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''), - 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; - 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; - 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, - data: {name: this.options.name}, - dataType: 'json', - success: $.proxy(function (data) { - cache.loading = false; - // this.options.source = data; - this.sourceData = this.makeArray(data); - if($.isArray(this.sourceData)) { - this.doPrepend(); - //store result in cache - cache.sourceData = this.sourceData; - success.call(this); - $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields - } else { - error.call(this); - $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields - } - }, this), - error: $.proxy(function () { - cache.loading = false; - error.call(this); - $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields - }, this) - }); - } else { //options as json/array - 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.editableform.utils.tryParseJson(this.options.prepend, true); - 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); - } - }, - - renderOptions: function() { + renderList: function() { if(!$.isArray(this.sourceData)) { return; } @@ -187,83 +38,24 @@ $(function(){ this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text)); } }, - - /** - * 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; + + value2htmlFinal: function(value, element) { + var text = '', item = this.itemByVal(value); + if(item) { + text = item.text; } - - 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: i, text: data[i]}); - } - } - } else { //object - $.each(data, function (k, v) { - result.push({value: k, text: v}); - }); - } - return result; - } - + Select.superclass.constructor.superclass.value2html(text, element); + } }); - Select.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { + Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { /** @property tpl @default <select></select> **/ - tpl:'<select></select>', - /** - Source data for dropdown list. If string - considered ajax url to load items. Otherwise should be an array. - Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br> - For compability it also supports format <code>{value1: text1, value2: text2 ...}</code> but it does not guarantee elements order. - - @property source - @type string|array|object - @default null - **/ - source:null, - /** - Data automatically prepended to the begining of dropdown list. - - @property prepend - @type string|array|object - @default false - **/ - prepend:false, - /** - Error message shown when list cannot be loaded (e.g. ajax error) - - @property sourceError - @type string - @default Error when loading options - **/ - sourceError: 'Error when loading options' + tpl:'<select></select>' }); $.fn.editableform.types.select = Select; -}(window.jQuery)); +}(window.jQuery)); \ No newline at end of file diff --git a/src/inputs/text.js b/src/inputs/text.js index 702c3f6..8d2f259 100644 --- a/src/inputs/text.js +++ b/src/inputs/text.js @@ -3,6 +3,7 @@ Text input @class text @extends abstract +@final @example <a href="#" id="username" data-type="text" data-pk="1">awesome</a> <script> diff --git a/src/inputs/textarea.js b/src/inputs/textarea.js index c4d867e..eb4cd3c 100644 --- a/src/inputs/textarea.js +++ b/src/inputs/textarea.js @@ -3,6 +3,7 @@ Textarea input @class textarea @extends abstract +@final @example <a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a> <script> diff --git a/test/loader.js b/test/loader.js index 136ba5b..ba91063 100644 --- a/test/loader.js +++ b/test/loader.js @@ -31,9 +31,11 @@ function getAssets(f, c, src, libs) { containers+'editable-container.js', element+'editable-element.js', inputs+'abstract.js', + inputs+'list.js', inputs+'text.js', inputs+'textarea.js', - inputs+'select.js' + inputs+'select.js', + inputs+'checklist.js' ], css = [