1
0
mirror of synced 2024-11-26 06:46:04 +03:00

add a tokenizer so choices can be automatically created and selected while user is typing or pasting into the field. fixes #101 #81 #292

This commit is contained in:
Igor Vaynberg 2012-08-08 19:33:24 -07:00
parent c2fa04529b
commit 289226cf6c

View File

@ -416,6 +416,59 @@
return count; return count;
} }
/**
* Default tokenizer. This function uses breaks the input on substring match of any string from the
* opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
* two options have to be defined in order for the tokenizer to work.
*
* @param input text user has typed so far or pasted into the search field
* @param selection currently selected choices
* @param selectCallback function(choice) callback tho add the choice to selection
* @param opts select2's opts
* @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
*/
function defaultTokenizer(input, selection, selectCallback, opts) {
var original = input, // store the original so we can compare and know if we need to tell the search to update its text
dupe = false, // check for whether a token we extracted represents a duplicate selected choice
token, // token
index, // position at which the separator was found
i, l, // looping variables
separator; // the matched separator
if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
while (true) {
index = -1;
for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
separator = opts.tokenSeparators[i];
index = input.indexOf(separator);
if (index >= 0) break;
}
if (index < 0) break; // did not find any token separator in the input string, bail
token = input.substring(0, index);
input = input.substring(index + separator.length);
if (token.length > 0) {
token = opts.createSearchChoice(token, selection);
if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
dupe = false;
for (i = 0, l = selection.length; i < l; i++) {
if (equal(opts.id(token), opts.id(selection[i]))) {
dupe = true; break;
}
}
if (!dupe) selectCallback(token);
}
}
}
if (original.localeCompare(input) != 0) return input;
}
/** /**
* blurs any Select2 container that has focus when an element outside them was clicked or received focus * blurs any Select2 container that has focus when an element outside them was clicked or received focus
* *
@ -599,9 +652,6 @@
this.select = select = opts.element; this.select = select = opts.element;
} }
//Custom tags separator.
opts.separator = opts.separator || ",";
if (select) { if (select) {
// these options are not allowed when attached to a select because they are picked up off the element itself // these options are not allowed when attached to a select because they are picked up off the element itself
$.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
@ -1074,12 +1124,19 @@
} }
}, },
/**
* Default tokenizer function which does nothing
*/
tokenize: function() {
},
/** /**
* @param initial whether or not this is the call to this method right after the dropdown has been opened * @param initial whether or not this is the call to this method right after the dropdown has been opened
*/ */
// abstract // abstract
updateResults: function (initial) { updateResults: function (initial) {
var search = this.search, results = this.results, opts = this.opts, data, self=this; var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
// if the search is currently hidden we do not alter the results // if the search is currently hidden we do not alter the results
if (initial !== true && (this.showSearchInput === false || !this.opened())) { if (initial !== true && (this.showSearchInput === false || !this.opened())) {
@ -1115,6 +1172,12 @@
render("<li class='select2-searching'>" + opts.formatSearching() + "</li>"); render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
} }
// give the tokenizer a chance to pre-process the input
input = this.tokenize();
if (input != undefined && input != null) {
search.val(input);
}
this.resultsPage = 1; this.resultsPage = 1;
opts.query({ opts.query({
term: search.val(), term: search.val(),
@ -1889,6 +1952,18 @@
self.postprocessResults(); self.postprocessResults();
}, },
tokenize: function() {
var input = this.search.val();
input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
if (input != null && input != undefined) {
this.search.val(input);
if (input.length > 0) {
this.open();
}
}
},
// multi // multi
onSelect: function (data) { onSelect: function (data) {
this.addSelectedChoice(data); this.addSelectedChoice(data);
@ -1898,10 +1973,9 @@
this.close(); this.close();
this.search.width(10); this.search.width(10);
} else { } else {
if (this.countSelectableResults()>0) {
this.search.width(10); this.search.width(10);
this.resizeSearch(); this.resizeSearch();
if (this.countSelectableResults()>0) {
this.positionDropdown(); this.positionDropdown();
} else { } else {
// if nothing left to select close // if nothing left to select close
@ -2032,7 +2106,6 @@
containerLeft = this.selection.offset().left; containerLeft = this.selection.offset().left;
searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
if (searchWidth < minimumWidth) { if (searchWidth < minimumWidth) {
searchWidth = maxWidth - sideBorderPadding; searchWidth = maxWidth - sideBorderPadding;
} }
@ -2232,7 +2305,10 @@
id: function (e) { return e.id; }, id: function (e) { return e.id; },
matcher: function(term, text) { matcher: function(term, text) {
return text.toUpperCase().indexOf(term.toUpperCase()) >= 0; return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
} },
separator: ",",
tokenSeparators: [],
tokenizer: defaultTokenizer
}; };
// exports // exports