Multiple selection support, add utility functions from getOffset.js
parent
78a2118554
commit
62f3b28e15
|
@ -15,6 +15,12 @@
|
|||
color: black;
|
||||
cursor: pointer;
|
||||
padding: 1px 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.hintItem input {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.hintActiveItem {
|
||||
color: white;
|
||||
|
|
185
hinter.js
185
hinter.js
|
@ -1,21 +1,36 @@
|
|||
/* 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
|
||||
/* Simple autocomplete for text inputs, with the support for multiple selection.
|
||||
Homepage: http://yourcmc.ru/wiki/SHint_JS
|
||||
(c) Vitaliy Filippov 2011
|
||||
Usage:
|
||||
Include hinter.css, hinter.js on your page. Then write:
|
||||
var hint = new SimpleAutocomplete(input, dataLoader, multipleDelimiter, onChangeListener, maxHeight, emptyText);
|
||||
Parameters:
|
||||
input
|
||||
The input, either id or DOM element reference.
|
||||
dataLoader(hint, value)
|
||||
Callback which should load autocomplete options and then call
|
||||
hint.replaceItems([ [ name, value ], [ name, value ], ... ])
|
||||
'hint' parameter will be this autocompleter object, and the guess
|
||||
should be done based on 'value' parameter (string).
|
||||
Optional parameters:
|
||||
multipleDelimiter
|
||||
Pass a delimiter string (for example ',' or ';') to enable multiple selection.
|
||||
Item values cannot have leading or trailing whitespace. Input value will consist
|
||||
of selected item values separated by this delimiter plus single space.
|
||||
dataLoader should handle it's 'value' parameter accordingly in this case,
|
||||
because it will be just the raw value of the input, probably with incomplete
|
||||
item or items, typed by the user.
|
||||
onChangeListener
|
||||
Callback which is called when input value is changed using this dropdown.
|
||||
It must be used instead of normal 'onchange' event.
|
||||
maxHeight
|
||||
Maximum hint dropdown height in pixels
|
||||
emptyText
|
||||
Text to show when dataLoader returns no options.
|
||||
If emptyText === false, the hint will be hidden instead of showing text.
|
||||
*/
|
||||
|
||||
var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight, emptyText)
|
||||
var SimpleAutocomplete = function(input, dataLoader, multipleDelimiter, onChangeListener, maxHeight, emptyText)
|
||||
{
|
||||
if (typeof(input) == 'string')
|
||||
input = document.getElementById(input);
|
||||
|
@ -25,6 +40,7 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
// Parameters
|
||||
var self = this;
|
||||
self.input = input;
|
||||
self.multipleDelimiter = multipleDelimiter;
|
||||
self.dataLoader = dataLoader;
|
||||
self.onChangeListener = onChangeListener;
|
||||
self.maxHeight = maxHeight;
|
||||
|
@ -37,8 +53,8 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
self.id = input.id;
|
||||
self.disabled = false;
|
||||
|
||||
// Initialise hinter
|
||||
self.init = function()
|
||||
// Initialiser
|
||||
var init = function()
|
||||
{
|
||||
var e = self.input;
|
||||
var p = getOffset(e);
|
||||
|
@ -102,8 +118,15 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
return;
|
||||
}
|
||||
self.enable();
|
||||
var h = {};
|
||||
if (self.multipleDelimiter)
|
||||
{
|
||||
var old = self.input.value.split(self.multipleDelimiter);
|
||||
for (var i = 0; i < old.length; i++)
|
||||
h[old[i].trim()] = true;
|
||||
}
|
||||
for (var i in items)
|
||||
self.hintLayer.appendChild(self.makeItem(items[i][0], items[i][1]));
|
||||
self.hintLayer.appendChild(self.makeItem(items[i][0], items[i][1], h[items[i][1]]));
|
||||
if (self.maxHeight)
|
||||
{
|
||||
self.hintLayer.style.height =
|
||||
|
@ -112,20 +135,37 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
}
|
||||
};
|
||||
|
||||
// Create a drop-down list item
|
||||
self.makeItem = function(name, value)
|
||||
// Create a drop-down list item, include checkbox if self.multipleDelimiter is true
|
||||
self.makeItem = function(name, value, checked)
|
||||
{
|
||||
var d = document.createElement('div');
|
||||
d.id = self.id+'_item_'+self.items.length;
|
||||
d.className = 'hintItem';
|
||||
d.title = value;
|
||||
if (self.multipleDelimiter)
|
||||
{
|
||||
var c = document.createElement('input');
|
||||
c.type = 'checkbox';
|
||||
c.id = self.id+'_check_'+self.items.length;
|
||||
c.checked = checked && true;
|
||||
c.value = value;
|
||||
d.appendChild(c);
|
||||
addListener(c, 'click', self.preventCheck);
|
||||
}
|
||||
d.appendChild(document.createTextNode(name));
|
||||
addListener(d, 'mouseover', self.onItemMouseOver);
|
||||
addListener(d, 'mousedown', self.onItemClick);
|
||||
self.items.push([name, value]);
|
||||
self.items.push([name, value, checked]);
|
||||
return d;
|
||||
};
|
||||
|
||||
// Prevent default action on checkbox
|
||||
self.preventCheck = function(ev)
|
||||
{
|
||||
ev = ev||window.event;
|
||||
return stopEvent(ev, false, true);
|
||||
};
|
||||
|
||||
// Handle item mouse over
|
||||
self.onItemMouseOver = function()
|
||||
{
|
||||
|
@ -133,7 +173,7 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
};
|
||||
|
||||
// Handle item clicks
|
||||
self.onItemClick = function()
|
||||
self.onItemClick = function(ev)
|
||||
{
|
||||
self.selectItem(parseInt(this.id.substr(self.id.length+6)));
|
||||
return true;
|
||||
|
@ -175,11 +215,48 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
return document.getElementById(self.id+'_item_'+self.selectedIndex);
|
||||
};
|
||||
|
||||
// Select index'th item - change the input value and hide the hint
|
||||
// Select index'th item - change the input value and hide the hint if not a multi-select
|
||||
self.selectItem = function(index)
|
||||
{
|
||||
self.input.value = self.items[index][1];
|
||||
self.hide();
|
||||
if (!self.multipleDelimiter)
|
||||
{
|
||||
self.input.value = self.items[index][1];
|
||||
self.hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById(self.id+'_check_'+index).checked = self.items[index][2] = !self.items[index][2];
|
||||
var old = self.input.value.split(self.multipleDelimiter);
|
||||
for (var i = 0; i < old.length; i++)
|
||||
old[i] = old[i].trim();
|
||||
if (!self.items[index][2])
|
||||
{
|
||||
for (var i = old.length-1; i >= 0; i--)
|
||||
if (old[i] == self.items[index][1])
|
||||
old.splice(i, 1);
|
||||
self.input.value = old.join(self.multipleDelimiter+' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
var h = {};
|
||||
for (var i = 0; i < self.items.length; i++)
|
||||
if (self.items[i][2])
|
||||
h[self.items[i][1]] = true;
|
||||
var nl = [];
|
||||
for (var i = 0; i < old.length; i++)
|
||||
{
|
||||
if (h[old[i]])
|
||||
{
|
||||
delete h[old[i]];
|
||||
nl.push(old[i]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < self.items.length; i++)
|
||||
if (self.items[i][2] && h[self.items[i][1]])
|
||||
nl.push(self.items[i][1]);
|
||||
self.input.value = nl.join(self.multipleDelimiter+' ');
|
||||
}
|
||||
}
|
||||
if (self.onChangeListener)
|
||||
self.onChangeListener(self, index);
|
||||
};
|
||||
|
@ -252,6 +329,7 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
self.onInputFocus = function()
|
||||
{
|
||||
self.show();
|
||||
self.hasFocus = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -259,6 +337,7 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
self.onInputBlur = function()
|
||||
{
|
||||
self.hide();
|
||||
self.hasFocus = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -295,7 +374,7 @@ var SimpleAutocomplete = function(input, dataLoader, onChangeListener, maxHeight
|
|||
}
|
||||
|
||||
// *** Call initialise ***
|
||||
self.init();
|
||||
init();
|
||||
};
|
||||
|
||||
// Global variable
|
||||
|
@ -313,7 +392,8 @@ SimpleAutocomplete.GlobalMouseDown = function(ev)
|
|||
break;
|
||||
else if (target.SimpleAutocomplete_layer)
|
||||
{
|
||||
target.SimpleAutocomplete_layer.skipHideCounter++;
|
||||
if (target.SimpleAutocomplete_layer.hasFocus)
|
||||
target.SimpleAutocomplete_layer.skipHideCounter++;
|
||||
return true;
|
||||
}
|
||||
target = target.parentNode;
|
||||
|
@ -324,7 +404,10 @@ SimpleAutocomplete.GlobalMouseDown = function(ev)
|
|||
return true;
|
||||
};
|
||||
|
||||
// Cross-browser adding of event listeners (remove if you already have it)
|
||||
//// UTILITY FUNCTIONS ////
|
||||
// You can delete this section if you already have them somewhere in your scripts //
|
||||
|
||||
// Cross-browser adding of event listeners
|
||||
var addListener = function()
|
||||
{
|
||||
if (window.addEventListener)
|
||||
|
@ -360,5 +443,53 @@ var stopEvent = function(ev, cancelBubble, preventDefault)
|
|||
return !preventDefault;
|
||||
};
|
||||
|
||||
// Remove leading and trailing whitespace
|
||||
String.prototype.trim = function()
|
||||
{
|
||||
return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
||||
};
|
||||
|
||||
// Get element position, relative to the top-left corner of page
|
||||
var getOffset = function(elem)
|
||||
{
|
||||
if (elem.getBoundingClientRect)
|
||||
return getOffsetRect(elem);
|
||||
else
|
||||
return getOffsetSum(elem);
|
||||
};
|
||||
|
||||
// Get element position using getBoundingClientRect()
|
||||
var getOffsetRect = function(elem)
|
||||
{
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docElem = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
};
|
||||
|
||||
// Get element position using sum of offsetTop/offsetLeft
|
||||
var getOffsetSum = function(elem)
|
||||
{
|
||||
var top = 0, left = 0;
|
||||
while(elem)
|
||||
{
|
||||
top = top + parseInt(elem.offsetTop);
|
||||
left = left + parseInt(elem.offsetLeft);
|
||||
elem = elem.offsetParent;
|
||||
}
|
||||
return { top: top, left: left };
|
||||
};
|
||||
|
||||
//// END UTILITY FUNCTIONS ////
|
||||
|
||||
// Set global mousedown listener
|
||||
addListener(window, 'load', function() { addListener(document, 'mousedown', SimpleAutocomplete.GlobalMouseDown) });
|
||||
|
|
Loading…
Reference in New Issue