Support disabled items, rework #MORE and prompt
parent
8043601c41
commit
4383d9daa8
10
hinter.css
10
hinter.css
|
@ -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;
|
||||||
|
|
90
hinter.js
90
hinter.js
|
@ -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)
|
if (this.emptyText)
|
||||||
{
|
this.hintLayer.innerHTML = '<div class="hintEmptyText">'+this.emptyText+'</div>';
|
||||||
var d = document.createElement('div');
|
|
||||||
d.className = 'hintEmptyText';
|
|
||||||
d.innerHTML = this.emptyText;
|
|
||||||
this.hintLayer.appendChild(d);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
this.disable();
|
this.disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.hintLayer.innerHTML = this.promptHTML;
|
this.hintLayer.innerHTML = this.prompt ? '<div class="hintPrompt">'+this.prompt+'</div>' : '';
|
||||||
this.enable();
|
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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue