Skip to content

Commit 8eb4ec1

Browse files
committed
added ability to autocomplete multiple values (comma delimited)
1 parent 0fd95f7 commit 8eb4ec1

File tree

2 files changed

+143
-7
lines changed

2 files changed

+143
-7
lines changed

doc/jquery.autocomplete.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ autoWidth (default value: "min-width")
179179
The CSS property to set the width of the results list to. Leave empty to stop jquery-autocomplete
180180
from interfering with your results width. Other sensible values are "min-width" (default) and "width".
181181

182+
useDelimiter (default value: false)
183+
Whether or not to delimit separate autocomplete strings within the same input field with a
184+
comma. For example, you may want to allow the user to enter a comma separated list of values--and
185+
each value will autocomplete without erasing the other delimited values in the input field.
186+
182187

183188
More advanced options :
184189
=======================

src/jquery.autocomplete.js

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@
7676
onFinish: null,
7777
matchStringConverter: null,
7878
beforeUseConverter: null,
79-
autoWidth: 'min-width'
79+
autoWidth: 'min-width',
80+
useDelimiter: false,
81+
delimiterChar: ',',
82+
delimiterKeyCode: 188
8083
};
8184

8285
/**
@@ -322,6 +325,22 @@
322325
self.lastKeyPressed_ = e.keyCode;
323326
switch(self.lastKeyPressed_) {
324327

328+
case self.options.delimiterKeyCode: // comma = 188
329+
if (self.options.useDelimiter && self.active_) {
330+
self.selectCurrent();
331+
}
332+
break;
333+
334+
// ignore navigational & special keys
335+
case 35: // end
336+
case 36: // home
337+
case 16: // shift
338+
case 17: // ctrl
339+
case 18: // alt
340+
case 37: // left
341+
case 39: // right
342+
break;
343+
325344
case 38: // up
326345
e.preventDefault();
327346
if (self.active_) {
@@ -710,6 +729,7 @@
710729
* @param b
711730
*/
712731
$.Autocompleter.prototype.beforeUseConverter = function(s, a, b) {
732+
s = this.getValue();
713733
var converter = this.options.beforeUseConverter;
714734
if ($.isFunction(converter)) {
715735
s = converter(s, a, b);
@@ -813,8 +833,14 @@
813833
valueLength = value.length;
814834
filterLength = filter.length;
815835
if (lcValue.substr(0, filterLength) === lcFilter) {
816-
this.dom.$elem.val(value);
817-
this.selectRange(filterLength, valueLength);
836+
var d = this.getDelimiterOffsets();
837+
if ( d.start == 0 ) {
838+
this.setValue(value);
839+
this.selectRange(filterLength + d.start, valueLength + d.start);
840+
} else {
841+
this.setValue(' ' + value);
842+
this.selectRange(filterLength + d.start + 1, valueLength + d.start + 1);
843+
}
818844
return true;
819845
}
820846
}
@@ -879,10 +905,28 @@
879905
var processedDisplayValue = this.beforeUseConverter(displayValue);
880906
this.lastProcessedValue_ = processedDisplayValue;
881907
this.lastSelectedValue_ = processedDisplayValue;
882-
this.dom.$elem.val(displayValue).focus();
883-
this.setCaret(displayValue.length);
908+
var d = this.getDelimiterOffsets();
909+
var delimiter = this.options.delimiterChar;
910+
var elem = this.dom.$elem;
911+
var extraCaretPos = 0;
912+
if ( this.options.useDelimiter ) {
913+
// if there is a preceding delimiter, add a space after the delimiter
914+
if ( elem.val().substring(d.start-1, d.start) == delimiter ) {
915+
displayValue = ' ' + displayValue;
916+
}
917+
// if there is not already a delimiter trailing this value, add it
918+
if ( elem.val().substring(d.end, d.end+1) != delimiter && this.lastKeyPressed_ != this.options.delimiterKeyCode ) {
919+
displayValue = displayValue + delimiter;
920+
} else {
921+
// move the cursor after the existing trailing delimiter
922+
extraCaretPos = 1;
923+
}
924+
}
925+
this.setValue(displayValue);
926+
elem.focus();
927+
this.setCaret(d.start + displayValue.length + extraCaretPos);
884928
this.callHook('onItemSelect', { value: value, data: data });
885-
this.deactivate(false);
929+
this.deactivate(true);
886930
};
887931

888932
$.Autocompleter.prototype.displayValue = function(value, data) {
@@ -906,7 +950,7 @@
906950
if (finish) {
907951
if (this.lastProcessedValue_ !== this.lastSelectedValue_) {
908952
if (this.options.mustMatch) {
909-
this.dom.$elem.val('');
953+
this.setValue('');
910954
}
911955
this.callHook('onNoMatch');
912956
}
@@ -943,4 +987,91 @@
943987
this.selectRange(pos, pos);
944988
};
945989

990+
/**
991+
* Get caret position
992+
*/
993+
$.Autocompleter.prototype.getCaret = function() {
994+
var elem = this.dom.$elem;
995+
if ($.browser.msie) {
996+
// ie
997+
var selection = document.selection;
998+
if (elem[0].tagName.toLowerCase() != 'textarea') {
999+
var val = elem.val();
1000+
var range = selection.createRange().duplicate();
1001+
range.moveEnd('character', val.length);
1002+
var s = ( range.text == '' ? val.length : val.lastIndexOf(range.text) );
1003+
range = selection.createRange().duplicate();
1004+
range.moveStart('character', -val.length);
1005+
var e = range.text.length;
1006+
} else {
1007+
var range = selection.createRange();
1008+
var stored_range = range.duplicate();
1009+
stored_range.moveToElementText(elem[0]);
1010+
stored_range.setEndPoint('EndToEnd', range);
1011+
var s = stored_range.text.length - range.text.length;
1012+
var e = s + range.text.length;
1013+
}
1014+
} else {
1015+
// ff, chrome, safari
1016+
var s = elem[0].selectionStart;
1017+
var e = elem[0].selectionEnd;
1018+
}
1019+
return {
1020+
start: s,
1021+
end: e
1022+
};
1023+
};
1024+
1025+
/**
1026+
* Set the value that is currently being autocompleted
1027+
* @param {String} value
1028+
*/
1029+
$.Autocompleter.prototype.setValue = function(value) {
1030+
if ( this.options.useDelimiter ) {
1031+
// set the substring between the current delimiters
1032+
var val = this.dom.$elem.val();
1033+
var d = this.getDelimiterOffsets();
1034+
var preVal = val.substring(0, d.start);
1035+
var postVal = val.substring(d.end);
1036+
value = preVal + value + postVal;
1037+
}
1038+
this.dom.$elem.val(value);
1039+
};
1040+
1041+
/**
1042+
* Get the value currently being autocompleted
1043+
* @param {String} value
1044+
*/
1045+
$.Autocompleter.prototype.getValue = function() {
1046+
var val = this.dom.$elem.val();
1047+
if ( this.options.useDelimiter ) {
1048+
var d = this.getDelimiterOffsets();
1049+
return val.substring(d.start, d.end).trim();
1050+
} else {
1051+
return val;
1052+
}
1053+
};
1054+
1055+
/**
1056+
* Get the offsets of the value currently being autocompleted
1057+
*/
1058+
$.Autocompleter.prototype.getDelimiterOffsets = function() {
1059+
var val = this.dom.$elem.val();
1060+
if ( this.options.useDelimiter ) {
1061+
var preCaretVal = val.substring(0, this.getCaret().start);
1062+
var start = preCaretVal.lastIndexOf(this.options.delimiterChar) + 1;
1063+
var postCaretVal = val.substring(this.getCaret().start);
1064+
var end = postCaretVal.indexOf(this.options.delimiterChar);
1065+
if ( end == -1 ) end = val.length;
1066+
end += this.getCaret().start;
1067+
} else {
1068+
start = 0;
1069+
end = val.length;
1070+
}
1071+
return {
1072+
start: start,
1073+
end: end
1074+
};
1075+
};
1076+
9461077
})(jQuery);

0 commit comments

Comments
 (0)