Fix overlapping ids, support loading more items in hint

master
Vitaliy Filippov 2013-01-17 18:59:14 +00:00
parent 01cad41a87
commit 2faa1f7732
2 changed files with 66 additions and 16 deletions

View File

@ -1,12 +1,12 @@
.hintLayer { .hintLayer {
border: 1px solid gray; border: 1px solid gray;
color: gray; color: gray;
width: 200pt;
background-color: white; background-color: white;
font-size: 80%; font-size: 80%;
max-height: 300pt; max-height: 300pt;
overflow-y: scroll; overflow-y: scroll;
overflow: -moz-scrollbars-vertical; overflow: -moz-scrollbars-vertical;
z-index: 1000;
} }
.hintEmptyText { .hintEmptyText {
padding: 3px; padding: 3px;
@ -16,15 +16,20 @@
cursor: pointer; cursor: pointer;
padding: 1px 3px; padding: 1px 3px;
vertical-align: middle; vertical-align: middle;
white-space: nowrap;
} }
.hintActiveItem { .hintActiveItem {
color: white; color: white;
background-color: #008; background-color: #008;
cursor: pointer; cursor: pointer;
padding: 1px 3px; padding: 1px 3px;
white-space: nowrap;
} }
.hintItem input, .hintActiveItem input { .hintItem input, .hintActiveItem input {
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
margin-right: 3px; margin-right: 3px;
} }
.hintItem img, .hintActiveItem img {
vertical-align: middle;
}

View File

