📄 autosuggest.js
字号:
/**
* $Id: autosuggest.js 205 2007-02-21 18:36:41Z wingedfox $
* $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.008/dom/autosuggest.js $
*
* Extends the selectbox interface
*
* @author Ilya Lebedev <ilya@lebedev.net>
* @modified $Date: 2007-02-21 21:36:41 +0300 (Срд, 21 Фев 2007) $
* @version $Rev: 205 $
* @title Selectbox
* @license LGPL 2.1 or later
*/
/**
* Autosuggestion control
*
* @constructor
* @param {Object} target
*/
var Autosuggest = function (target) {
this.$VERSION$ = " $Rev: 205 $ ".replace(/\D/g,"");
var self = this;
/**
* CSS classes
*/
var cssClasses = {
'root' : 'Autosuggest'
}
/**
* Stores selectbox with suggestions
*
* @type HTMLElement
* @scope private
*/
var node = null;
/**
* Selectbox controller
*
* @type Selectbox
* @scope private
*/
var controller = null;
/**
* Config settings
*
* @type Object
* @access private
*/
var options = {
'minlength' : 2 // minimum filter length to show the box
,'delay' : 50 // delay before displaying the box
,'size' : 15 // max select size
,'list' : [] // array of suggestions
,'place' : 0 // 1: top side
// 0: bottom side
,'match' : 'start'// how to filter user input
}
/**
* Stores the open interval ID to clear it, if needed
*
* @type Number
* @scope private
*/
var interval = null;
//-------------------------------------------------------------------------
// PUBLICS
//-----------------------------------------------------
// Setters
//---------------------------------
/**
* Defines the draw place
*
* @param {String} dir 'top' or 'bottom'
* @return {Boolean}
* @scope public
*/
this.setPlace = function (dir) {
if (!isString(dir)) return false;
switch (dir.toLowerCase()) {
case 'top' :
options.place = 1;
break;
case 'bottom' :
options.place = 0;
break;
default :
return false;
}
return true;
}
/**
* Set the minimum number of the letters to show the box
*
* @return {Number}
* @scope public
*/
this.setMinLength = function (d /* :Number */) /* Boolean */ {
if (!isNumeric(parseInt(d))) return false;
options.minlength = d;
return true;
}
/**
* Set the delay to show the box
*
* @param {Number, String} d delay
* @return {Boolean}
* @scope public
*/
this.setDelay = function (d /* :Number */) /* Boolean */ {
if (!isNumeric(parseInt(d))) return false;
options.delay = d;
return true;
}
/**
* Set suggestions
*
* @param {Array} list of the suggestions
* @return {Boolean}
* @scope public
*/
this.setSuggestions = function (list /* :Array */) /* :Boolean */ {
if (!isArray(list)) return false;
options.list = list;
options.rebuildList = true;
}
/**
* Adds the filter to the suggestions list
*
* @param {String} filter string to filter suggestions
* @return {Boolean}
* @scope public
*/
this.setFilter = function (filter /* :String */) /* :Boolean */ {
if (!controller) return false;
return controller.showOnlyMatchingOptions(filter, options.match);
}
/**
* Adds the filter to the suggestions list
*
* @see Selectbox#showOnlyMatchingOptions
* @param {String} match option to be sent to the selectbox controller
* @scope public
*/
this.setFilterMatch = function (match /* :String */) {
options.match = match;
}
//---------------------------------
// Getters
//---------------------------------
/**
* Retrieves the delay to show the box
*
* @return {Number}
* @scope public
*/
this.getDelay = function () /* Boolean */ {
return options.delay;
}
/**
* Retrieves the minimum number of the letters to show the box
*
* @return {Number}
* @scope public
*/
this.getMinLength = function () /* Boolean */ {
return options.minlength;
}
//---------------------------------
// Misc functions
//---------------------------------
/**
* Shows the suggestions box
*
* @return {Boolean}
* @scope public
*/
this.show = function () /* :Boolean */ {
if (!node || self.isVisible()) return false;
if (!node.parentNode || node.parentNode.nodeType==11) {
document.body.appendChild(node);
/*
* where to place the box
*/
var xy = DOM.getOffset(target);
node.style.left = xy.x+'px';
node.style.top = xy.y+target.offsetHeight+'px';
/*
* attach selectbox controller
*/
controller = new Selectbox(node.firstChild.id);
}
/*
* do things, only if controller is available
*/
if (controller) {
if (options.rebuildList) {
controller.addOptionsList(options.list);
options.rebuildList = false;
}
controller.showOnlyMatchingOptions(target.value, options.match);
var len = controller.getOptionsLength();
if (len == 0) return false;
else node.firstChild.size = options.size;
} else {
return false;
}
setTimeout(doShow, options.delay);
return true;
}
/**
* Hides the suggestions box
*
* @return {Boolean}
* @scope public
*/
this.hide = function (e) /* :Boolean */{
if (!node || self.isHidden()) return false;
target.focus();
interval = setTimeout(function() {node.style.display = 'none'; interval=null}, options.delay);
return true;
}
/**
* Tells, if suggest box is visible
*
* @return {Booolean}
* @scope public
*/
this.isVisible = function () /* :Boolean */ {
return !node.style.display;
}
/**
* Tells, if suggest box is hidden
*
* @return {Booolean}
* @scope public
*/
this.isHidden = function () /* :Boolean */ {
return !self.isVisible();
}
//-------------------------------------------------------------------------
// PRIVATES
//-----------------------------------------------------
// Misc functions
//---------------------------------
/**
* Actual method, showing the box
*
* @scope private
*/
var doShow = function () {
node.style.visibility = 'hidden';
node.style.display = '';
var tsp = DOM.getOffset(target)['y']-DOM.getBodyScrollTop()
,ht = DOM.getClientHeight()
,bsp = ht - (DOM.getOffset(target)['y'] + target.offsetHeight-DOM.getBodyScrollTop())
,ct = document.getElementById(controller.getId())
,ttop = DOM.getOffset(target)['y']
,tbot = ttop + target.offsetHeight+'px'
if (tsp>ct.parentNode.offsetHeight && options.place) {
/*
* default place - top and it's enough space there
*/
ct.parentNode.style.top = tsp-ct.parentNode.offsetHeight+DOM.getBodyScrollTop()+'px';
} else if (bsp>ct.parentNode.offsetHeight && !options.place) {
/*
* default place - bottom and it's enough space there
*/
ct.parentNode.style.top = DOM.getOffset(target)['y'] + target.offsetHeight+'px';
} else {
/*
* calculate the needed height
*/
while (ct.size>2 && bsp<ct.parentNode.offsetHeight && tsp<ct.parentNode.offsetHeight) {
ct.size--;
}
if (bsp>=ct.parentNode.offsetHeight || bsp>=tsp) {
ct.parentNode.style.top = DOM.getOffset(target)['y'] + target.offsetHeight+'px';
} else if (tsp>=ct.parentNode.offsetHeight) {
ct.parentNode.style.top = tsp-ct.parentNode.offsetHeight+DOM.getBodyScrollTop()+'px';
}
}
node.style.visibility = '';
if (controller.getSelectedIndex()<0) controller.selectOption(0);
interval=1;
}
//---------------------------------
// Event handlers
//---------------------------------
/**
* Keypress event handler, used to track the input
*
* @param {EventTarget} e
* @scope protected
*/
var keypress = function (e) {
el = e.target||e.srcElement;
switch (e.keyCode) {
case 33: //page up
case 34: //page down
case 36: //home
case 35: //end
case 38: //up arrow
case 40: //down arrow
self.show();
case 27: // escape
if (self.isVisible() && !e.shiftKey) __preventDefault(e);
break;
case 9:
case 13: // enter
pasteSuggestion(e);
break;
case 37: // left arrow
case 39: // right arrow
break;
default:
if (target.value.length >= options.minlength) self.show()
if (controller) {
controller.showOnlyMatchingOptions(target.value, options.match);
if (controller.getSelectedIndex()<0) controller.selectOption(0);
}
break;
}
}
var pasteSuggestion = function (e) {
var el = e.target||e.srcElement;
/*
* since we use this handler for both click and keypress events, emulate 'Enter' press
*/
if ('click' == e.type) e.keyCode = 13;
switch (e.keyCode) {
case 9: // tab
case 13: // enter
if (self.hide()) {
target.value = node.firstChild.value;
return __preventDefault(e);
}
default:
}
target.focus();
}
/**
* Prevents some keys to generate default actions
*
* @param {EventTarget} e
* @scope protected
*/
var __preventKeyPress = function (e) {
switch (e.keyCode) {
case 27: //esc
case 33: //page up
case 34: //page down
case 36: //home
case 35: //end
case 27 : // escape
if (self.isHidden()) return;
case 38: //up arrow
case 40: //down arrow
case 37: //left arrow
case 39: //right arrow
if (!e.shiftKey) return;
// case 9: //tab
case 13: //enter
return __preventDefault(e);
}
}
/**
* Prevents default action for the target
*
* @param {EventTarget} el
* @scope private
*/
var __preventDefault = function (e) {
e.returnValue = false;
if (e.preventDefault) e.preventDefault();
return false;
}
/**
* constructor
*/
var __construct = function () {
node = document.createElementExt('div', {'class' : cssClasses.root});
node.innerHTML = '<select id="Autosuggest'+(new Date).valueOf()+'" size="10" autocomplete="off"></select>';
if (!node) return false;
node.style.display = 'none';
/*
* init target
*/
if (isString(target)) target = document.getElementById(target);
target.attachEvent('onkeyup',keypress);
target.attachEvent('onkeypress',__preventKeyPress);
target.attachEvent('onkeydown',function (e) {
switch (e.keyCode) {
case 9:
pasteSuggestion(e);
break;
case 27:
if (self.hide()) {
return __preventDefault(e);
}
break;
case 32:
if (!e.ctrlKey) return;
case 38: // key_up
case 40: // key_down
case 33: //page up
case 34: //page down
if (self.isHidden()) {
self.show();
} else {
if (38 == e.keyCode) controller.selectPrev(true);
else if (40 == e.keyCode) controller.selectNext(true);
else if (33 == e.keyCode) controller.selectOption(controller.getSelectedIndex()-node.firstChild.size, true);
else if (34 == e.keyCode) controller.selectOption(controller.getSelectedIndex()-(-node.firstChild.size), true);
}
__preventDefault(e);
break;
case 36: //home
case 35: //end
if (self.isVisible()) {
if (36 == e.keyCode) controller.selectOption(0,true);
else if (35 == e.keyCode) controller.selectOption(controller.getOptionsLength(), true);
__preventDefault(e);
}
break;
}
});
/*
* keep things persistent, while clicking between the boxes
*/
target.attachEvent('onmousedown', function () {interval = 1;});
target.attachEvent('onblur', function(){interval==1?self.hide():interval=2});
node.firstChild.attachEvent('onmousedown', function () {interval = 2;});
node.firstChild.attachEvent('onkeydown', function () {interval = 1;});
node.firstChild.attachEvent('onblur', function(){interval==2?self.hide():interval=1});
/*
* turn off damn autocomplete
*/
target.autocomplete = 'off';
target.setAttribute('autocomplete','off');
node.firstChild.attachEvent('onclick', pasteSuggestion);
node.firstChild.attachEvent('onkeydown', pasteSuggestion);
}
return __construct();
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -