]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/layout/container/boxOverflow/Menu.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / layout / container / boxOverflow / Menu.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 */\r
4Ext.define('Ext.layout.container.boxOverflow.Menu', {\r
5\r
6 /* Begin Definitions */\r
7\r
8 extend: 'Ext.layout.container.boxOverflow.None',\r
9 requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],\r
10 alternateClassName: 'Ext.layout.boxOverflow.Menu',\r
11 alias: [\r
12 'box.overflow.menu',\r
13 'box.overflow.Menu' // capitalized for 4.x compat\r
14 ],\r
15\r
16 /* End Definitions */\r
17\r
18 /**\r
19 * @property {String} noItemsMenuText\r
20 * HTML fragment to render into the toolbar overflow menu if there are no items to display\r
21 */\r
22 noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items" role="menuitem">(None)</div>',\r
23\r
24 menuCls: Ext.baseCSSPrefix + 'box-menu',\r
25\r
26 constructor: function(config) {\r
27 var me = this;\r
28\r
29 me.callParent([config]);\r
30\r
31 /**\r
32 * @property {Array} menuItems\r
33 * Array of all items that are currently hidden and should go into the dropdown menu\r
34 */\r
35 me.menuItems = [];\r
36 },\r
37\r
38 beginLayout: function (ownerContext) {\r
39 this.callParent([ownerContext]);\r
40\r
41 // Before layout, we need to re-show all items which we may have hidden due to a\r
42 // previous overflow...\r
43 this.clearOverflow(ownerContext);\r
44 },\r
45\r
46 beginLayoutCycle: function (ownerContext, firstCycle) {\r
47 this.callParent([ownerContext, firstCycle]);\r
48\r
49 if (!firstCycle) {\r
50 // if we are being re-run, we need to clear any overflow from the last run and\r
51 // recache the childItems collection\r
52 this.clearOverflow(ownerContext);\r
53\r
54 this.layout.cacheChildItems(ownerContext);\r
55 }\r
56 },\r
57\r
58 onRemove: function(comp) {\r
59 Ext.Array.remove(this.menuItems, comp);\r
60 },\r
61\r
62 clearItem: function(comp) {\r
63 var menu = comp.menu;\r
64\r
65 if (comp.isButton && menu) {\r
66 // If the button had a menu, forcibly set it\r
67 // again so that the ownerCmp is reset correctly\r
68 // and is no longer pointing at the overflow\r
69 comp.setMenu(menu, false);\r
70 }\r
71 },\r
72\r
73 // We don't define a prefix in menu overflow.\r
74 getSuffixConfig: function() {\r
75 var me = this,\r
76 layout = me.layout,\r
77 owner = layout.owner,\r
78 oid = owner.id;\r
79\r
80 /**\r
81 * @private\r
82 * @property {Ext.menu.Menu} menu\r
83 * The expand menu - holds items for every item that cannot be shown\r
84 * because the container is currently not large enough.\r
85 */\r
86 me.menu = new Ext.menu.Menu({\r
87 listeners: {\r
88 scope: me,\r
89 beforeshow: me.beforeMenuShow\r
90 }\r
91 });\r
92\r
93 /**\r
94 * @private\r
95 * @property {Ext.button.Button} menuTrigger\r
96 * The expand button which triggers the overflow menu to be shown\r
97 */\r
98 me.menuTrigger = new Ext.button.Button({\r
99 id: oid + '-menu-trigger',\r
100 cls: me.menuCls + '-after ' + Ext.baseCSSPrefix + 'toolbar-item',\r
101 plain: owner.usePlainButtons,\r
102 ownerCt: owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree\r
103 ownerLayout: layout,\r
104 iconCls: Ext.baseCSSPrefix + me.getOwnerType(owner) + '-more-icon',\r
105 ui: owner.defaultButtonUI || 'default',\r
106 menu: me.menu,\r
107 // Menu will be empty when we're showing it because we populate items after\r
108 showEmptyMenu: true,\r
109 getSplitCls: function() { return '';}\r
110 });\r
111\r
112 return me.menuTrigger.getRenderTree();\r
113 },\r
114 \r
115 getOverflowCls: function(direction) {\r
116 return this.menuCls + '-body-' + direction;\r
117 },\r
118\r
119 handleOverflow: function(ownerContext) {\r
120 var me = this,\r
121 layout = me.layout;\r
122\r
123 me.showTrigger(ownerContext);\r
124\r
125 // Center the menuTrigger button only if we are not vertical.\r
126 if (layout.direction !== 'vertical') {\r
127 me.menuTrigger.setLocalY(\r
128 (ownerContext.state.boxPlan.maxSize - me.menuTrigger[layout.names.getHeight]()) / 2\r
129 );\r
130 }\r
131\r
132 return {\r
133 reservedSpace: me.triggerTotalWidth\r
134 };\r
135 },\r
136\r
137 /**\r
138 * Finishes the render operation of the trigger Button.\r
139 * @private\r
140 */\r
141 captureChildElements: function() {\r
142 var me = this,\r
143 menuTrigger = me.menuTrigger,\r
144 names = me.layout.names;\r
145\r
146 // The rendering flag is set when getRenderTree is called which we do when returning markup string for the owning layout's "suffix"\r
147 if (menuTrigger.rendering) {\r
148 menuTrigger.finishRender();\r
149 me.triggerTotalWidth = menuTrigger[names.getWidth]() + menuTrigger.el.getMargin(names.parallelMargins);\r
150 }\r
151 },\r
152\r
153 /**\r
154 * @private\r
155 * Called by the layout, when it determines that there is no overflow.\r
156 * Also called as an interceptor to the layout's onLayout method to reshow\r
157 * previously hidden overflowing items.\r
158 */\r
159 clearOverflow: function(ownerContext) {\r
160 var me = this,\r
161 items = me.menuItems,\r
162 length = items.length,\r
163 owner = me.layout.owner,\r
164 asLayoutRoot = owner._asLayoutRoot,\r
165 item, i;\r
166\r
167 owner.suspendLayouts();\r
168 me.captureChildElements();\r
169 me.hideTrigger();\r
170 owner.resumeLayouts();\r
171\r
172 for (i = 0; i < length; i++) {\r
173 item = items[i];\r
174\r
175 // What we are doing here is preventing the layout bubble from invalidating our\r
176 // owner component. We need just the button to be added to the layout run.\r
177 item.suspendLayouts();\r
178 item.show();\r
179 me.clearItem(item);\r
180 item.resumeLayouts(asLayoutRoot);\r
181 }\r
182\r
183 items.length = 0;\r
184 },\r
185\r
186 /**\r
187 * @private\r
188 * Shows the overflow trigger when enableOverflow is set to true and the items\r
189 * in the layout are too wide to fit in the space available\r
190 */\r
191 showTrigger: function(ownerContext) {\r
192 var me = this,\r
193 layout = me.layout,\r
194 owner = layout.owner,\r
195 names = layout.names,\r
196 startProp = names.x,\r
197 sizeProp = names.width,\r
198 plan = ownerContext.state.boxPlan,\r
199 available = plan.targetSize[sizeProp],\r
200 childItems = ownerContext.childItems,\r
201 menuTrigger = me.menuTrigger,\r
202 menuItems = me.menuItems,\r
203 childContext, comp, i, props, len;\r
204\r
205 // We don't want the menuTrigger.show to cause owner's layout to be invalidated, so\r
206 // we force just the button to be invalidated and added to the current run.\r
207 menuTrigger.suspendLayouts();\r
208 menuTrigger.show();\r
209 menuTrigger.resumeLayouts(me._asLayoutRoot);\r
210\r
211 available -= me.triggerTotalWidth;\r
212\r
213 owner.suspendLayouts();\r
214\r
215 // Hide all items which are off the end, and store them to allow them to be restored\r
216 // before each layout operation.\r
217 for (i = 0, len = menuItems.length; i < len; ++i) {\r
218 me.clearItem(menuItems[i]);\r
219 }\r
220 menuItems.length = 0;\r
221\r
222 for (i = 0, len = childItems.length; i < len; i++) {\r
223 childContext = childItems[i];\r
224 props = childContext.props;\r
225 if (props[startProp] + props[sizeProp] > available) {\r
226 comp = childContext.target;\r
227 me.menuItems.push(comp);\r
228 comp.hide();\r
229 }\r
230 }\r
231\r
232 owner.resumeLayouts();\r
233 },\r
234\r
235 /**\r
236 * @private\r
237 */\r
238 hideTrigger: function() {\r
239 var menuTrigger = this.menuTrigger;\r
240 if (menuTrigger) {\r
241 menuTrigger.hide();\r
242 }\r
243 },\r
244\r
245 /**\r
246 * @private\r
247 * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.\r
248 */\r
249 beforeMenuShow: function(menu) {\r
250 var me = this,\r
251 items = me.menuItems,\r
252 i = 0,\r
253 len = items.length,\r
254 item,\r
255 prev,\r
256 needsSep = function(group, prev){\r
257 return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);\r
258 };\r
259\r
260 menu.suspendLayouts();\r
261 menu.removeAll(false);\r
262\r
263 for (; i < len; i++) {\r
264 item = items[i];\r
265\r
266 // Do not show a separator as a first item\r
267 if (!i && (item instanceof Ext.toolbar.Separator)) {\r
268 continue;\r
269 }\r
270 if (prev && (needsSep(item, prev) || needsSep(prev, item))) {\r
271 menu.add('-');\r
272 }\r
273\r
274 me.addComponentToMenu(menu, item);\r
275 prev = item;\r
276 }\r
277\r
278 // put something so the menu isn't empty if no compatible items found\r
279 if (menu.items.length < 1) {\r
280 menu.add(me.noItemsMenuText);\r
281 }\r
282 menu.resumeLayouts();\r
283 },\r
284 \r
285 /**\r
286 * @private\r
287 * Returns a menu config for a given component. This config is used to create a menu item\r
288 * to be added to the expander menu\r
289 * @param {Ext.Component} component The component to create the config for\r
290 * @param {Boolean} hideOnClick Passed through to the menu item\r
291 */\r
292 createMenuConfig: function (component, hideOnClick) {\r
293 var config = Ext.apply({}, component.initialConfig),\r
294 group = component.toggleGroup;\r
295\r
296 Ext.copy(config, component, [\r
297 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu', 'tabIndex'\r
298 ]);\r
299\r
300 Ext.applyIf(config, {\r
301 text: component.overflowText || component.text,\r
302 hideOnClick: hideOnClick,\r
303 destroyMenu: false,\r
304 listeners: null\r
305 });\r
306 config.masterComponent = component;\r
307\r
308 // Clone must have same value, and must sync original's value on change\r
309 if (component.isFormField) {\r
310 config.value = component.getValue();\r
311\r
312 // Sync the original component's value when the clone changes value.\r
313 // This intentionally overwrites any developer-configured change listener on the clone.\r
314 // That's because we monitor the clone's change event, and sync the\r
315 // original field by calling setValue, so the original field's change\r
316 // event will still fire.\r
317 config.listeners = {\r
318 change: function(c, newVal, oldVal) { \r
319 c.masterComponent.setValue(newVal);\r
320 }\r
321 };\r
322 // Sync the cloned Component's value when the master changes value.\r
323 component.on('change', function(c, newVal, oldVal) {\r
324 c.overflowClone.setValue(newVal);\r
325 });\r
326 }\r
327\r
328 // ToggleButtons become CheckItems\r
329 else if (group || component.enableToggle) {\r
330 Ext.apply(config, {\r
331 hideOnClick: false,\r
332 group: group,\r
333 checked: component.pressed,\r
334 handler: function (item, e) {\r
335 item.masterComponent.onClick(e);\r
336 }\r
337 });\r
338 }\r
339\r
340 // Buttons may have their text or icon changed - this must be propagated to the clone in the overflow menu\r
341 if (component.isButton && !component.changeListenersAdded) {\r
342 component.on({\r
343 textchange: this.onButtonAttrChange,\r
344 iconchange: this.onButtonAttrChange,\r
345 toggle: this.onButtonToggle\r
346 });\r
347 component.changeListenersAdded = true;\r
348 }\r
349\r
350 // Typically margins are used to separate items in a toolbar\r
351 // but don't really make a lot of sense in a menu, so we strip\r
352 // them out here.\r
353 delete config.margin;\r
354 delete config.ownerCt;\r
355 delete config.xtype;\r
356 delete config.id;\r
357 delete config.itemId;\r
358 return config;\r
359 },\r
360\r
361 onButtonAttrChange: function(btn) {\r
362 var clone = btn.overflowClone;\r
363 clone.suspendLayouts();\r
364 clone.setText(btn.text);\r
365 clone.setIcon(btn.icon);\r
366 clone.setIconCls(btn.iconCls);\r
367 clone.resumeLayouts(true);\r
368 },\r
369\r
370 onButtonToggle: function(btn, state) {\r
371 // Keep the clone in sync with the original if necessary\r
372 if (btn.overflowClone.checked !== state) {\r
373 btn.overflowClone.setChecked(state);\r
374 }\r
375 },\r
376\r
377 /**\r
378 * @private\r
379 * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.\r
380 * @param {Ext.menu.Menu} menu The menu to add to\r
381 * @param {Ext.Component} component The component to add\r
382 * TODO: Implement overrides in Ext.layout.container.boxOverflow which create overrides\r
383 * for SplitButton, Button, ButtonGroup, and TextField. And a generic one for Component\r
384 * which create clones suitable for use in an overflow menu.\r
385 */\r
386 addComponentToMenu: function(menu, component) {\r
387 var me = this,\r
388 i, items, iLen;\r
389\r
390 // No equivalent to fill, skip it\r
391 if (component instanceof Ext.toolbar.Fill) {\r
392 return;\r
393 }\r
394 // Separator maps to MenuSeparator\r
395 else if (component instanceof Ext.toolbar.Separator) {\r
396 menu.add('-');\r
397 }\r
398 else if (component.overflowClone) {\r
399 menu.add(component.overflowClone);\r
400 }\r
401 // Other types...\r
402 else if (component.isComponent) {\r
403 if (component.isXType('splitbutton')) {\r
404 component.overflowClone = menu.add(me.createMenuConfig(component, true));\r
405\r
406 } else if (component.isXType('button')) {\r
407 component.overflowClone = menu.add(me.createMenuConfig(component, !component.menu));\r
408\r
409 } else if (component.isXType('buttongroup')) {\r
410 items = component.items.items;\r
411 iLen = items.length;\r
412\r
413 for (i = 0; i < iLen; i++) {\r
414 me.addComponentToMenu(menu, items[i]);\r
415 }\r
416 } else {\r
417 component.overflowClone = menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));\r
418 }\r
419 }\r
420 },\r
421\r
422 destroy: function() {\r
423 var me = this,\r
424 trigger = me.menuTrigger;\r
425 \r
426 if (trigger && !me.layout.owner.items.contains(trigger)) {\r
427 // Ensure we delete the ownerCt if it's not in the items\r
428 // so we don't get spurious container remove warnings.\r
429 delete trigger.ownerCt;\r
430 }\r
431 me.menu = me.menuTrigger = Ext.destroy(me.menu, trigger);\r
432 me.callParent();\r
433 }\r
434});