⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 autosuggest.js

📁 Javascript code for developing the virtual keyboard
💻 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 + -