Support disabled items, rework #MORE and prompt

master
Vitaliy Filippov 2013-03-05 16:26:55 +00:00
parent 8043601c41
commit 4383d9daa8
2 changed files with 66 additions and 46 deletions

View File

@ -11,6 +11,11 @@
.hintEmptyText { .hintEmptyText {
padding: 3px; padding: 3px;
} }
.hintPrompt {
padding: 3px;
color: gray;
font-style: italic;
}
.hintItem { .hintItem {
color: black; color: black;
cursor: pointer; cursor: pointer;
@ -18,6 +23,11 @@
vertical-align: middle; vertical-align: middle;
white-space: nowrap; white-space: nowrap;
} }
.hintDisabledItem {
color: gray;
padding: 1px 3px;
white-space: nowrap;
}
.hintActiveItem { .hintActiveItem {
color: white; color: white;
background-color: #008; background-color: #008;

102
hinter.js
View File

@ -12,10 +12,10 @@
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[, more]) dataLoader(hint, value[, more])
Callback which should load autocomplete options and then call Callback which should load autocomplete options and then call:
hint.replaceItems(newOptions, keepPosition), where: hint.replaceItems(newOptions, append)
newOptions is [ [ name, value ], [ name, value ], ... ] and newOptions is [ [ name, value[, disabled ] ], [ name, value ], ... ]
keepPosition should be true when more was > 0. append=(more>0)
Callback parameters: Callback parameters:
hint hint
This SimpleAutocomplete object This SimpleAutocomplete object
@ -46,9 +46,8 @@
If emptyText === false, the hint will be hidden instead of showing text. If emptyText === false, the hint will be hidden instead of showing text.
allowHTML allowHTML
If true, HTML code will be allowed in option names. If true, HTML code will be allowed in option names.
promptHTML prompt
The HTML code to be displayed before the option list ("input prompt"). HTML text to be displayed before a non-empty option list. Empty by default.
Empty by default.
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.
@ -60,6 +59,10 @@
at the end of the list, and when it will be clicked, SimpleAutocomplete at the end of the list, and when it will be clicked, SimpleAutocomplete
will issue another request to dataLoader with incremented 'more' parameter. will issue another request to dataLoader with incremented 'more' parameter.
You can also set moreMarker to false to disable this feature. 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 *** // *** Constructor ***
@ -79,7 +82,7 @@ var SimpleAutocomplete = function(input, dataLoader, params)
this.maxHeight = params.maxHeight; this.maxHeight = params.maxHeight;
this.emptyText = params.emptyText; this.emptyText = params.emptyText;
this.allowHTML = params.allowHTML; this.allowHTML = params.allowHTML;
this.promptHTML = params.promptHTML || ''; this.prompt = params.prompt;
this.delay = params.delay; this.delay = params.delay;
this.moreMarker = params.moreMarker; this.moreMarker = params.moreMarker;
@ -89,11 +92,10 @@ var SimpleAutocomplete = function(input, dataLoader, params)
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 = false;
// Variables // Variables
this.origAutocomplete = input.autocomplete; this.origAutocomplete = input.autocomplete;
this.isMoreClick = false;
this.more = 0; this.more = 0;
this.timer = null; this.timer = null;
this.closure = []; this.closure = [];
@ -158,30 +160,24 @@ SimpleAutocomplete.prototype.init = function()
this.onChange(); this.onChange();
}; };
// obj = [ [ name, value, disabled ], [ name, value ], ... ] // items = [ [ name, value[, disabled] ], [ name, value ], ... ]
SimpleAutocomplete.prototype.replaceItems = function(items, keepPosition) SimpleAutocomplete.prototype.replaceItems = function(items, append)
{ {
this.hintLayer.innerHTML = ''; if (!append)
if (!keepPosition)
{ {
this.hintLayer.scrollTop = 0; this.hintLayer.scrollTop = 0;
} this.items = [];
this.items = []; if (!items || items.length == 0)
if (!items || items.length == 0)
{
if (this.emptyText)
{ {
var d = document.createElement('div'); if (this.emptyText)
d.className = 'hintEmptyText'; this.hintLayer.innerHTML = '<div class="hintEmptyText">'+this.emptyText+'</div>';
d.innerHTML = this.emptyText; else
this.hintLayer.appendChild(d); this.disable();
return;
} }
else this.hintLayer.innerHTML = this.prompt ? '<div class="hintPrompt">'+this.prompt+'</div>' : '';
this.disable(); this.enable();
return;
} }
this.hintLayer.innerHTML = this.promptHTML;
this.enable();
var h = {}; var h = {};
if (this.multipleDelimiter) if (this.multipleDelimiter)
{ {
@ -190,7 +186,7 @@ SimpleAutocomplete.prototype.replaceItems = function(items, keepPosition)
h[old[i].trim()] = true; h[old[i].trim()] = true;
} }
for (var i in items) 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) if (this.maxHeight)
{ {
this.hintLayer.style.height = this.hintLayer.style.height =
@ -230,12 +226,24 @@ SimpleAutocomplete.prototype.remove = function()
this.items = null; 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 // 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'); var d = document.createElement('div');
d.id = this.id+'_item_'+this.items.length; d.id = this.id+'_item_'+this.items.length;
d.className = 'hintItem'; d.className = disabled ? 'hintDisabledItem' : 'hintItem';
d.title = value; d.title = value;
if (this.allowHTML) if (this.allowHTML)
d.innerHTML = name; d.innerHTML = name;
@ -247,6 +255,7 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked)
c.type = 'checkbox'; c.type = 'checkbox';
c.id = this.id+'_check_'+this.items.length; c.id = this.id+'_check_'+this.items.length;
c.checked = checked && true; c.checked = checked && true;
c.disabled = disabled && true;
c.value = value; c.value = value;
if (d.childNodes.length) if (d.childNodes.length)
d.insertBefore(c, d.firstChild); d.insertBefore(c, d.firstChild);
@ -257,7 +266,7 @@ SimpleAutocomplete.prototype.makeItem = function(name, value, checked)
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); });
this.items.push([name, value, checked]); this.items.push([name, value, disabled, checked]);
return d; return d;
}; };
@ -276,13 +285,16 @@ SimpleAutocomplete.prototype.moveHighlight = function(by)
// Make item 'elem' active (highlighted) // Make item 'elem' active (highlighted)
SimpleAutocomplete.prototype.highlightItem = function(elem) 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) if (this.selectedIndex >= 0)
{ {
var c = this.getItem(); var c = this.getItem();
if (c) if (c)
c.className = 'hintItem'; c.className = 'hintItem';
} }
this.selectedIndex = parseInt(elem.id.substr(this.id.length+6)); this.selectedIndex = ni;
elem.className = 'hintActiveItem'; elem.className = 'hintActiveItem';
return false; return false;
}; };
@ -307,12 +319,12 @@ SimpleAutocomplete.prototype.selectItem = function(index)
} }
else 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); 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 // 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--) for (var i = old.length-1; i >= 0; i--)
if (old[i] == this.items[index][1]) if (old[i] == this.items[index][1])
@ -323,7 +335,7 @@ SimpleAutocomplete.prototype.selectItem = function(index)
{ {
var h = {}; var h = {};
for (var i = 0; i < this.items.length; i++) 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; h[this.items[i][1]] = true;
var nl = []; var nl = [];
for (var i = 0; i < old.length; i++) 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++) 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]); nl.push(this.items[i][1]);
this.input.value = nl.join(this.multipleDelimiter+' '); 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) if (this.moreMarker && this.items[index][1] == this.moreMarker)
{ {
// User clicked 'more'. Load more items without delay. // User clicked 'more'. Load more items without delay.
this.isMoreClick = true; this.items.splice(index, 1);
elm.parentNode.removeChild(elm);
this.more++; this.more++;
this.onChange(); this.onChange(true);
this.isMoreClick = false;
return true; return true;
} }
this.selectItem(index); this.selectItem(index);
@ -428,17 +440,15 @@ SimpleAutocomplete.prototype.onItemClick = function(ev, elm)
}; };
// Handle user input, load new items // Handle user input, load new items
SimpleAutocomplete.prototype.onChange = function() SimpleAutocomplete.prototype.onChange = function(force)
{ {
var v = this.input.value.trim(); var v = this.input.value.trim();
if (!this.isMoreClick) if (!force)
{
this.more = 0; this.more = 0;
} if (v != this.curValue || force)
if (v != this.curValue || this.isMoreClick)
{ {
this.curValue = v; this.curValue = v;
if (!this.delay || this.isMoreClick) if (!this.delay || force)
this.dataLoader(this, v, this.more); this.dataLoader(this, v, this.more);
else if (!this.timer) else if (!this.timer)
{ {