|
1 /** |
|
2 * MenuButton.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * Creates a new menu button. |
|
13 * |
|
14 * @-x-less MenuButton.less |
|
15 * @class tinymce.ui.MenuButton |
|
16 * @extends tinymce.ui.Button |
|
17 */ |
|
18 define("tinymce/ui/MenuButton", [ |
|
19 "tinymce/ui/Button", |
|
20 "tinymce/ui/Factory", |
|
21 "tinymce/ui/MenuBar" |
|
22 ], function(Button, Factory, MenuBar) { |
|
23 "use strict"; |
|
24 |
|
25 // TODO: Maybe add as some global function |
|
26 function isChildOf(node, parent) { |
|
27 while (node) { |
|
28 if (parent === node) { |
|
29 return true; |
|
30 } |
|
31 |
|
32 node = node.parentNode; |
|
33 } |
|
34 |
|
35 return false; |
|
36 } |
|
37 |
|
38 var MenuButton = Button.extend({ |
|
39 /** |
|
40 * Constructs a instance with the specified settings. |
|
41 * |
|
42 * @constructor |
|
43 * @param {Object} settings Name/value object with settings. |
|
44 */ |
|
45 init: function(settings) { |
|
46 var self = this; |
|
47 |
|
48 self._renderOpen = true; |
|
49 self._super(settings); |
|
50 |
|
51 self.addClass('menubtn'); |
|
52 |
|
53 if (settings.fixedWidth) { |
|
54 self.addClass('fixed-width'); |
|
55 } |
|
56 |
|
57 self.aria('haspopup', true); |
|
58 self.hasPopup = true; |
|
59 }, |
|
60 |
|
61 /** |
|
62 * Shows the menu for the button. |
|
63 * |
|
64 * @method showMenu |
|
65 */ |
|
66 showMenu: function() { |
|
67 var self = this, settings = self.settings, menu; |
|
68 |
|
69 if (self.menu && self.menu.visible()) { |
|
70 return self.hideMenu(); |
|
71 } |
|
72 |
|
73 if (!self.menu) { |
|
74 menu = settings.menu || []; |
|
75 |
|
76 // Is menu array then auto constuct menu control |
|
77 if (menu.length) { |
|
78 menu = { |
|
79 type: 'menu', |
|
80 items: menu |
|
81 }; |
|
82 } else { |
|
83 menu.type = menu.type || 'menu'; |
|
84 } |
|
85 |
|
86 self.menu = Factory.create(menu).parent(self).renderTo(); |
|
87 self.fire('createmenu'); |
|
88 self.menu.reflow(); |
|
89 self.menu.on('cancel', function(e) { |
|
90 if (e.control.parent() === self.menu) { |
|
91 e.stopPropagation(); |
|
92 self.focus(); |
|
93 self.hideMenu(); |
|
94 } |
|
95 }); |
|
96 |
|
97 // Move focus to button when a menu item is selected/clicked |
|
98 self.menu.on('select', function() { |
|
99 self.focus(); |
|
100 }); |
|
101 |
|
102 self.menu.on('show hide', function(e) { |
|
103 if (e.control == self.menu) { |
|
104 self.activeMenu(e.type == 'show'); |
|
105 } |
|
106 |
|
107 self.aria('expanded', e.type == 'show'); |
|
108 }).fire('show'); |
|
109 } |
|
110 |
|
111 self.menu.show(); |
|
112 self.menu.layoutRect({w: self.layoutRect().w}); |
|
113 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); |
|
114 }, |
|
115 |
|
116 /** |
|
117 * Hides the menu for the button. |
|
118 * |
|
119 * @method hideMenu |
|
120 */ |
|
121 hideMenu: function() { |
|
122 var self = this; |
|
123 |
|
124 if (self.menu) { |
|
125 self.menu.items().each(function(item) { |
|
126 if (item.hideMenu) { |
|
127 item.hideMenu(); |
|
128 } |
|
129 }); |
|
130 |
|
131 self.menu.hide(); |
|
132 } |
|
133 }, |
|
134 |
|
135 /** |
|
136 * Sets the active menu state. |
|
137 * |
|
138 * @private |
|
139 */ |
|
140 activeMenu: function(state) { |
|
141 this.toggleClass('active', state); |
|
142 }, |
|
143 |
|
144 /** |
|
145 * Renders the control as a HTML string. |
|
146 * |
|
147 * @method renderHtml |
|
148 * @return {String} HTML representing the control. |
|
149 */ |
|
150 renderHtml: function() { |
|
151 var self = this, id = self._id, prefix = self.classPrefix; |
|
152 var icon = self.settings.icon, image; |
|
153 |
|
154 image = self.settings.image; |
|
155 if (image) { |
|
156 icon = 'none'; |
|
157 |
|
158 // Support for [high dpi, low dpi] image sources |
|
159 if (typeof image != "string") { |
|
160 image = window.getSelection ? image[0] : image[1]; |
|
161 } |
|
162 |
|
163 image = ' style="background-image: url(\'' + image + '\')"'; |
|
164 } else { |
|
165 image = ''; |
|
166 } |
|
167 |
|
168 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; |
|
169 |
|
170 self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); |
|
171 |
|
172 return ( |
|
173 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' + |
|
174 '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' + |
|
175 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + |
|
176 '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' + |
|
177 ' <i class="' + prefix + 'caret"></i>' + |
|
178 '</button>' + |
|
179 '</div>' |
|
180 ); |
|
181 }, |
|
182 |
|
183 /** |
|
184 * Gets invoked after the control has been rendered. |
|
185 * |
|
186 * @method postRender |
|
187 */ |
|
188 postRender: function() { |
|
189 var self = this; |
|
190 |
|
191 self.on('click', function(e) { |
|
192 if (e.control === self && isChildOf(e.target, self.getEl())) { |
|
193 self.showMenu(); |
|
194 |
|
195 if (e.aria) { |
|
196 self.menu.items()[0].focus(); |
|
197 } |
|
198 } |
|
199 }); |
|
200 |
|
201 self.on('mouseenter', function(e) { |
|
202 var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; |
|
203 |
|
204 if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { |
|
205 parent.items().filter('MenuButton').each(function(ctrl) { |
|
206 if (ctrl.hideMenu && ctrl != overCtrl) { |
|
207 if (ctrl.menu && ctrl.menu.visible()) { |
|
208 hasVisibleSiblingMenu = true; |
|
209 } |
|
210 |
|
211 ctrl.hideMenu(); |
|
212 } |
|
213 }); |
|
214 |
|
215 if (hasVisibleSiblingMenu) { |
|
216 overCtrl.focus(); // Fix for: #5887 |
|
217 overCtrl.showMenu(); |
|
218 } |
|
219 } |
|
220 }); |
|
221 |
|
222 return self._super(); |
|
223 }, |
|
224 |
|
225 /** |
|
226 * Sets/gets the current button text. |
|
227 * |
|
228 * @method text |
|
229 * @param {String} [text] New button text. |
|
230 * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance. |
|
231 */ |
|
232 text: function(text) { |
|
233 var self = this, i, children; |
|
234 |
|
235 if (self._rendered) { |
|
236 children = self.getEl('open').getElementsByTagName('span'); |
|
237 for (i = 0; i < children.length; i++) { |
|
238 children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text); |
|
239 } |
|
240 } |
|
241 |
|
242 return this._super(text); |
|
243 }, |
|
244 |
|
245 /** |
|
246 * Removes the control and it's menus. |
|
247 * |
|
248 * @method remove |
|
249 */ |
|
250 remove: function() { |
|
251 this._super(); |
|
252 |
|
253 if (this.menu) { |
|
254 this.menu.remove(); |
|
255 } |
|
256 } |
|
257 }); |
|
258 |
|
259 return MenuButton; |
|
260 }); |