]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/button/Split.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / button / Split.js
CommitLineData
6527f429
DM
1/**\r
2 * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event\r
3 * of the button. Typically this would be used to display a dropdown menu that provides additional options to the\r
4 * primary button action, but any custom handler can provide the arrowclick implementation. Example usage:\r
5 *\r
6 * @example\r
7 * // display a dropdown menu:\r
8 * Ext.create('Ext.button.Split', {\r
9 * renderTo: Ext.getBody(),\r
10 * text: 'Options',\r
11 * // handle a click on the button itself\r
12 * handler: function() {\r
13 * alert("The button was clicked");\r
14 * },\r
15 * menu: new Ext.menu.Menu({\r
16 * items: [\r
17 * // these will render as dropdown menu items when the arrow is clicked:\r
18 * {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},\r
19 * {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}\r
20 * ]\r
21 * })\r
22 * });\r
23 *\r
24 * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown\r
25 * arrow is clicked:\r
26 *\r
27 * Ext.create('Ext.button.Split', {\r
28 * renderTo: 'button-ct',\r
29 * text: 'Options',\r
30 * handler: optionsHandler,\r
31 * arrowHandler: myCustomHandler\r
32 * });\r
33 *\r
34 */\r
35Ext.define('Ext.button.Split', {\r
36 extend: 'Ext.button.Button',\r
37 alternateClassName: 'Ext.SplitButton',\r
38 alias: 'widget.splitbutton',\r
39\r
40 isSplitButton: true,\r
41 \r
42 /**\r
43 * @cfg {Function/String} arrowHandler\r
44 * A function called when the arrow button is clicked (can be used instead of click event)\r
45 * @cfg {Ext.button.Split} arrowHandler.this\r
46 * @cfg {Event} arrowHandler.e The click event.\r
47 * @declarativeHandler\r
48 */\r
49 \r
50 /**\r
51 * @cfg {String} arrowTooltip\r
52 * The title attribute of the arrow.\r
53 */\r
54\r
55 /**\r
56 * @private\r
57 */\r
58 arrowCls: 'split',\r
59 split: true,\r
60\r
61 /**\r
62 * @event arrowclick\r
63 * Fires when this button's arrow is clicked.\r
64 * @param {Ext.button.Split} this\r
65 * @param {Event} e The click event.\r
66 */\r
67\r
68 // It is possible to use both menu and arrowHandler with a Split button, which is confusing\r
69 // and will clash with WAI-ARIA requirements. So we check that and warn if need be.\r
70 // Unfortunately this won't work with arrowclick event that can be subscribed to\r
71 // dynamically but we don't want to run these checks at run time so there's a limit\r
72 // to what we can do about it.\r
73 //<debug>\r
74 initComponent: function() {\r
75 var me = this;\r
76 \r
77 // Don't warn if we're under the slicer\r
78 if (Ext.enableAriaButtons && !Ext.slicer && me.menu &&\r
79 (me.arrowHandler || me.hasListeners.hasOwnProperty('arrowclick'))) {\r
80 // Hard error if full ARIA compatibility is enabled, otherwise a warning\r
81 var logFn = Ext.enableAria ? Ext.log.error : Ext.log.warn;\r
82 \r
83 logFn(\r
84 "Using both menu and arrowHandler config options in Split buttons " +\r
85 "leads to confusing user experience and conflicts with accessibility " +\r
86 "best practices. See WAI-ARIA 1.0 Authoring guide: " +\r
87 "http://www.w3.org/TR/wai-aria-practices/#menubutton"\r
88 );\r
89 }\r
90 \r
91 me.callParent();\r
92 },\r
93 //</debug>\r
94 \r
95 getTemplateArgs: function() {\r
96 var me = this,\r
97 ariaAttr, data;\r
98 \r
99 data = me.callParent();\r
100 \r
101 if (me.disabled) {\r
102 data.tabIndex = null;\r
103 }\r
104 \r
105 ariaAttr = me.ariaArrowElAttributes || {};\r
106 \r
107 ariaAttr['aria-hidden'] = !!me.hidden;\r
108 ariaAttr['aria-disabled'] = !!me.disabled;\r
109 \r
110 if (me.arrowTooltip) {\r
111 ariaAttr['aria-label'] = me.arrowTooltip;\r
112 }\r
113 else {\r
114 ariaAttr['aria-labelledby'] = me.id;\r
115 }\r
116 \r
117 data.arrowElAttributes = ariaAttr;\r
118 \r
119 return data;\r
120 },\r
121\r
122 onRender: function() {\r
123 var me = this,\r
124 el;\r
125 \r
126 me.callParent();\r
127 \r
128 el = me.getFocusEl();\r
129 \r
130 if (el) {\r
131 el.on({\r
132 scope: me,\r
133 focus: me.onMainElFocus,\r
134 blur: me.onMainElBlur\r
135 });\r
136 }\r
137 \r
138 el = me.arrowEl;\r
139 \r
140 if (el) {\r
141 el.dom.setAttribute('data-componentid', me.id);\r
142 el.setVisibilityMode(Ext.dom.Element.DISPLAY);\r
143 \r
144 el.on({\r
145 scope: me,\r
146 focus: me.onArrowElFocus,\r
147 blur: me.onArrowElBlur\r
148 });\r
149 }\r
150 },\r
151\r
152 /**\r
153 * Sets this button's arrow click handler.\r
154 * @param {Function} handler The function to call when the arrow is clicked.\r
155 * @param {Object} scope (optional) Scope for the function passed above.\r
156 */\r
157 setArrowHandler: function(handler, scope) {\r
158 this.arrowHandler = handler;\r
159 this.scope = scope;\r
160 },\r
161\r
162 /**\r
163 * @private\r
164 */\r
165 onClick: function(e) {\r
166 var me = this,\r
167 arrowKeydown = e.type === 'keydown' && e.target === me.arrowEl.dom;\r
168\r
169 me.doPreventDefault(e);\r
170 \r
171 if (!me.disabled) {\r
172 if (arrowKeydown || me.isWithinTrigger(e)) {\r
173 // Force prevent default here, if we click on the arrow part\r
174 // we want to trigger the menu, not any link if we have it\r
175 e.preventDefault();\r
176 me.maybeShowMenu(e);\r
177 me.fireEvent("arrowclick", me, e);\r
178 if (me.arrowHandler) {\r
179 me.arrowHandler.call(me.scope || me, me, e);\r
180 }\r
181 } else {\r
182 me.doToggle();\r
183 me.fireHandler(e);\r
184 }\r
185 }\r
186 },\r
187 \r
188 enable: function(silent) {\r
189 var me = this,\r
190 arrowEl = me.arrowEl;\r
191 \r
192 me.callParent([silent]);\r
193 \r
194 // May not be rendered yet\r
195 if (arrowEl) {\r
196 arrowEl.dom.setAttribute('tabIndex', me.tabIndex);\r
197 arrowEl.dom.setAttribute('aria-disabled', 'false');\r
198 }\r
199 },\r
200 \r
201 disable: function(silent) {\r
202 var me = this,\r
203 arrowEl = me.arrowEl;\r
204 \r
205 me.callParent([silent]);\r
206 \r
207 // May not be rendered yet\r
208 if (arrowEl) {\r
209 arrowEl.dom.removeAttribute('tabIndex');\r
210 arrowEl.dom.setAttribute('aria-disabled', 'true');\r
211 }\r
212 },\r
213 \r
214 afterHide: function(cb, scope) {\r
215 this.callParent([cb, scope]);\r
216 this.arrowEl.dom.setAttribute('aria-hidden', 'true');\r
217 },\r
218 \r
219 afterShow: function(animateTarget, cb, scope) {\r
220 this.callParent([animateTarget, cb, scope]);\r
221 this.arrowEl.dom.setAttribute('aria-hidden', 'false');\r
222 },\r
223 \r
224 privates: {\r
225 isFocusing: function(e) {\r
226 var me = this,\r
227 from = e.fromElement,\r
228 to = e.toElement,\r
229 focusEl = me.focusEl && me.focusEl.dom,\r
230 arrowEl = me.arrowEl && me.arrowEl.dom;\r
231 \r
232 if (me.focusable) {\r
233 if (to === focusEl) { \r
234 return from === arrowEl ? false : true;\r
235 }\r
236 else if (to === arrowEl) {\r
237 return from === focusEl ? false : true;\r
238 }\r
239 \r
240 return true;\r
241 }\r
242 \r
243 return false;\r
244 },\r
245 \r
246 isBlurring: function(e) {\r
247 var me = this,\r
248 from = e.fromElement,\r
249 to = e.toElement,\r
250 focusEl = me.focusEl && me.focusEl.dom,\r
251 arrowEl = me.arrowEl && me.arrowEl.dom;\r
252 \r
253 if (me.focusable) {\r
254 if (from === focusEl) {\r
255 return to === arrowEl ? false : true;\r
256 }\r
257 else if (from === arrowEl) {\r
258 return to === focusEl ? false : true;\r
259 }\r
260 \r
261 return true;\r
262 }\r
263 \r
264 return false;\r
265 },\r
266 \r
267 // We roll our own focus style handling for Split button, see below\r
268 getFocusClsEl: Ext.privateFn,\r
269 \r
270 onMainElFocus: function(e) {\r
271 this.el.addCls(this._focusCls);\r
272 },\r
273 \r
274 onMainElBlur: function(e) {\r
275 this.el.removeCls(this._focusCls);\r
276 },\r
277 \r
278 onArrowElFocus: function(e) {\r
279 this.el.addCls(this._arrowFocusCls);\r
280 },\r
281 \r
282 onArrowElBlur: function() {\r
283 this.el.removeCls(this._arrowFocusCls);\r
284 },\r
285 \r
286 setTabIndex: function(newTabIndex) {\r
287 this.callParent([newTabIndex]);\r
288 \r
289 // May not be rendered yet\r
290 if (this.arrowEl) {\r
291 this.arrowEl.set({ tabIndex: newTabIndex });\r
292 }\r
293 },\r
294 \r
295 // This and below are called by the setMenu method in the parent class.\r
296 _addSplitCls: function() {\r
297 var arrowEl = this.arrowEl;\r
298 \r
299 this.callParent();\r
300 \r
301 arrowEl.dom.setAttribute('tabIndex', this.tabIndex);\r
302 arrowEl.setVisible(true);\r
303 },\r
304 \r
305 _removeSplitCls: function() {\r
306 var arrowEl = this.arrowEl;\r
307 \r
308 this.callParent();\r
309 \r
310 arrowEl.dom.removeAttribute('tabIndex');\r
311 arrowEl.setVisible(false);\r
312 }\r
313 }\r
314});\r