|
|
|
@ -12,10 +12,10 @@ |
|
|
|
|
input |
|
|
|
|
The input, either id or DOM element reference (the input must have an id anyway). |
|
|
|
|
dataLoader(hint, value[, more]) |
|
|
|
|
Callback which should load autocomplete options and then call |
|
|
|
|
hint.replaceItems(newOptions, keepPosition), where: |
|
|
|
|
newOptions is [ [ name, value ], [ name, value ], ... ] and |
|
|
|
|
keepPosition should be true when more was > 0. |
|
|
|
|
Callback which should load autocomplete options and then call: |
|
|
|
|
hint.replaceItems(newOptions, append) |
|
|
|
|
newOptions is [ [ name, value[, disabled ] ], [ name, value ], ... ] |
|
|
|
|
append=(more>0) |
|
|
|
|
Callback parameters: |
|
|
|
|
hint |
|
|
|
|
This SimpleAutocomplete object |
|
|
|
@ -46,9 +46,8 @@ |
|
|
|
|
If emptyText === false, the hint will be hidden instead of showing text. |
|
|
|
|
allowHTML |
|
|
|
|
If true, HTML code will be allowed in option names. |
|
|
|
|
promptHTML |
|
|
|
|
The HTML code to be displayed before the option list ("input prompt"). |
|
|
|
|
Empty by default. |
|
|
|
|
prompt |
|
|
|
|
HTML text to be displayed before a non-empty option list. Empty by default. |
|
|
|
|
delay |
|
|
|
|
If this is set to a non-zero value, the autocompleter does no more than |
|
|
|
|
1 request in each delay milliseconds. |
|
|
|
@ -60,6 +59,10 @@ |
|
|
|
|
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. |
|
|
|
|
|
|
|
|
|
Other methods: |
|
|
|
|
hint.remove() -- Destroy the instance |
|
|
|
|
hint.enableItem(index, enabled) -- Enable or disable an item |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
// *** Constructor ***
|
|
|
|
@ -79,7 +82,7 @@ var SimpleAutocomplete = function(input, dataLoader, params) |
|
|
|
|
this.maxHeight = params.maxHeight; |
|
|
|
|
this.emptyText = params.emptyText; |
|
|
|
|
this.allowHTML = params.allowHTML; |
|
|
|
|
this.promptHTML = params.promptHTML || ''; |
|
|
|
|
this.prompt = params.prompt; |
|
|
|
|
this.delay = params.delay; |
|
|
|
|
this.moreMarker = params.moreMarker; |
|
|
|
|
|
|
|
|
@ -89,11 +92,10 @@ var SimpleAutocomplete = function(input, dataLoader, params) |
|
|
|
|
if (this.delay === undefined) |
|
|
|
|
this.delay = 300; |
|
|
|
|
if (this.emptyText === undefined) |
|
|
|
|
this.emptyText = 'No items found'; |
|
|
|
|
this.emptyText = false; |
|
|
|
|
|
|
|
|
|
// Variables
|
|
|
|
|
this.origAutocomplete = input.autocomplete; |
|
|
|
|
this.isMoreClick = false; |
|
|
|
|
this.more = 0; |
|
|
|
|
this.timer = null; |
|
|
|
|
this.closure = []; |
|
|
|
@ -158,30 +160,24 @@ SimpleAutocomplete.prototype.init = function() |
|
|
|
|
this.onChange(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// obj = [ [ name, value, disabled ], [ name, value ], ... ]
|
|
|
|
|
SimpleAutocomplete.prototype.replaceItems = function(items, keepPosition) |
|
|
|
|
// items = [ [ name, value[, disabled] ], [ name, value ], ... ]
|
|
|
|
|
SimpleAutocomplete.prototype.replaceItems = function(items, append) |
|
|
|
|
{ |
|
|
|
|
this.hintLayer.innerHTML = ''; |
|
|
|
|
if (!keepPosition) |
|
|
|
|
if (!append) |
|
|
|
|
{ |
|
|
|
|
this.hintLayer.scrollTop = 0; |
|
|
|
|
} |
|
|
|
|
this.items = []; |
|
|
|
|
if (!items || items.length == 0) |
|
|
|
|
{ |
|
|
|
|
if (this.emptyText) |
|
|
|
|
this.items = []; |
|
|
|
|
if (!items || items.length == 0) |
|
|
|
|
{ |
|
|
|
|
var d = document.createElement('div'); |
|
|
|
|
d.className = 'hintEmptyText'; |
|
|
|
|
d.innerHTML = this.emptyText; |
|
|
|
|
this.hintLayer.appendChild(d); |
|
|
|
|
if (this.emptyText) |
|
|
|
|
this.hintLayer.innerHTML = '<div class="hintEmptyText">'+this.emptyText+'</div>'; |
|
|
|
|
else |
|
|
|
|
this.disable(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
this.disable(); |
|
|
|
|
return; |
|
|
|
|
this.hintLayer.innerHTML = this.prompt ? '<div class="hintPrompt">'+this.prompt+'</div>' : ''; |
|
|
|
|
this.enable(); |
|
|
|
|
} |
|
|
|
|
this.hintLayer.innerHTML = this.promptHTML; |
|
|
|
|
this.enable(); |
|
|
|
|
var h = {}; |
|
|
|
|
if (this.multipleDelimiter) |
|
|
|
|
{ |
|
|
|
@ -190,7 +186,7 @@ SimpleAutocomplete.prototype.replaceItems = function(items, keepPosition) |
|
|
|
|
h[old[i].trim()] = true; |
|
|
|
|
} |
|
|
|
|
for (var i in items) |
|
|
|
|
this.hintLayer.appendChild(this.makeItem(items[i][0], items[i][1], h[items[i][1]])); |
|
|
|
|
this.hintLayer.appendChild(this.makeItem(items[i][0], items[i][1], items[i][2], h[items[i][1]])); |
|
|
|
|
if (this.maxHeight) |
|
|
|
|
{ |
|
|
|
|
this.hintLayer.style.height = |
|
|
|
@ -230,12 +226,24 @@ SimpleAutocomplete.prototype.remove = function() |
|
|
|
|
this.items = null; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Enable or disable an item
|
|
|
|
|
SimpleAutocomplete.prototype.enableItem = function(index, enabled) |
|
|
|
|
{ |
|
|
|
|
if (this.items && this.items[index]) |
|
|
|
|
{ |
|
|
|
|
this.items[index][2] = enabled; |
|
|
|
|
document.getElementById(this.id+'_item_'+index).style = enabled ? 'hintItem' : 'hintDisabledItem'; |
|
|
|
|
if (this.multipleDelimiter) |
|
|
|
|
document.getElementById(this.id+'_check_'+index).disabled = !enabled; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Create a drop-down list item, include checkbox if this.multipleDelimiter is true
|
|
|
|
|
SimpleAutocomplete.prototype.makeItem = function(name, value, checked) |
|
|
|
|
SimpleAutocomplete.prototype.makeItem = function(name, value, disabled, checked) |
|
|
|
|
{ |
|
|
|
|
var d = document.createElement('div'); |
|
|
|
|
d.id = this.id+'_item_'+this.items.length; |
|
|
|
|
d.className = 'hintItem'; |
|
|
|
|
d.className = disabled ? 'hintDisabledItem' : 'hintItem'; |
|
|
|
|
d.title = value; |
|
|
|
|
if (this.allowHTML) |
|
|
|
|
d.innerHTML = name; |
|
|
|
@ -247,6 +255,7 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked) |
|
|
|
|
c.type = 'checkbox'; |
|
|
|
|
c.id = this.id+'_check_'+this.items.length; |
|
|
|
|
c.checked = checked && true; |
|
|
|
|
c.disabled = disabled && true; |
|
|
|
|
c.value = value; |
|
|
|
|
if (d.childNodes.length) |
|
|
|
|
d.insertBefore(c, d.firstChild); |
|
|
|
@ -257,7 +266,7 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked) |
|
|
|
|
var self = this; |
|
|
|
|
addListener(d, 'mouseover', function() { return self.onItemMouseOver(this); }); |
|
|
|
|
addListener(d, 'mousedown', function(ev) { return self.onItemClick(ev, this); }); |
|
|
|
|
this.items.push([name, value, checked]); |
|
|
|
|
this.items.push([name, value, disabled, checked]); |
|
|
|
|
return d; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -276,13 +285,16 @@ SimpleAutocomplete.prototype.moveHighlight = function(by) |
|
|
|
|
// Make item 'elem' active (highlighted)
|
|
|
|
|
SimpleAutocomplete.prototype.highlightItem = function(elem) |
|
|
|
|
{ |
|
|
|
|
var ni = parseInt(elem.id.substr(this.id.length+6)); |
|
|
|
|
if (this.items[ni][2]) |
|
|
|
|
return false; |
|
|
|
|
if (this.selectedIndex >= 0) |
|
|
|
|
{ |
|
|
|
|
var c = this.getItem(); |
|
|
|
|
if (c) |
|
|
|
|
c.className = 'hintItem'; |
|
|
|
|
} |
|
|
|
|
this.selectedIndex = parseInt(elem.id.substr(this.id.length+6)); |
|
|
|
|
this.selectedIndex = ni; |
|
|
|
|
elem.className = 'hintActiveItem'; |
|
|
|
|
return false; |
|
|
|
|
}; |
|
|
|
@ -307,12 +319,12 @@ SimpleAutocomplete.prototype.selectItem = function(index) |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
document.getElementById(this.id+'_check_'+index).checked = this.items[index][2] = !this.items[index][2]; |
|
|
|
|
document.getElementById(this.id+'_check_'+index).checked = this.items[index][3] = !this.items[index][3]; |
|
|
|
|
var old = this.input.value.split(this.multipleDelimiter); |
|
|
|
|
for (var i = 0; i < old.length; i++) |
|
|
|
|
old[i] = old[i].trim(); |
|
|
|
|
// Turn the clicked item on or off, preserving order
|
|
|
|
|
if (!this.items[index][2]) |
|
|
|
|
if (!this.items[index][3]) |
|
|
|
|
{ |
|
|
|
|
for (var i = old.length-1; i >= 0; i--) |
|
|
|
|
if (old[i] == this.items[index][1]) |
|
|
|
@ -323,7 +335,7 @@ SimpleAutocomplete.prototype.selectItem = function(index) |
|
|
|
|
{ |
|
|
|
|
var h = {}; |
|
|
|
|
for (var i = 0; i < this.items.length; i++) |
|
|
|
|
if (this.items[i][2]) |
|
|
|
|
if (this.items[i][3]) |
|
|
|
|
h[this.items[i][1]] = true; |
|
|
|
|
var nl = []; |
|
|
|
|
for (var i = 0; i < old.length; i++) |
|
|
|
@ -335,7 +347,7 @@ SimpleAutocomplete.prototype.selectItem = function(index) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
for (var i = 0; i < this.items.length; i++) |
|
|
|
|
if (this.items[i][2] && h[this.items[i][1]]) |
|
|
|
|
if (this.items[i][3] && h[this.items[i][1]]) |
|
|
|
|
nl.push(this.items[i][1]); |
|
|
|
|
this.input.value = nl.join(this.multipleDelimiter+' '); |
|
|
|
|
} |
|
|
|
@ -417,10 +429,10 @@ SimpleAutocomplete.prototype.onItemClick = function(ev, elm) |
|
|
|
|
if (this.moreMarker && this.items[index][1] == this.moreMarker) |
|
|
|
|
{ |
|
|
|
|
// User clicked 'more'. Load more items without delay.
|
|
|
|
|
this.isMoreClick = true; |
|
|
|
|
this.items.splice(index, 1); |
|
|
|
|
elm.parentNode.removeChild(elm); |
|
|
|
|
this.more++; |
|
|
|
|
this.onChange(); |
|
|
|
|
this.isMoreClick = false; |
|
|
|
|
this.onChange(true); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
this.selectItem(index); |
|
|
|
@ -428,17 +440,15 @@ SimpleAutocomplete.prototype.onItemClick = function(ev, elm) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Handle user input, load new items
|
|
|
|
|
SimpleAutocomplete.prototype.onChange = function() |
|
|
|
|
SimpleAutocomplete.prototype.onChange = function(force) |
|
|
|
|
{ |
|
|
|
|
var v = this.input.value.trim(); |
|
|
|
|
if (!this.isMoreClick) |
|
|
|
|
{ |
|
|
|
|
if (!force) |
|
|
|
|
this.more = 0; |
|
|
|
|
} |
|
|
|
|
if (v != this.curValue || this.isMoreClick) |
|
|
|
|
if (v != this.curValue || force) |
|
|
|
|
{ |
|
|
|
|
this.curValue = v; |
|
|
|
|
if (!this.delay || this.isMoreClick) |
|
|
|
|
if (!this.delay || force) |
|
|
|
|
this.dataLoader(this, v, this.more); |
|
|
|
|
else if (!this.timer) |
|
|
|
|
{ |
|
|
|
|