/* * jQuery tui tuihotkey plugin 0.3 * * Copyright (c) 2010 china * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * * Create: 2010-10-21 11:33:15 yewf $ * Revision: $Id: tui.hotkey.js 2010-02-18 17:44:23 yewf $ * * Notes:1、IE环境下alt键会聚焦菜单,因此尽量不要使用alt键,目前可选ctrl,shift,alt,但由于键盘布局,目前使用了alt键,在IE环境下需要按两次alt键; * 2、html tag标签id必须区分大小写; * 3、由于使用了document.click与keydown事件,因此存件事件冒泡问题; * 4、多键请使用半角逗号分隔; * 5、只能支持两个按键; * 6、由于$("#obj").height()的值,同一个UL下的li都会有不一样的情况, * 因此无法准确设置top值,解决方法是给每个key增加一个top属性,用过手动定义top值。默认使用keyStyle.top值。 * keyTop = $("#obj").offset().top + key.top; * 7、最后一个按键必须定义event,否则会提示未定义event; * 8、不能使用一个JS进行全局定义,必须分页面定义。原因如下: * (1)跳转冲突,例如:一级菜单,公司管理,系统管理需要互相跳转,就需要定义跳转事件, * 如果当前停在公司管理,而又执行了事件跳转,在显示二级菜单热键时,就会降低用户体验。 * (2)使用全局定义,如果定义有几百行,会导致每个页面下载的数据过大; * (3)对于某个页面来说,全局定义有跟本页面无关的定义; * Sample: * $(function() { * $.tuihotkey({ * "primaryId": 0, * "keyStyle": { * "top": 20, * "align": "center" * }, * "keys": [ * { "key": "a", "id": 1, "pid": 0, "objId": "menu0", "event": function() { goMyoffice(); } }, * { "key": "b,o", "id": 2, "pid": 0, "objId": "menu1", "event": function() { goOrder(); } }, * { "key": "c", "id": 3, "pid": 0, "objId": "menu2", "event": function() { goGoods(); } }, * { "key": "d", "id": 7, "pid": 0, "objId": "menu7" }, * { "key": "g", "id": 700, "pid": 7, "objId": "Company0" }, * { "key": "j", "id": 701, "pid": 7, "objId": "Company1" }, * { "key": "c", "id": 702, "pid": 7, "objId": "Company2" }, * { "key": "k", "id": 703, "pid": 7, "objId": "Company3" }, * { "key": "a", "id": 7001, "pid": 700, "objId": "liTest1", top: 70, "event": function() { org_Add(); } }, * { "key": "b", "id": 7002, "pid": 700, "objId": "liTest2", top: 10, "event": function() { testTT2(); } }, * { "key": "c", "id": 7003, "pid": 700, "objId": "liTest3", top: 10, "event": function() { testTT3(); } } * ] * }); * }); * * */ jQuery.tuihotkey = function(options) { var defaults = { "primaryKey": "alt", "primaryId": 0, "keyStyle": { "top": 20, "align": "center" }, "focusId": null, "keys": null }; var options = jQuery.extend({}, defaults, options); var HOTKEY_PRESS = false; // 如果是hotkey跳转,则为true,否则为false。 var HOTKEY_WIDTH = 20; var HOTKEY_HEIGHT = 20; var KEY = { ALT: 18, CTRL: 17, SHIFT: 16, ESC: 27, C: 67, V: 86 }; // 存储在Cookie中的按键,格式: { "key": "17", "id": 0, "pid": 0 } 或者 { "key": "65,66", "id": 1, "pid": 0} var _objDIV = new Array(); // 存储显示层的ID号 var _objFirstKey = new Array(); //第一次按键 // 热键进行页面跳转时,热键必须保持状态 init(); jQuery(document).keydown(function(e) { var event = window.event || e; var pressKey = event.keyCode; if (KEY.ESC === pressKey) { clearHotkey(true); } else if ((KEY.SHIFT === pressKey && options.primaryKey.toUpperCase() === "SHIFT") || (KEY.CTRL === pressKey && options.primaryKey.toUpperCase() === "CTRL") || (KEY.ALT === pressKey && options.primaryKey.toUpperCase() === "ALT")) { // 只要使用了快捷键,就将当前焦点从当前控件移开(比如当前是textbox时,保证输入的热键不会在textbox中出现) //alert("您正在使用快捷键提示功能,请参照提示使用"); if ($.browser.msie) jQuery(document.body).focus(); else { // 其它浏览器的情况下,无法使用document.body.focus() // 先用尝试使用配置的id号,然后再使用div,再尝试使用table的focus() var d; if (options.focusId != null) { d = jQuery("#" + options.focusId); } else { d = $("div:first"); if (d.length == 0) { d = jQuery("table:frist"); } } d.attr("tabindex", "9999").css("outline", "none"); d.focus(); } firstKeyPress(pressKey); } else { processKeyPress(pressKey); ; } }) .keyup(function(e) { // 这里对firefox有效,对ie无效。 _stopBubble(e); }) .click(function() { clearHotkey(true); }); // 页面离开时 jQuery(window).unload(function() { // 如果不是用热键离开的页面,则清除cookie。 if (HOTKEY_PRESS === false) { deleteCookie(); } }); // 页面载入时,承接上一页的按键提示 function init() { // 最后一次按钮 processKeyPress(); }; // 第一次按键操作,功能键 function firstKeyPress(keycode) { clearHotkey(true); _objFirstKey = new Array(); // 处理keys var objCurrent = keysByPID(options.primaryId); if (objCurrent.length === 0) return; jQuery.each(objCurrent, function(i, n) { displayHotkey(n); }); // 做了热键提示,则保存alt键 if (objCurrent.length > 0) { // 保存Cookie var t = "{ /"key/": /"" + keycode + "/", /"id/": 0, /"pid/": 0 }"; setCookie(t); } }; // 处理其它按键操作,字母键 function processKeyPress(keycode) { // 如果是功能键,则不处理 if (keycode == KEY.CTRL || keycode == KEY.ALT || keycode == KEY.SHIFT) { return; } // 最后一次按键 var lastKey = getCookieObject(); if (lastKey === null) { return; } var lastKeyCode, lastKeyId, hotkeyPageRedirect = true; //该参数在使用热键跳转时,不执行热键事件。 if (typeof keycode === "undefined") { // 页面跳转后的初始化热键显示 lastKeyCode = lastKey.key; lastKeyId = lastKey.pid; // 这里使用pid,才能找到上一次的按钮定义 hotkeyPageRedirect = false; } else { // 当前页 lastKeyCode = keycode; lastKeyId = lastKey.id; // 没有跳转,使用上次按键的id查找下一级按键定义 // 第一次按错误的,第二次按正确的。必须再次判断 var fk = findKey(String.fromCharCode(lastKeyCode), lastKeyId); if (fk === "none") { if (_objFirstKey.length > 0) { lastKeyCode = _objFirstKey.pop() + "," + lastKeyCode; } } } // 将逗号分隔的按钮进行字符转换,如68,67转换为D,C var keyary = lastKeyCode.toString().split(","); var keych = ""; for (var j = 0; j < keyary.length; j++) { if (keych === "") { keych = String.fromCharCode(keyary[j]); } else { keych = keych + "," + String.fromCharCode(keyary[j]); } } // 查找当前按键是否定义 var curkey = findKey(keych, lastKeyId); if (curkey === "none") { // 支持两个按钮,第一个按键找不到,则有可能是两个按键定义。 _objFirstKey.push(keycode); return; } else if (curkey === "repeat") { alert("Error:/nCan not repeat definition of hotkeys at the same level."); return; } // 如果当前key的父对象为空或隐藏,执行到对应的热键时,不执行事件。 var objTarget = jQuery("#" + curkey.objId); if (objTarget.length === 0 || objTarget.is(":hidden")) { return; } var t = "{ /"key/": /"" + lastKeyCode + "/", /"id/": " + curkey.id + ", /"pid/": " + curkey.pid + " }"; // 返回符合pid条件的数组 var objCurrent = keysByPID(curkey.id); if (objCurrent.length === 0) { // 如果没有子节点,则必须有事件执行,否则按键就没有意义 if (typeof curkey.event === "undefined" || curkey.event === null) { alert("Error:/nLast key must be define [event] argument,please check [/"key/": /"" + curkey.key + "/"],[ /"id/": " + curkey.id + "]。"); return; } // 保存最后一次按键 setCookie(t); if (hotkeyPageRedirect) { // 清除提示层,并执行事件 clearHotkey(false); HOTKEY_PRESS = true; _objFirstKey = new Array(); curkey.event(); } return; } // 移除上一次的按键提示层 clearHotkey(false); // 保存最后一次按键 setCookie(t); // 执行事件 curkey.event && curkey.event(); // 显示按键 jQuery.each(objCurrent, function(i, n) { displayHotkey(n); }); // 清除储存多键的数组。 _objFirstKey = new Array(); }; // 显示热键提示层(div) function displayHotkey(term) { var keyName = term.key, objId = term.objId; var objTag = jQuery("#" + objId); if (objTag.length === 0 || objTag.is(":hidden")) return; // throw new Error("Final Error:Html Object [" + objId + "] not exist, the application is not work."); var t = objTag.offset().top; var l = objTag.offset().left; var w = objTag.width(); var padl = jQuery.browser.mozilla ? "1" : "0"; if (term.top) { t = t + term.top; } else { t = t + options.keyStyle.top; } // 根据个数计算宽度 var cc = keyName.split(",").length; //var kwidth = cc === 1 ? HOTKEY_WIDTH : cc * HOTKEY_WIDTH * 0.6;//动态宽度 var kwidth = HOTKEY_WIDTH; if (options.keyStyle.align === "center") { l = l + w / 2 - kwidth / 2; } var tagid = objId + "_hotkey_" + keyName.substr(0, 1); var tagdiv = jQuery("#" + tagid); if (tagdiv.length === 0) { jQuery("<div/>") .attr("id", tagid) .text(keyName.replace(//,/g, "").toUpperCase()) .css({ "top": t + "px", "left": l + "px", "position": "absolute", "width": kwidth + "px", "height": HOTKEY_HEIGHT + "px", "text-align": "center", "font-weight": "bold", "padding-left": padl + "px", "z-index": 999999 }) .addClass("tui-hotkey-bg") .appendTo(document.body); // 将当前显示的div存储,以便移除操作。 _objDIV.push(tagid); } }; // 移除热键提示层,并且传入是否移除Cookie的bool值。 function clearHotkey(isDeleteCookie) { jQuery.each(_objDIV, function(i, n) { jQuery("#" + n).remove(); }); if (isDeleteCookie) { deleteCookie(); } // 重置显示层与当前按键key数组。 _objDIV = new Array(); }; // 返回符合pid条件的key function keysByPID(pid) { var temp = new Array(); jQuery.each(options.keys, function(i, n) { if (n.pid === pid) { temp.push(n); } }); return temp; }; // 根据key与pid查找key对象,只返回第一个符合条件的,如果是两个就说明同层进行了重复定义。 function findKey(key, pid) { var temp, c = 0; jQuery.each(options.keys, function(i, n) { if (n.pid === pid && n.key.toUpperCase() === key.toUpperCase()) { temp = n; c++; } }); if (c === 0) return "none"; if (c > 1) return "repeat"; return temp; }; // Cookie操作,调用jquery.cookie.js function setCookie(value) { _cookie("tuihotkey", value, { path: '/' }); }; // 返回保存的按键数组 function getCookieObject() { if (_cookie("tuihotkey") != null) { if (_cookie("tuihotkey") === "") { return null; } return eval("(" + _cookie("tuihotkey") + ")"); } return null; }; // 删除cookie function deleteCookie() { _cookie("tuihotkey", null, { path: '/' }); }; // 从jquery.cookie.js中复制过来的cookie操作 function _cookie(name, value, options) { if (typeof value != 'undefined') { // name and value given, set cookie options = options || {}; if (value === null) { value = ''; options = jQuery.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE } // NOTE Needed to parenthesize options.path and options.domain // in the following expressions, otherwise they evaluate to undefined // in the packed version for some reason... var path = options.path ? '; path=' + (options.path) : ''; var domain = options.domain ? '; domain=' + (options.domain) : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { // only name given, get cookie var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } }; //阻止浏览器的默认行为 function _stopDefault(e) { //阻止默认浏览器动作(W3C) if (e && e.preventDefault) e.preventDefault(); //IE中阻止函数器默认动作的方式 else window.event.returnValue = false; return false; }; function _stopBubble(e) { //如果提供了事件对象,则这是一个非IE浏览器 if (e && e.stopPropagation) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否则,我们需要使用IE的方式来取消事件冒泡 window.event.cancelBubble = true; }; }; 如下图所示: