269 lines
9.1 KiB
JavaScript
269 lines
9.1 KiB
JavaScript
/**
|
|
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('typeahead', options, Constructor.defaults);
|
|
|
|
//overriding objects in 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.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]');
|
|
|
|
//set source of 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');
|
|
},
|
|
|
|
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).attr('data-value', item)
|
|
i = $(that.options.item).data('item', item);
|
|
i.find('a').html(that.highlighter(item));
|
|
return i[0];
|
|
});
|
|
|
|
//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:'<input type="text">',
|
|
/**
|
|
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
|
|
});
|
|
|
|
$.fn.editabletypes.typeahead = Constructor;
|
|
|
|
}(window.jQuery)); |