@ -11,11 +11,21 @@
Parameters: Parameters:
input input
The input, either id or DOM element reference (the input must have an id anyway). The input, either id or DOM element reference (the input must have an id anyway).
dataLoader(hint, value) dataLoader(hint, value[, more])
Callback which should load autocomplete options and then call Callback which should load autocomplete options and then call
hint.replaceItems([ [ name, value ], [ name, value ], ... ]) hint.replaceItems(newOptions, keepPosition), where:
'hint' parameter will be this autocompleter object, and the guess newOptions is [ [ name, value ], [ name, value ], ... ] and
should be done based on 'value' parameter (string). keepPosition should be true when more was > 0.
Callback parameters:
hint
This SimpleAutocomplete object
value
The string guess should be done based on
more
This is optional and means the times user has triggered 'load more items'.
i.e. if the original list had 10 items (more=0), after first 'more' click
user would expect 20 items (more=1), and etc.
See also moreMarker option below.
params attribute is an object with optional parameters: params attribute is an object with optional parameters:
multipleDelimiter multipleDelimiter
@ -39,6 +49,14 @@
delay delay
If this is set to a non-zero value, the autocompleter does no more than If this is set to a non-zero value, the autocompleter does no more than
1 request in each delay milliseconds. 1 request in each delay milliseconds.
moreMarker
The server supplying hint options usually limits their count.
But it's not always convenient having to type additional characters for
narrowing down the selection. Optionally you can supply additional item
with special value '#MORE' (or 'moreMarker' option value if it is there)
at the end of the list, and when it will be clicked, SimpleAutocomplete
will issue another request to dataLoader with incremented 'more' parameter.
You can also set moreMarker to false to disable this feature.
*/ */
// *** Constructor *** // *** Constructor ***
@ -59,18 +77,24 @@ var SimpleAutocomplete = function(input, dataLoader, params)
this.emptyText = params.emptyText; this.emptyText = params.emptyText;
this.allowHTML = params.allowHTML; this.allowHTML = params.allowHTML;
this.delay = params.delay; this.delay = params.delay;
this.moreMarker = params.moreMarker;
// Default values
if (this.moreMarker === undefined)
this.moreMarker = '#MORE';
if (this.delay === undefined) if (this.delay === undefined)
this.delay = 300; this.delay = 300;
if (this.emptyText === undefined) if (this.emptyText === undefined)
this.emptyText = 'No items found'; this.emptyText = 'No items found';
// Variables // Variables
this.isMoreClick = false;
this.more = 0;
this.timer = null; this.timer = null;
this.closure = []; this.closure = [];
this.items = []; this.items = [];
this.skipHideCounter = 0; this.skipHideCounter = 0;
this.selectedIndex = -1; this.selectedIndex = -1;
this.id = input.id;
this.disabled = false; this.disabled = false;
// *** Call initialise *** // *** Call initialise ***
@ -83,6 +107,10 @@ var SimpleAutocomplete = function(input, dataLoader, params)
SimpleAutocomplete.prototype.init = function() SimpleAutocomplete.prototype.init = function()
{ {
var e = this.input; var e = this.input;
var l = SimpleAutocomplete.SimpleAutocompletes;
this.id = this.input.id + l.length;
l.push(this);
var p = getOffset(e); var p = getOffset(e);
// Create hint layer // Create hint layer
@ -106,7 +134,6 @@ SimpleAutocomplete.prototype.init = function()
// Remember instance // Remember instance
e.SimpleAutocomplete_input = this; e.SimpleAutocomplete_input = this;
t.SimpleAutocomplete_layer = this; t.SimpleAutocomplete_layer = this;
SimpleAutocomplete.SimpleAutocompletes.push(this);
// Set event listeners // Set event listeners
var self = this; var self = this;
@ -127,10 +154,13 @@ SimpleAutocomplete.prototype.init = function()
}; };
// obj = [ [ name, value, disabled ], [ name, value ], ... ] // obj = [ [ name, value, disabled ], [ name, value ], ... ]
SimpleAutocomplete.prototype.replaceItems = function(items) SimpleAutocomplete.prototype.replaceItems = function(items, keepPosition)
{ {
this.hintLayer.innerHTML = ''; this.hintLayer.innerHTML = '';
this.hintLayer.scrollTop = 0; if (!keepPosition)
{
this.hintLayer.scrollTop = 0;
}
this.items = []; this.items = [];
if (!items || items.length == 0) if (!items || items.length == 0)
{ {
@ -203,6 +233,8 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked)
d.title = value; d.title = value;
if (this.allowHTML) if (this.allowHTML)
d.innerHTML = name; d.innerHTML = name;
else
d.appendChild(document.createTextNode(name));
if (this.multipleDelimiter) if (this.multipleDelimiter)
{ {
var c = document.createElement('input'); var c = document.createElement('input');
@ -216,8 +248,6 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked)
d.appendChild(c); d.appendChild(c);
addListener(c, 'click', this.preventCheck); addListener(c, 'click', this.preventCheck);
} }
if (!this.allowHTML)
d.appendChild(document.createTextNode(name));
var self = this; var self = this;
addListener(d, 'mouseover', function() { return self.onItemMouseOver(this); }); addListener(d, 'mouseover', function() { return self.onItemMouseOver(this); });
addListener(d, 'mousedown', function(ev) { return self.onItemClick(ev, this); }); addListener(d, 'mousedown', function(ev) { return self.onItemClick(ev, this); });
@ -275,6 +305,7 @@ SimpleAutocomplete.prototype.selectItem = function(index)
var old = this.input.value.split(this.multipleDelimiter); var old = this.input.value.split(this.multipleDelimiter);
for (var i = 0; i < old.length; i++) for (var i = 0; i < old.length; i++)
old[i] = old[i].trim(); old[i] = old[i].trim();
// Turn the clicked item on or off, preserving order
if (!this.items[index][2]) if (!this.items[index][2])
{ {
for (var i = old.length-1; i >= 0; i--) for (var i = old.length-1; i >= 0; i--)
@ -376,7 +407,17 @@ SimpleAutocomplete.prototype.onItemMouseOver = function(elm)
// Handle item clicks // Handle item clicks
SimpleAutocomplete.prototype.onItemClick = function(ev, elm) SimpleAutocomplete.prototype.onItemClick = function(ev, elm)
{ {
this.selectItem(parseInt(elm.id.substr(this.id.length+6))); var index = parseInt(elm.id.substr(this.id.length+6));
if (this.moreMarker && this.items[index][1] == this.moreMarker)
{
// User clicked 'more'. Load more items without delay.
this.isMoreClick = true;
this.more++;
this.onChange();
this.isMoreClick = false;
return true;
}
this.selectItem(index);
return true; return true;
}; };
@ -384,16 +425,20 @@ SimpleAutocomplete.prototype.onItemClick = function(ev, elm)
SimpleAutocomplete.prototype.onChange = function() SimpleAutocomplete.prototype.onChange = function()
{ {
var v = this.input.value.trim(); var v = this.input.value.trim();
if (v != this.curValue) if (!this.isMoreClick)
{
this.more = 0;
}
if (v != this.curValue || this.isMoreClick)
{ {
this.curValue = v; this.curValue = v;
if (!this.delay) if (!this.delay || this.isMoreClick)
this.dataLoader(this, v); this.dataLoader(this, v, this.more);
else if (!this.timer) else if (!this.timer)
{ {
var self = this; var self = this;
this.timer = setTimeout(function() { this.timer = setTimeout(function() {
self.dataLoader(self, self.curValue); self.dataLoader(self, self.curValue, self.more);
self.timer = null; self.timer = null;
}, this.delay); }, this.delay);
} }