1 /** 2 * @fileOverview combination of menu and button ,similar to native select 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("menubutton/base", function (S, Node, Button, MenuButtonRender, Menu, Component, undefined) { 6 7 var win = S.Env.host; 8 9 function getMenu(self, init) { 10 var m = self.get("menu"); 11 if (m && m.xclass) { 12 if (init) { 13 m = Component.create(m, self); 14 self.__set("menu", m); 15 } else { 16 return null; 17 } 18 } 19 return m; 20 } 21 22 function reposition() { 23 var self = this, 24 menu = getMenu(self); 25 if (menu && menu.get("visible")) { 26 var align = S.clone(menu.get("align")); 27 align.node = self.get("el"); 28 S.mix(align, ALIGN, false); 29 menu.set("align", align); 30 } 31 } 32 33 var repositionBuffer = S.buffer(reposition, 50); 34 35 function hideMenu(self) { 36 var menu = getMenu(self); 37 if (menu) { 38 menu.hide(); 39 } 40 } 41 42 function showMenu(self) { 43 var el = self.get("el"), 44 menu = getMenu(self, 1); 45 // 保证显示前已经 bind 好 menu 事件 46 if (menu && !menu.get("visible")) { 47 // 先 render,监听 width 变化事件 48 menu.render(); 49 self.bindMenu(); 50 // 根据 el 自动调整大小 51 if (self.get("matchElWidth")) { 52 menu.set("width", el.innerWidth()); 53 } 54 menu.show(); 55 reposition.call(self); 56 el.attr("aria-haspopup", menu.get("el").attr("id")); 57 } 58 } 59 60 var $ = Node.all, 61 KeyCodes = Node.KeyCodes, 62 ALIGN = { 63 points:["bl", "tl"], 64 overflow:{ 65 adjustX:1, 66 adjustY:1 67 } 68 }; 69 /** 70 * @class 71 * A menu button component, consist of a button and a drop down popup menu. 72 * xclass: 'menubutton'. 73 * @name MenuButton 74 * @extends Button 75 */ 76 var MenuButton = Button.extend([Component.DecorateChild], 77 /** 78 * @lends MenuButton.prototype 79 */ 80 { 81 _uiSetCollapsed:function (v) { 82 if (v) { 83 hideMenu(this); 84 } else { 85 showMenu(this); 86 } 87 }, 88 89 /** 90 * Bind menu to current component. 91 * Protected, should only be overridden by subclasses. 92 * @protected 93 */ 94 bindMenu:function () { 95 var self = this, 96 menu = self.get("menu"); 97 98 menu.on("afterActiveItemChange", function (ev) { 99 self.set("activeItem", ev.newVal); 100 }); 101 102 menu.on("click", self.handleMenuClick, self); 103 104 // 窗口改变大小,重新调整 105 $(win).on("resize", repositionBuffer, self); 106 107 /* 108 只绑定事件一次 109 */ 110 self.bindMenu = S.noop; 111 }, 112 113 /** 114 * Handle click on drop down menu. Fire click event on menubutton. 115 * Protected, should only be overridden by subclasses. 116 * @param {Event.Object} e Click event object. 117 * @protected 118 */ 119 handleMenuClick:function (e) { 120 var self = this; 121 self.fire("click", { 122 target:e.target 123 }); 124 }, 125 126 /** 127 * Handle keydown/up event. 128 * If drop down menu is visible then handle event to menu. 129 * Returns true if the event was handled, falsy otherwise. 130 * Protected, should only be overridden by subclasses. 131 * @param {Event.Object} e key event to handle. 132 * @return {Boolean} True Whether the key event was handled. 133 * @protected 134 * @override 135 */ 136 handleKeyEventInternal:function (e) { 137 var self = this, 138 menu = getMenu(self); 139 140 // space 只在 keyup 时处理 141 if (e.keyCode == KeyCodes.SPACE) { 142 // Prevent page scrolling in Chrome. 143 e.preventDefault(); 144 if (e.type != "keyup") { 145 return undefined; 146 } 147 } else if (e.type != "keydown") { 148 return undefined; 149 } 150 //转发给 menu 处理 151 if (menu && menu.get("visible")) { 152 var handledByMenu = menu.handleKeydown(e); 153 // esc 154 if (e.keyCode == KeyCodes.ESC) { 155 self.set("collapsed", true); 156 return true; 157 } 158 return handledByMenu; 159 } 160 161 // Menu is closed, and the user hit the down/up/space key; open menu. 162 if (e.keyCode == KeyCodes.SPACE || 163 e.keyCode == KeyCodes.DOWN || 164 e.keyCode == KeyCodes.UP) { 165 self.set("collapsed", false); 166 return true; 167 } 168 return undefined; 169 }, 170 171 /** 172 * Perform default action for menubutton. 173 * Toggle the drop down menu to show or hide. 174 * Protected, should only be overridden by subclasses. 175 * @protected 176 * @override 177 */ 178 performActionInternal:function () { 179 var self = this; 180 self.set("collapsed", !self.get("collapsed")); 181 }, 182 183 /** 184 * Handles blur event. 185 * When it loses keyboard focus, close the drop dow menu. 186 * @param {Event.Object} e Blur event. 187 * Protected, should only be overridden by subclasses. 188 * @protected 189 * @override 190 */ 191 handleBlur:function (e) { 192 var self = this; 193 MenuButton.superclass.handleBlur.call(self, e); 194 // such as : click the document 195 self.set("collapsed", true); 196 }, 197 198 199 /** 200 * Adds a new menu item at the end of the menu. 201 * @param {Menu.Item} item Menu item to add to the menu. 202 */ 203 addItem:function (item, index) { 204 var menu = getMenu(this, 1); 205 menu.addChild(item, index); 206 }, 207 208 /** 209 * Remove a existing menu item from drop down menu. 210 * @param c {Menu.Item} Existing menu item. 211 * @param [destroy] {Boolean} Whether destroy removed menu item. 212 */ 213 removeItem:function (c, destroy) { 214 /** 215 * @type Controller 216 */ 217 var menu = getMenu(this); 218 if (menu) { 219 menu.removeChild(c, destroy); 220 } 221 }, 222 223 /** 224 * Remove all menu items from drop down menu. 225 * @param [destroy] {Boolean} Whether destroy removed menu items. 226 */ 227 removeItems:function (destroy) { 228 var menu = this.get("menu"); 229 if (menu) { 230 if (menu.removeChildren) { 231 menu.removeChildren(destroy); 232 } else if (menu.children) { 233 menu.children = []; 234 } 235 } 236 }, 237 238 /** 239 * Returns the child menu item of drop down menu at the given index, or null if the index is out of bounds. 240 * @param {Number} index 0-based index. 241 */ 242 getItemAt:function (index) { 243 var menu = getMenu(this); 244 return menu && menu.getChildAt(index); 245 }, 246 247 // 禁用时关闭已显示菜单 248 _uiSetDisabled:function (v) { 249 var self = this; 250 !v && self.set("collapsed", true); 251 }, 252 253 /** 254 * Decorate child element to from a child component. 255 * @param {Function} UI Child component's constructor 256 * @param {NodeList} el Child component's root element. 257 * @protected 258 * @override 259 */ 260 decorateChildrenInternal:function (UI, el) { 261 // 不能用 display:none , menu 的隐藏是靠 visibility 262 // eg: menu.show(); menu.hide(); 263 var self = this, 264 docBody = S.one(el[0].ownerDocument.body); 265 docBody.prepend(el); 266 var menu = new UI(S.mix({ 267 srcNode:el, 268 prefixCls:self.get("prefixCls") 269 })); 270 self.__set("menu", menu); 271 }, 272 273 destructor:function () { 274 var self = this; 275 $(win).detach("resize", repositionBuffer, self); 276 var menu = self.get("menu"); 277 if (menu.destroy) { 278 menu.destroy(); 279 } 280 } 281 282 }, 283 284 { 285 ATTRS:/** 286 * @lends MenuButton.prototype 287 */ 288 { 289 /** 290 * Current active menu item. 291 * @type Menu.Item 292 */ 293 activeItem:{ 294 view:1 295 }, 296 297 /** 298 * Whether drop down menu is same width with button. 299 * Default: true. 300 * @type {Boolean} 301 */ 302 matchElWidth:{ 303 value:true 304 }, 305 306 /** 307 * @private 308 */ 309 decorateChildCls:{ 310 valueFn:function () { 311 return this.get("prefixCls") + "popupmenu" 312 } 313 }, 314 /** 315 * Drop down menu associated with this menubutton. 316 * @type Menu 317 */ 318 menu:{ 319 value:{ 320 xclass:'popupmenu' 321 }, 322 setter:function (v) { 323 if (v instanceof Menu) { 324 v.__set("parent", this); 325 } 326 } 327 }, 328 /** 329 * Whether drop menu is shown. 330 * @type Boolean 331 */ 332 collapsed:{ 333 view:1 334 }, 335 xrender:{ 336 value:MenuButtonRender 337 } 338 } 339 }, { 340 xclass:'menu-button', 341 priority:20 342 }); 343 344 return MenuButton; 345 }, { 346 requires:[ "node", "button", "./baseRender", "menu", "component"] 347 });