Ещё более простая версия автокомплита (хотя может меньше), и сильно более читаемый код и нет зависимости от кривого exAttach
commit
78a2118554
|
@ -0,0 +1,28 @@
|
|||
.hintLayer {
|
||||
border: 1px solid gray;
|
||||
color: gray;
|
||||
width: 200pt;
|
||||
background-color: white;
|
||||
font-size: 80%;
|
||||
max-height: 300pt;
|
||||
overflow-y: scroll;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
}
|
||||
.hintEmptyText {
|
||||
padding: 3px;
|
||||
}
|
||||
.hintItem {
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
.hintActiveItem {
|
||||
color: white;
|
||||
background-color: #008;
|
||||
cursor: pointer;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
.hintDisabledItem {
|
||||
background-color: white;
|
||||
color: gray;
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
/* Simple autocomplete for text inputs.
|
||||
Usage:
|
||||
1) include hinter.css, hinter.js, offsetRect.js
|
||||
2) var hint = new SimpleAutocomplete(input, dataLoader, onChangeListener, maxHeight, emptyText);
|
||||
Parameters:
|
||||
input - the input, either id or DOM element
|
||||
dataLoader(hint, value) - callback which should load autocomplete options
|
||||
and call hint.replaceItems([ [ name, value ], [ name, value ], ... ])
|
||||
Optional parameters:
|
||||
onChangeListener - callback which is called when an item is selected through the drop-down list
|
||||
maxHeight - maximum hint dropdown height in pixels
|
||||
emptyText - text to show when dataLoader returns no options
|
||||
if emptyText === false, the hint will be hidden
|
||||
Homepage: http://yourcmc.ru/wiki/SHint_JS
|
||||
(c) Vitaliy Filippov 2011
|
||||
*/
|
||||
|
||||
var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight, emptyText)
|
||||
{
|
||||
if (typeof(input) == 'string')
|
||||
input = document.getElementById(input);
|
||||
if (emptyText === undefined)
|
||||
emptyText = 'No items found';
|
||||
|
||||
// Parameters
|
||||
var self = this;
|
||||
self.input = input;
|
||||
self.dataLoader = dataLoader;
|
||||
self.onChangeListener = onChangeListener;
|
||||
self.maxHeight = maxHeight;
|
||||
self.emptyText = emptyText;
|
||||
|
||||
// Variables
|
||||
self.items = [];
|
||||
self.skipHideCounter = 0;
|
||||
self.selectedIndex = -1;
|
||||
self.id = input.id;
|
||||
self.disabled = false;
|
||||
|
||||
// Initialise hinter
|
||||
self.init = function()
|
||||
{
|
||||
var e = self.input;
|
||||
var p = getOffset(e);
|
||||
|
||||
// Create hint layer
|
||||
var t = self.hintLayer = document.createElement('div');
|
||||
t.className = 'hintLayer';
|
||||
t.style.display = 'none';
|
||||
t.style.position = 'absolute';
|
||||
t.style.top = (p.top+e.offsetHeight) + 'px';
|
||||
t.style.zIndex = 1000;
|
||||
t.style.left = p.left + 'px';
|
||||
if (self.maxHeight)
|
||||
{
|
||||
t.style.overflowY = 'scroll';
|
||||
try { t.style.overflow = '-moz-scrollbars-vertical'; } catch(exc) {}
|
||||
t.style.maxHeight = self.maxHeight+'px';
|
||||
if (!t.style.maxHeight)
|
||||
self.scriptMaxHeight = true;
|
||||
}
|
||||
document.body.appendChild(t);
|
||||
|
||||
// Remember instance
|
||||
e.SimpleAutocomplete_input = self;
|
||||
t.SimpleAutocomplete_layer = self;
|
||||
SimpleAutocomplete.SimpleAutocompletes.push(self);
|
||||
|
||||
// Set event listeners
|
||||
var msie = navigator.userAgent.match('MSIE') && !navigator.userAgent.match('Opera');
|
||||
if (msie)
|
||||
addListener(e, 'keydown', self.onKeyPress);
|
||||
else
|
||||
{
|
||||
addListener(e, 'keydown', self.onKeyDown);
|
||||
addListener(e, 'keypress', self.onKeyPress);
|
||||
}
|
||||
addListener(e, 'keyup', self.onKeyUp);
|
||||
addListener(e, 'change', self.onChange);
|
||||
addListener(e, 'focus', self.onInputFocus);
|
||||
addListener(e, 'blur', self.onInputBlur);
|
||||
self.onChange();
|
||||
};
|
||||
|
||||
// obj = [ [ name, value, disabled ], [ name, value ], ... ]
|
||||
self.replaceItems = function(items)
|
||||
{
|
||||
self.hintLayer.innerHTML = '';
|
||||
self.hintLayer.scrollTop = 0;
|
||||
self.items = [];
|
||||
if (!items || items.length == 0)
|
||||
{
|
||||
if (self.emptyText)
|
||||
{
|
||||
var d = document.createElement('div');
|
||||
d.className = 'hintEmptyText';
|
||||
d.innerHTML = self.emptyText;
|
||||
self.hintLayer.appendChild(d);
|
||||
}
|
||||
else
|
||||
self.disable();
|
||||
return;
|
||||
}
|
||||
self.enable();
|
||||
for (var i in items)
|
||||
self.hintLayer.appendChild(self.makeItem(items[i][0], items[i][1]));
|
||||
if (self.maxHeight)
|
||||
{
|
||||
self.hintLayer.style.height =
|
||||
(self.hintLayer.scrollHeight > self.maxHeight
|
||||
? self.maxHeight : self.hintLayer.scrollHeight) + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
// Create a drop-down list item
|
||||
self.makeItem = function(name, value)
|
||||
{
|
||||
var d = document.createElement('div');
|
||||
d.id = self.id+'_item_'+self.items.length;
|
||||
d.className = 'hintItem';
|
||||
d.title = value;
|
||||
d.appendChild(document.createTextNode(name));
|
||||
addListener(d, 'mouseover', self.onItemMouseOver);
|
||||
addListener(d, 'mousedown', self.onItemClick);
|
||||
self.items.push([name, value]);
|
||||
return d;
|
||||
};
|
||||
|
||||
// Handle item mouse over
|
||||
self.onItemMouseOver = function()
|
||||
{
|
||||
return self.highlightItem(this);
|
||||
};
|
||||
|
||||
// Handle item clicks
|
||||
self.onItemClick = function()
|
||||
{
|
||||
self.selectItem(parseInt(this.id.substr(self.id.length+6)));
|
||||
return true;
|
||||
};
|
||||
|
||||
// Move highlight forward or back by 'by' items (integer)
|
||||
self.moveHighlight = function(by)
|
||||
{
|
||||
var n = self.selectedIndex+by;
|
||||
if (n < 0)
|
||||
n = 0;
|
||||
var elem = document.getElementById(self.id+'_item_'+n);
|
||||
if (!elem)
|
||||
return true;
|
||||
return self.highlightItem(elem);
|
||||
};
|
||||
|
||||
// Make item 'elem' active (highlighted)
|
||||
self.highlightItem = function(elem)
|
||||
{
|
||||
if (self.selectedIndex >= 0)
|
||||
{
|
||||
var c = self.getItem();
|
||||
if (c)
|
||||
c.className = 'hintItem';
|
||||
}
|
||||
self.selectedIndex = parseInt(elem.id.substr(self.id.length+6));
|
||||
elem.className = 'hintActiveItem';
|
||||
return false;
|
||||
};
|
||||
|
||||
// Get index'th item, or current when index is null
|
||||
self.getItem = function(index)
|
||||
{
|
||||
if (index == null)
|
||||
index = self.selectedIndex;
|
||||
if (index < 0)
|
||||
return null;
|
||||
return document.getElementById(self.id+'_item_'+self.selectedIndex);
|
||||
};
|
||||
|
||||
// Select index'th item - change the input value and hide the hint
|
||||
self.selectItem = function(index)
|
||||
{
|
||||
self.input.value = self.items[index][1];
|
||||
self.hide();
|
||||
if (self.onChangeListener)
|
||||
self.onChangeListener(self, index);
|
||||
};
|
||||
|
||||
// Handle user input, load new items
|
||||
self.onChange = function()
|
||||
{
|
||||
var v = self.input.value.trim();
|
||||
if (v != self.curValue)
|
||||
{
|
||||
self.curValue = v;
|
||||
self.dataLoader(self, v);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Handle Enter key presses, cancel handling of arrow keys
|
||||
self.onKeyUp = function(ev)
|
||||
{
|
||||
ev = ev||window.event;
|
||||
if (ev.keyCode != 10 && ev.keyCode != 13)
|
||||
self.show();
|
||||
if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 10 || ev.keyCode == 13)
|
||||
return stopEvent(ev, true, true);
|
||||
self.onChange();
|
||||
return true;
|
||||
};
|
||||
|
||||
// Cancel handling of Enter key
|
||||
self.onKeyDown = function(ev)
|
||||
{
|
||||
ev = ev||window.event;
|
||||
if (ev.keyCode == 10 || ev.keyCode == 13)
|
||||
return stopEvent(ev, true, true);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Handle arrow keys and Enter
|
||||
self.onKeyPress = function(ev)
|
||||
{
|
||||
ev = ev||window.event;
|
||||
if (ev.keyCode == 38) // up
|
||||
self.moveHighlight(-1);
|
||||
else if (ev.keyCode == 40) // down
|
||||
self.moveHighlight(1);
|
||||
else if (ev.keyCode == 10 || ev.keyCode == 13) // enter
|
||||
{
|
||||
if (self.selectedIndex >= 0)
|
||||
self.selectItem(self.selectedIndex);
|
||||
return stopEvent(ev, true, true);
|
||||
}
|
||||
else
|
||||
return true;
|
||||
// scrolling
|
||||
if (self.selectedIndex >= 0)
|
||||
{
|
||||
var c = self.getItem();
|
||||
var t = self.hintLayer;
|
||||
var ct = getOffset(c).top + t.scrollTop - t.style.top.substr(0, t.style.top.length-2);
|
||||
var ch = c.scrollHeight;
|
||||
if (ct+ch-t.offsetHeight > t.scrollTop)
|
||||
t.scrollTop = ct+ch-t.offsetHeight;
|
||||
else if (ct < t.scrollTop)
|
||||
t.scrollTop = ct;
|
||||
}
|
||||
return stopEvent(ev, true, true);
|
||||
};
|
||||
|
||||
// Called when input receives focus
|
||||
self.onInputFocus = function()
|
||||
{
|
||||
self.show();
|
||||
return true;
|
||||
};
|
||||
|
||||
// Called when input loses focus
|
||||
self.onInputBlur = function()
|
||||
{
|
||||
self.hide();
|
||||
return true;
|
||||
};
|
||||
|
||||
// Hide hinter
|
||||
self.hide = function()
|
||||
{
|
||||
if (!self.skipHideCounter)
|
||||
self.hintLayer.style.display = 'none';
|
||||
else
|
||||
self.skipHideCounter = 0;
|
||||
};
|
||||
|
||||
// Show hinter
|
||||
self.show = function()
|
||||
{
|
||||
if (!self.disabled)
|
||||
self.hintLayer.style.display = '';
|
||||
};
|
||||
|
||||
// Disable hinter, for the case when there is no items and no empty text
|
||||
self.disable = function()
|
||||
{
|
||||
self.disabled = true;
|
||||
self.hide();
|
||||
};
|
||||
|
||||
// Enable hinter
|
||||
self.enable = function()
|
||||
{
|
||||
var show = self.disabled;
|
||||
self.disabled = false;
|
||||
if (show)
|
||||
self.show();
|
||||
}
|
||||
|
||||
// *** Call initialise ***
|
||||
self.init();
|
||||
};
|
||||
|
||||
// Global variable
|
||||
SimpleAutocomplete.SimpleAutocompletes = [];
|
||||
|
||||
// Global mousedown handler, hides dropdowns when clicked outside
|
||||
SimpleAutocomplete.GlobalMouseDown = function(ev)
|
||||
{
|
||||
var target = ev.target || ev.srcElement;
|
||||
var esh;
|
||||
while (target)
|
||||
{
|
||||
esh = target.SimpleAutocomplete_input;
|
||||
if (esh)
|
||||
break;
|
||||
else if (target.SimpleAutocomplete_layer)
|
||||
{
|
||||
target.SimpleAutocomplete_layer.skipHideCounter++;
|
||||
return true;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
for (var i in SimpleAutocomplete.SimpleAutocompletes)
|
||||
if (SimpleAutocomplete.SimpleAutocompletes[i] != esh)
|
||||
SimpleAutocomplete.SimpleAutocompletes[i].hide();
|
||||
return true;
|
||||
};
|
||||
|
||||
// Cross-browser adding of event listeners (remove if you already have it)
|
||||
var addListener = function()
|
||||
{
|
||||
if (window.addEventListener)
|
||||
{
|
||||
return function(el, type, fn) { el.addEventListener(type, fn, false); };
|
||||
}
|
||||
else if (window.attachEvent)
|
||||
{
|
||||
return function(el, type, fn) {
|
||||
var f = function() { return fn.call(el, window.event); };
|
||||
el.attachEvent('on'+type, f);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return function(el, type, fn) { element['on'+type] = fn; }
|
||||
}
|
||||
}();
|
||||
|
||||
// Cancel event bubbling and/or default action
|
||||
var stopEvent = function(ev, cancelBubble, preventDefault)
|
||||
{
|
||||
if (cancelBubble)
|
||||
{
|
||||
if (ev.stopPropagation)
|
||||
ev.stopPropagation();
|
||||
else
|
||||
ev.cancelBubble = true;
|
||||
}
|
||||
if (preventDefault && ev.preventDefault)
|
||||
ev.preventDefault();
|
||||
ev.returnValue = !preventDefault;
|
||||
return !preventDefault;
|
||||
};
|
||||
|
||||
// Set global mousedown listener
|
||||
addListener(window, 'load', function() { addListener(document, 'mousedown', SimpleAutocomplete.GlobalMouseDown) });
|
Loading…
Reference in New Issue