diff --git a/src/inputs/list.js b/src/inputs/list.js index 0aab53d..133a9e6 100644 --- a/src/inputs/list.js +++ b/src/inputs/list.js @@ -144,7 +144,7 @@ List - abstract class for inputs that have source option loaded from js array or }, this) }); } else { //options as json/array/function - if (typeof this.options.source === 'function') { + if ($.isFunction(this.options.source)) { this.sourceData = this.makeArray(this.options.source()); } else { this.sourceData = this.makeArray(this.options.source); diff --git a/src/inputs/text.js b/src/inputs/text.js index 93fd193..1d0683d 100644 --- a/src/inputs/text.js +++ b/src/inputs/text.js @@ -48,10 +48,7 @@ $(function(){ .keyup($.proxy(this.toggleClear, this)) .parent().css('position', 'relative'); - this.$clear.click($.proxy(function(){ - this.$clear.hide(); - this.$input.val('').focus(); - }, this)); + this.$clear.click($.proxy(this.clear, this)); } }, @@ -81,7 +78,12 @@ $(function(){ } else { this.$clear.hide(); } - } + }, + + clear: function() { + this.$clear.hide(); + this.$input.val('').focus(); + } }); Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { diff --git a/src/inputs/typeahead.js b/src/inputs/typeahead.js new file mode 100644 index 0000000..1e5c5d4 --- /dev/null +++ b/src/inputs/typeahead.js @@ -0,0 +1,204 @@ +/** +Typeahead input (bootstrap only) + +@class typeahead +@extends list +@final +@example +<a href="#" id="country" data-type="typeahead" data-pk="1" data-url="/post" data-original-title="Input country"></a> +<script> +$(function(){ + $('#country').editable({ + value: 2, + source: [ + {value: 1, text: 'Active'}, + {value: 2, text: 'Blocked'}, + {value: 3, text: 'Deleted'} + ] + } + }); +}); +</script> +**/ +(function ($) { + + var Constructor = function (options) { + this.init('typeahead', options, Constructor.defaults); + + //overriding combodate config (as by default jQuery extend() is not recursive) + this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, { + //set default methods for typeahead to work with objects + matcher: this.matcher, + sorter: this.sorter, + highlighter: this.highlighter, + updater: this.updater + }, options.typeahead); + }; + + $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.list); + + $.extend(Constructor.prototype, { + renderList: function() { + this.options.typeahead.source = this.sourceData; + + this.$input.typeahead(this.options.typeahead); + + //attach own render method + this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead')); + + this.renderClear(); + this.setClass(); + this.setAttr('placeholder'); + }, + + value2htmlFinal: function(value, element) { + if(this.getIsObjects()) { + var items = $.fn.editableutils.itemsByValue(value, this.sourceData); + $(element).text(items.length ? items[0].text : ''); + } else { + $(element).text(value); + } + }, + + html2value: function (html) { + return html ? html : null; + }, + + value2input: function(value) { + if(this.getIsObjects()) { + var items = $.fn.editableutils.itemsByValue(value, this.sourceData); + this.$input.data('value', value).val(items.length ? items[0].text : ''); + } else { + this.$input.val(value); + } + }, + + input2value: function() { + if(this.getIsObjects()) { + var value = this.$input.data('value'), + items = $.fn.editableutils.itemsByValue(value, this.sourceData); + + if(items.length && items[0].text.toLowerCase() === this.$input.val().toLowerCase()) { + return value; + } else { + return null; //entered string not found in source + } + } else { + return this.$input.val(); + } + }, + + /* + if in sourceData values <> texts, put typeahead in "objects" mode: + user must pick some value from list + if all values == texts put typeahead in "strings" mode: + anything what entered is submited. + */ + getIsObjects: function() { + if(this.isObjects === undefined) { + this.isObjects = false; + for(var i=0; i<this.sourceData.length; i++) { + if(this.sourceData[i].value !== this.sourceData[i].text) { + this.isObjects = true; + break; + } + } + } + return this.isObjects; + }, + + /* + Methods borrowed from text input + */ + activate: $.fn.editabletypes.text.prototype.activate, + renderClear: $.fn.editabletypes.text.prototype.renderClear, + postrender: $.fn.editabletypes.text.prototype.postrender, + toggleClear: $.fn.editabletypes.text.prototype.toggleClear, + clear: function() { + $.fn.editabletypes.text.prototype.clear.call(this); + this.$input.data('value', ''); + }, + + + /* + Typeahead option methods used as defaults + */ + matcher: function (item) { + return $.fn.typeahead.Constructor.prototype.matcher.call(this, item.text); + }, + sorter: function (items) { + var beginswith = [] + , caseSensitive = [] + , caseInsensitive = [] + , item + , text + + while (item = items.shift()) { + text = item.text + if (!text.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) + else if (~text.indexOf(this.query)) caseSensitive.push(item) + else caseInsensitive.push(item) + } + + return beginswith.concat(caseSensitive, caseInsensitive) + }, + highlighter: function (item) { + return $.fn.typeahead.Constructor.prototype.highlighter.call(this, item.text); + }, + updater: function (item) { + item = this.$menu.find('.active').data('item'); + this.$element.data('value', item.value); + return item.text; + }, + + /* + Overwrite typeahead's render method to store objects. + There are a lot of disscussion in bootstrap repo on this point and still no result. + See https://github.com/twitter/bootstrap/issues/5967 + + This function just store item in via jQuery data() method instead of attr('data-value') + */ + typeaheadRender: function (items) { + var that = this + + items = $(items).map(function (i, item) { +// i = $(that.options.item).attr('data-value', item) + i = $(that.options.item).data('item', item) + i.find('a').html(that.highlighter(item)) + return i[0] + }) + + items.first().addClass('active') + this.$menu.html(items) + return this + } + + }); + + Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { + /** + @property tpl + @default <input type="text"> + **/ + tpl:'<input type="text">', + /** + Configuration of typeahead.[Possible options](http://twitter.github.com/bootstrap/javascript.html#typeahead). + + @property typeahead + @type object + @default null + **/ + typeahead: null, + /** + Whether to show `clear` button + + @property clear + @type boolean + @default true + **/ + clear: true + }); + + $.fn.editabletypes.typeahead = Constructor; + +}(window.jQuery)); \ No newline at end of file diff --git a/test/loader.js b/test/loader.js index b3eae32..fc0ccfa 100644 --- a/test/loader.js +++ b/test/loader.js @@ -52,7 +52,8 @@ define(function () { 'inputs/textarea': ['inputs/abstract'], 'inputs/abstract': ['editable-form/editable-form-utils'], 'inputs/html5types': ['inputs/text'], - 'inputs/combodate/combodate': ['inputs/abstract', 'inputs/combodate/lib/combodate', 'inputs/combodate/lib/moment.min'], + 'inputs/combodate/combodate': ['inputs/abstract', 'inputs/combodate/lib/combodate', 'inputs/combodate/lib/moment.min'], + 'inputs/typeahead': ['inputs/list'], /* bootstrap @@ -155,6 +156,7 @@ define(function () { //bootstrap shim['editable-form/editable-form'].deps.push('inputs/date/datefield'); shim['editable-form/editable-form'].deps.push('inputs-ext/wysihtml5/wysihtml5'); + shim['editable-form/editable-form'].deps.push('inputs/typeahead'); shim['element/editable-element'].deps.push('editable-form/editable-form-bootstrap'); shim['element/editable-element'].deps.push('containers/editable-popover'); } else if(f === 'jqueryui') {