1 /** 2 * @fileOverview menu model and controller for kissy,accommodate menu items 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("menu/base", function (S, Event, Component, MenuRender) { 6 var KeyCodes = Event.KeyCodes; 7 8 function onMenuHide() { 9 this.set("highlightedItem", null); 10 } 11 12 /** 13 * @name Menu 14 * @class 15 * KISSY Menu. 16 * xclass: 'menu'. 17 * @extends Component.Container 18 */ 19 var Menu = Component.Container.extend( 20 /** @lends Menu.prototype*/ 21 { 22 _uiSetHighlightedItem:function (v, ev) { 23 var pre = ev && ev.prevVal; 24 if (pre) { 25 pre.set("highlighted", false); 26 } 27 v && v.set("highlighted", true); 28 this.set("activeItem", v); 29 }, 30 31 handleBlur:function (e) { 32 Menu.superclass.handleBlur.call(this, e); 33 this.set("highlightedItem", null); 34 }, 35 36 37 //dir : -1 ,+1 38 //skip disabled items 39 _getNextEnabledHighlighted:function (index, dir) { 40 var children = this.get("children"), 41 len = children.length, 42 o = index; 43 do { 44 var c = children[index]; 45 if (!c.get("disabled") && (c.get("visible") !== false)) { 46 return children[index]; 47 } 48 index = (index + dir + len) % len; 49 } while (index != o); 50 return undefined; 51 }, 52 53 handleKeydown:function (e) { 54 if (this.handleKeyEventInternal(e)) { 55 e.halt(); 56 return true; 57 } 58 // return false , 会阻止 tab 键 .... 59 return undefined; 60 }, 61 62 /** 63 * Attempts to handle a keyboard event; 64 * returns true if the event was handled, 65 * false otherwise. 66 * If the container is enabled, and a child is highlighted, 67 * calls the child controller's {@code handleKeydown} method to give the control 68 * a chance to handle the event first. 69 * Protected, should only be overridden by subclasses. 70 * @param {Event.Object} e Key event to handle. 71 * @return {Boolean} Whether the event was handled by the container (or one of 72 * its children). 73 * @protected 74 * @override 75 */ 76 handleKeyEventInternal:function (e) { 77 78 // Give the highlighted control the chance to handle the key event. 79 var highlightedItem = this.get("highlightedItem"); 80 81 // 先看当前活跃 menuitem 是否要处理 82 if (highlightedItem && highlightedItem.handleKeydown(e)) { 83 return true; 84 } 85 86 var children = this.get("children"), len = children.length; 87 88 if (len === 0) { 89 return undefined; 90 } 91 92 var index, destIndex; 93 94 //自己处理了,不要向上处理,嵌套菜单情况 95 switch (e.keyCode) { 96 // esc 97 case KeyCodes.ESC: 98 // TODO 99 // focus 的话手动失去焦点 100 return undefined; 101 break; 102 103 // home 104 case KeyCodes.HOME: 105 this.set("highlightedItem", 106 this._getNextEnabledHighlighted(0, 1)); 107 break; 108 // end 109 case KeyCodes.END: 110 this.set("highlightedItem", 111 this._getNextEnabledHighlighted(len - 1, -1)); 112 break; 113 // up 114 case KeyCodes.UP: 115 if (!highlightedItem) { 116 destIndex = len - 1; 117 } else { 118 index = S.indexOf(highlightedItem, children); 119 destIndex = (index - 1 + len) % len; 120 } 121 this.set("highlightedItem", 122 this._getNextEnabledHighlighted(destIndex, -1)); 123 break; 124 //down 125 case KeyCodes.DOWN: 126 if (!highlightedItem) { 127 destIndex = 0; 128 } else { 129 index = S.indexOf(highlightedItem, children); 130 destIndex = (index + 1 + len) % len; 131 } 132 this.set("highlightedItem", 133 this._getNextEnabledHighlighted(destIndex, 1)); 134 break; 135 default: 136 return undefined; 137 } 138 return true; 139 }, 140 141 bindUI:function () { 142 var self = this; 143 /** 144 * 隐藏后,去掉高亮与当前 145 */ 146 self.on("hide", onMenuHide, self); 147 }, 148 149 /** 150 * Whether this menu contains specified html element. 151 * @param {NodeList} element Html Element to be tested. 152 * @return {Boolean} 153 */ 154 containsElement:function (element) { 155 var self = this; 156 157 // 隐藏当然不包含了 158 // self.get("visible") === undefined 相当于 true 159 if (self.get("visible") === false || !self.get("view")) { 160 return false; 161 } 162 163 if (self.get("view").containsElement(element)) { 164 return true; 165 } 166 167 var children = self.get('children'); 168 169 for (var i = 0, count = children.length; i < count; i++) { 170 var child = children[i]; 171 if (typeof child.containsElement == 'function' && 172 child.containsElement(element)) { 173 return true; 174 } 175 } 176 177 return false; 178 } 179 }, { 180 ATTRS:/** @lends Menu.prototype*/ 181 { 182 visibleMode:{ 183 value:"display" 184 }, 185 /** 186 * Current highlighted child menu item. 187 * @type Menu.Item 188 */ 189 highlightedItem:{}, 190 /** 191 * Current active menu item. Maybe a descendant but not a child of current menu. 192 * @type Menu.Item 193 */ 194 activeItem:{ 195 view:1 196 }, 197 xrender:{ 198 value:MenuRender 199 } 200 } 201 }, { 202 xclass:'menu', 203 priority:10 204 }); 205 206 return Menu; 207 208 }, { 209 requires:['event', 'component', './menuRender', './submenu'] 210 }); 211 212 /** 213 * 普通菜单可聚焦 214 * 通过 tab 聚焦到菜单的根节点,通过上下左右操作子菜单项 215 * 216 * TODO 217 * - 去除 activeItem 218 **/