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

102
hinter.js
View File

@ -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)
{