|
1 /** |
|
2 * ComboBox.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 * This class creates a combobox control. Select box that you select a value from or |
|
13 * type a value into. |
|
14 * |
|
15 * @-x-less ComboBox.less |
|
16 * @class tinymce.ui.ComboBox |
|
17 * @extends tinymce.ui.Widget |
|
18 */ |
|
19 define("tinymce/ui/ComboBox", [ |
|
20 "tinymce/ui/Widget", |
|
21 "tinymce/ui/Factory", |
|
22 "tinymce/ui/DomUtils" |
|
23 ], function(Widget, Factory, DomUtils) { |
|
24 "use strict"; |
|
25 |
|
26 return Widget.extend({ |
|
27 /** |
|
28 * Constructs a new control instance with the specified settings. |
|
29 * |
|
30 * @constructor |
|
31 * @param {Object} settings Name/value object with settings. |
|
32 * @setting {String} placeholder Placeholder text to display. |
|
33 */ |
|
34 init: function(settings) { |
|
35 var self = this; |
|
36 |
|
37 self._super(settings); |
|
38 self.addClass('combobox'); |
|
39 self.subinput = true; |
|
40 self.ariaTarget = 'inp'; // TODO: Figure out a better way |
|
41 |
|
42 settings = self.settings; |
|
43 settings.menu = settings.menu || settings.values; |
|
44 |
|
45 if (settings.menu) { |
|
46 settings.icon = 'caret'; |
|
47 } |
|
48 |
|
49 self.on('click', function(e) { |
|
50 var elm = e.target, root = self.getEl(); |
|
51 |
|
52 while (elm && elm != root) { |
|
53 if (elm.id && elm.id.indexOf('-open') != -1) { |
|
54 self.fire('action'); |
|
55 |
|
56 if (settings.menu) { |
|
57 self.showMenu(); |
|
58 |
|
59 if (e.aria) { |
|
60 self.menu.items()[0].focus(); |
|
61 } |
|
62 } |
|
63 } |
|
64 |
|
65 elm = elm.parentNode; |
|
66 } |
|
67 }); |
|
68 |
|
69 // TODO: Rework this |
|
70 self.on('keydown', function(e) { |
|
71 if (e.target.nodeName == "INPUT" && e.keyCode == 13) { |
|
72 self.parents().reverse().each(function(ctrl) { |
|
73 e.preventDefault(); |
|
74 self.fire('change'); |
|
75 |
|
76 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { |
|
77 ctrl.fire('submit', {data: ctrl.toJSON()}); |
|
78 return false; |
|
79 } |
|
80 }); |
|
81 } |
|
82 }); |
|
83 |
|
84 if (settings.placeholder) { |
|
85 self.addClass('placeholder'); |
|
86 |
|
87 self.on('focusin', function() { |
|
88 if (!self._hasOnChange) { |
|
89 DomUtils.on(self.getEl('inp'), 'change', function() { |
|
90 self.fire('change'); |
|
91 }); |
|
92 |
|
93 self._hasOnChange = true; |
|
94 } |
|
95 |
|
96 if (self.hasClass('placeholder')) { |
|
97 self.getEl('inp').value = ''; |
|
98 self.removeClass('placeholder'); |
|
99 } |
|
100 }); |
|
101 |
|
102 self.on('focusout', function() { |
|
103 if (self.value().length === 0) { |
|
104 self.getEl('inp').value = settings.placeholder; |
|
105 self.addClass('placeholder'); |
|
106 } |
|
107 }); |
|
108 } |
|
109 }, |
|
110 |
|
111 showMenu: function() { |
|
112 var self = this, settings = self.settings, menu; |
|
113 |
|
114 if (!self.menu) { |
|
115 menu = settings.menu || []; |
|
116 |
|
117 // Is menu array then auto constuct menu control |
|
118 if (menu.length) { |
|
119 menu = { |
|
120 type: 'menu', |
|
121 items: menu |
|
122 }; |
|
123 } else { |
|
124 menu.type = menu.type || 'menu'; |
|
125 } |
|
126 |
|
127 self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); |
|
128 self.fire('createmenu'); |
|
129 self.menu.reflow(); |
|
130 self.menu.on('cancel', function(e) { |
|
131 if (e.control === self.menu) { |
|
132 self.focus(); |
|
133 } |
|
134 }); |
|
135 |
|
136 self.menu.on('show hide', function(e) { |
|
137 e.control.items().each(function(ctrl) { |
|
138 ctrl.active(ctrl.value() == self.value()); |
|
139 }); |
|
140 }).fire('show'); |
|
141 |
|
142 self.menu.on('select', function(e) { |
|
143 self.value(e.control.value()); |
|
144 }); |
|
145 |
|
146 self.on('focusin', function(e) { |
|
147 if (e.target.tagName.toUpperCase() == 'INPUT') { |
|
148 self.menu.hide(); |
|
149 } |
|
150 }); |
|
151 |
|
152 self.aria('expanded', true); |
|
153 } |
|
154 |
|
155 self.menu.show(); |
|
156 self.menu.layoutRect({w: self.layoutRect().w}); |
|
157 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); |
|
158 }, |
|
159 |
|
160 /** |
|
161 * Getter/setter function for the control value. |
|
162 * |
|
163 * @method value |
|
164 * @param {String} [value] Value to be set. |
|
165 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. |
|
166 */ |
|
167 value: function(value) { |
|
168 var self = this; |
|
169 |
|
170 if (typeof value != "undefined") { |
|
171 self._value = value; |
|
172 self.removeClass('placeholder'); |
|
173 |
|
174 if (self._rendered) { |
|
175 self.getEl('inp').value = value; |
|
176 } |
|
177 |
|
178 return self; |
|
179 } |
|
180 |
|
181 if (self._rendered) { |
|
182 value = self.getEl('inp').value; |
|
183 |
|
184 if (value != self.settings.placeholder) { |
|
185 return value; |
|
186 } |
|
187 |
|
188 return ''; |
|
189 } |
|
190 |
|
191 return self._value; |
|
192 }, |
|
193 |
|
194 /** |
|
195 * Getter/setter function for the disabled state. |
|
196 * |
|
197 * @method value |
|
198 * @param {Boolean} [state] State to be set. |
|
199 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. |
|
200 */ |
|
201 disabled: function(state) { |
|
202 var self = this; |
|
203 |
|
204 if (self._rendered && typeof state != 'undefined') { |
|
205 self.getEl('inp').disabled = state; |
|
206 } |
|
207 |
|
208 return self._super(state); |
|
209 }, |
|
210 |
|
211 /** |
|
212 * Focuses the input area of the control. |
|
213 * |
|
214 * @method focus |
|
215 */ |
|
216 focus: function() { |
|
217 this.getEl('inp').focus(); |
|
218 }, |
|
219 |
|
220 /** |
|
221 * Repaints the control after a layout operation. |
|
222 * |
|
223 * @method repaint |
|
224 */ |
|
225 repaint: function() { |
|
226 var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); |
|
227 var width, lineHeight; |
|
228 |
|
229 if (openElm) { |
|
230 width = rect.w - DomUtils.getSize(openElm).width - 10; |
|
231 } else { |
|
232 width = rect.w - 10; |
|
233 } |
|
234 |
|
235 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle |
|
236 var doc = document; |
|
237 if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { |
|
238 lineHeight = (self.layoutRect().h - 2) + 'px'; |
|
239 } |
|
240 |
|
241 DomUtils.css(elm.firstChild, { |
|
242 width: width, |
|
243 lineHeight: lineHeight |
|
244 }); |
|
245 |
|
246 self._super(); |
|
247 |
|
248 return self; |
|
249 }, |
|
250 |
|
251 /** |
|
252 * Post render method. Called after the control has been rendered to the target. |
|
253 * |
|
254 * @method postRender |
|
255 * @return {tinymce.ui.ComboBox} Current combobox instance. |
|
256 */ |
|
257 postRender: function() { |
|
258 var self = this; |
|
259 |
|
260 DomUtils.on(this.getEl('inp'), 'change', function() { |
|
261 self.fire('change'); |
|
262 }); |
|
263 |
|
264 return self._super(); |
|
265 }, |
|
266 |
|
267 remove: function() { |
|
268 DomUtils.off(this.getEl('inp')); |
|
269 this._super(); |
|
270 }, |
|
271 |
|
272 /** |
|
273 * Renders the control as a HTML string. |
|
274 * |
|
275 * @method renderHtml |
|
276 * @return {String} HTML representing the control. |
|
277 */ |
|
278 renderHtml: function() { |
|
279 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; |
|
280 var value = settings.value || settings.placeholder || ''; |
|
281 var icon, text, openBtnHtml = '', extraAttrs = ''; |
|
282 |
|
283 if ("spellcheck" in settings) { |
|
284 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; |
|
285 } |
|
286 |
|
287 if (settings.maxLength) { |
|
288 extraAttrs += ' maxlength="' + settings.maxLength + '"'; |
|
289 } |
|
290 |
|
291 if (settings.size) { |
|
292 extraAttrs += ' size="' + settings.size + '"'; |
|
293 } |
|
294 |
|
295 if (settings.subtype) { |
|
296 extraAttrs += ' type="' + settings.subtype + '"'; |
|
297 } |
|
298 |
|
299 if (self.disabled()) { |
|
300 extraAttrs += ' disabled="disabled"'; |
|
301 } |
|
302 |
|
303 icon = settings.icon; |
|
304 if (icon && icon != 'caret') { |
|
305 icon = prefix + 'ico ' + prefix + 'i-' + settings.icon; |
|
306 } |
|
307 |
|
308 text = self._text; |
|
309 |
|
310 if (icon || text) { |
|
311 openBtnHtml = ( |
|
312 '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' + |
|
313 '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' + |
|
314 (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') + |
|
315 (text ? (icon ? ' ' : '') + text : '') + |
|
316 '</button>' + |
|
317 '</div>' |
|
318 ); |
|
319 |
|
320 self.addClass('has-open'); |
|
321 } |
|
322 |
|
323 return ( |
|
324 '<div id="' + id + '" class="' + self.classes() + '">' + |
|
325 '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' + |
|
326 value + '" hidefocus="1"' + extraAttrs + ' />' + |
|
327 openBtnHtml + |
|
328 '</div>' |
|
329 ); |
|
330 } |
|
331 }); |
|
332 }); |