Last active
December 17, 2015 23:29
-
-
Save vitalets/5689823 to your computer and use it in GitHub Desktop.
X-editable combobox: typeahead + dropdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead). | |
Depending on `source` format typeahead operates in two modes: | |
* **strings**: | |
When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`. | |
User can submit one of these strings or any text entered in input (even if it is not matching source). | |
* **objects**: | |
When `source` defined as array of objects, e.g. `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`. | |
User can submit only values that are in source (otherwise `null` is submitted). This is more like *dropdown* behavior. | |
@class typeahead | |
@extends list | |
@since 1.4.1 | |
@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: 'ru', | |
source: [ | |
{value: 'gb', text: 'Great Britain'}, | |
{value: 'us', text: 'United States'}, | |
{value: 'ru', text: 'Russia'} | |
] | |
}); | |
}); | |
</script> | |
**/ | |
(function ($) { | |
"use strict"; | |
var Constructor = function (options) { | |
this.init('combobox', options, Constructor.defaults); | |
//overriding objects in config (as by default jQuery extend() is not recursive) | |
this.options.combobox = $.extend({}, Constructor.defaults.combobox, { | |
//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); | |
// Переопределение шаблона кнопок | |
// $.fn.editableform.buttons = '<button type="submit" class="btn btn-primary editable-submit"><i class="icon-ok icon-white"></i></button>'; | |
$.extend(Constructor.prototype, { | |
renderList: function() { | |
this.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]'); | |
// console.log(this.options); | |
// set source of typeahead | |
if(!this.options.typeahead) { | |
this.options.typeahead = {}; | |
} | |
this.options.typeahead.source = this.sourceData; | |
// apply typeahead | |
this.$input.typeahead(this.options.typeahead); | |
// patch some methods in typeahead | |
var ta = this.$input.data('typeahead'); | |
ta.render = $.proxy(this.typeaheadRender, ta); | |
ta.select = $.proxy(this.typeaheadSelect, ta); | |
ta.move = $.proxy(this.typeaheadMove, ta); | |
this.renderClear(); | |
this.setClass(); | |
this.setAttr('placeholder'); | |
// ####################################################################### | |
// Генерация стандартных вариантов для выпадающего списка | |
var fillItems = function($menu, data) { | |
// console.log('Enter FillItems for '+ data.length); | |
if($.isArray(data)) { | |
for(var i=0; i<data.length; i++) { | |
var li = $('<li/>') | |
.appendTo($menu); | |
var a = $('<a/>') | |
.text(data[i]) | |
.appendTo(li); | |
} | |
} | |
return $menu; | |
}; | |
// Определяем массив стандартных вариантов и кнопку списка | |
var dropdownArray = this.options.defaultValues; | |
var button = this.$tpl.find('button'); | |
// Создание выпадающего списка стандартных вариантов | |
if ($.isArray(dropdownArray) && dropdownArray.length > 0) { | |
var menuReady = fillItems($(this.options.menu), dropdownArray); | |
// Делаем кнопку активной и добавляем после нее список вариантов | |
button | |
.removeClass('disabled') | |
.attr('title','Стандартные варианты') | |
.after(menuReady); | |
} else { | |
// При отсутствии вариантов и нажатии на кнопку переводим фокус на input | |
button.click(this.$input, function(event) { | |
event.preventDefault(); | |
event.data.focus(); | |
}); | |
} | |
// При выборе ссылки передаем как event.data контекст this.$input к текстовому полю | |
// Устанавливаем в поле новое значение и переводим в него фокус | |
this.$tpl.find('.dropdown-menu a').click(this.$input, function(event) { | |
event.preventDefault(); | |
event.data | |
.val($(this).text()) | |
.focus(); | |
}); | |
// ####################################################################### | |
}, | |
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, typeahead in "objects" mode: | |
user must pick some value from list, otherwise `null` returned. | |
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 | |
*/ | |
/*jshint eqeqeq:false, curly: false, laxcomma: true, asi: true*/ | |
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) { | |
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 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).data('item', item); | |
i.find('a').html(that.highlighter(item)); | |
return i[0]; | |
}); | |
// console.log(that.options); | |
//add option to disable autoselect of first line | |
//see https://github.com/twitter/bootstrap/pull/4164 | |
if (this.options.autoSelect) { | |
items.first().addClass('active'); | |
} | |
this.$menu.html(items); | |
return this; | |
}, | |
//add option to disable autoselect of first line | |
//see https://github.com/twitter/bootstrap/pull/4164 | |
typeaheadSelect: function () { | |
var val = this.$menu.find('.active').data('item') | |
if(this.options.autoSelect || val){ | |
this.$element | |
.val(this.updater(val)) | |
.change() | |
} | |
return this.hide() | |
}, | |
/* | |
if autoSelect = false and nothing matched we need extra press onEnter that is not convinient. | |
This patch fixes it. | |
*/ | |
typeaheadMove: function (e) { | |
if (!this.shown) return | |
switch(e.keyCode) { | |
case 9: // tab | |
case 13: // enter | |
case 27: // escape | |
if (!this.$menu.find('.active').length) return | |
e.preventDefault() | |
break | |
case 38: // up arrow | |
e.preventDefault() | |
this.prev() | |
break | |
case 40: // down arrow | |
e.preventDefault() | |
this.next() | |
break | |
} | |
e.stopPropagation() | |
} | |
/*jshint eqeqeq: true, curly: true, laxcomma: false, asi: false*/ | |
}); | |
// Расширение стандартных опций типа и перезапись необходимых | |
Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { | |
/** | |
@property tpl | |
@default <input type="text"> | |
**/ | |
tpl:'<div class="input-prepend dropdown combobox">'+ | |
'<input type="text">'+ | |
'<button class="btn disabled" data-toggle="dropdown" title="Стандартных вариантов нет">'+ | |
'<i class="icon-chevron-down"></i></button>'+ | |
'</div>', | |
menu:'<ul class="dropdown-menu"></ul>', | |
/** | |
Configuration of typeahead. [Full list of 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, | |
/** | |
Массив для списка стандартных вариантов | |
@property defaultValues | |
@type array | |
@default [] | |
**/ | |
defaultValues: [] | |
}); | |
$.fn.editabletypes.combobox = Constructor; | |
}(window.jQuery)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment