]>
Commit | Line | Data |
---|---|---|
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 | |
35 | Ext.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 |