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  **/