4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
15 * This class is used to create list boxes/select list. This one will generate
16 * a non native control. This one has the benefits of having visual items added.
18 * @class tinymce.ui.ListBox
19 * @extends tinymce.ui.Control
21 * // Creates a new plugin class and a custom listbox
22 * tinymce.create('tinymce.plugins.ExamplePlugin', {
23 * createControl: function(n, cm) {
26 * var mlb = cm.createListBox('mylistbox', {
27 * title : 'My list box',
28 * onselect : function(v) {
29 * tinyMCE.activeEditor.windowManager.alert('Value selected:' + v);
33 * // Add some values to the list box
34 * mlb.add('Some item 1', 'val1');
35 * mlb.add('some item 2', 'val2');
36 * mlb.add('some item 3', 'val3');
38 * // Return the new listbox instance
46 * // Register plugin with a short name
47 * tinymce.PluginManager.add('example', tinymce.plugins.ExamplePlugin);
49 * // Initialize TinyMCE with the new plugin and button
52 * plugins : '-example', // - means TinyMCE will not try to load it
53 * theme_advanced_buttons1 : 'mylistbox' // Add the new example listbox to the toolbar
56 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
58 * Constructs a new listbox control instance.
62 * @param {String} id Control id for the list box.
63 * @param {Object} s Optional name/value settings object.
64 * @param {Editor} ed Optional the editor instance this button is for.
66 ListBox : function(id, s, ed) {
72 * Array of ListBox items.
80 * Fires when the selection has been changed.
84 t.onChange = new Dispatcher(t);
87 * Fires after the element has been rendered to DOM.
91 t.onPostRender = new Dispatcher(t);
94 * Fires when a new item is added.
98 t.onAdd = new Dispatcher(t);
101 * Fires when the menu gets rendered.
103 * @event onRenderMenu
105 t.onRenderMenu = new tinymce.util.Dispatcher(this);
107 t.classPrefix = 'mceListBox';
111 * Selects a item/option by value. This will both add a visual selection to the
112 * item and change the title of the control to the title of the option.
115 * @param {String/function} va Value to look for inside the list box or a function selector.
117 select : function(va) {
121 return t.selectByIndex(-1);
123 // Is string or number make function selector
132 // Do we need to do something?
133 if (va != t.selectedValue) {
135 each(t.items, function(o, i) {
149 * Selects a item/option by index. This will both add a visual selection to the
150 * item and change the title of the control to the title of the option.
152 * @method selectByIndex
153 * @param {String} idx Index to select, pass -1 to select menu/title of select box.
155 selectByIndex : function(idx) {
158 if (idx != t.selectedIndex) {
159 e = DOM.get(t.id + '_text');
163 t.selectedValue = o.value;
164 t.selectedIndex = idx;
165 DOM.setHTML(e, DOM.encode(o.title));
166 DOM.removeClass(e, 'mceTitle');
167 DOM.setAttrib(t.id, 'aria-valuenow', o.title);
169 DOM.setHTML(e, DOM.encode(t.settings.title));
170 DOM.addClass(e, 'mceTitle');
171 t.selectedValue = t.selectedIndex = null;
172 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
179 * Adds a option item to the list box.
182 * @param {String} n Title for the new option.
183 * @param {String} v Value for the new option.
184 * @param {Object} o Optional object with settings like for example class.
186 add : function(n, v, o) {
190 o = tinymce.extend(o, {
196 t.onAdd.dispatch(t, o);
200 * Returns the number of items inside the list box.
203 * @param {Number} Number of items inside the list box.
205 getLength : function() {
206 return this.items.length;
210 * Renders the list box as a HTML string. This method is much faster than using the DOM and when
211 * creating a whole toolbar with buttons it does make a lot of difference.
214 * @return {String} HTML for the list box control element.
216 renderHTML : function() {
217 var h = '', t = this, s = t.settings, cp = t.classPrefix;
219 h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
220 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
221 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
222 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
223 h += '</tr></tbody></table></span>';
229 * Displays the drop menu with all items.
233 showMenu : function() {
234 var t = this, p2, e = DOM.get(this.id), m;
236 if (t.isDisabled() || t.items.length == 0)
239 if (t.menu && t.menu.isMenuVisible)
242 if (!t.isMenuRendered) {
244 t.isMenuRendered = true;
250 m.settings.offset_x = p2.x;
251 m.settings.offset_y = p2.y;
252 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
256 m.items[t.oldID].setSelected(0);
258 each(t.items, function(o) {
259 if (o.value === t.selectedValue) {
260 m.items[o.id].setSelected(1);
265 m.showMenu(0, e.clientHeight);
267 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
268 DOM.addClass(t.id, t.classPrefix + 'Selected');
270 //DOM.get(t.id + '_text').focus();
274 * Hides the drop menu.
278 hideMenu : function(e) {
281 if (t.menu && t.menu.isMenuVisible) {
282 DOM.removeClass(t.id, t.classPrefix + 'Selected');
284 // Prevent double toogles by canceling the mouse click event to the button
285 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
288 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
289 DOM.removeClass(t.id, t.classPrefix + 'Selected');
290 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
297 * Renders the menu to the DOM.
301 renderMenu : function() {
304 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
306 'class' : t.classPrefix + 'Menu mceNoIcons',
311 m.onHideMenu.add(function() {
317 title : t.settings.title,
318 'class' : 'mceMenuItemTitle',
319 onclick : function() {
320 if (t.settings.onselect('') !== false)
321 t.select(''); // Must be runned after
325 each(t.items, function(o) {
326 // No value then treat it as a title
327 if (o.value === undefined) {
330 'class' : 'mceMenuItemTitle',
331 onclick : function() {
332 if (t.settings.onselect('') !== false)
333 t.select(''); // Must be runned after
337 o.id = DOM.uniqueId();
338 o.onclick = function() {
339 if (t.settings.onselect(o.value) !== false)
340 t.select(o.value); // Must be runned after
347 t.onRenderMenu.dispatch(t, m);
352 * Post render event. This will be executed after the control has been rendered and can be used to
353 * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
357 postRender : function() {
358 var t = this, cp = t.classPrefix;
360 Event.add(t.id, 'click', t.showMenu, t);
361 Event.add(t.id, 'keydown', function(evt) {
362 if (evt.keyCode == 32) { // Space
367 Event.add(t.id, 'focus', function() {
369 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
370 if (e.keyCode == 40) {
375 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
377 if (e.keyCode == 13) {
378 // Fake select on enter
380 t.selectedValue = null; // Needs to be null to fake change
382 t.settings.onselect(v);
389 Event.add(t.id, 'blur', function() {
390 Event.remove(t.id, 'keydown', t.keyDownHandler);
391 Event.remove(t.id, 'keypress', t.keyPressHandler);
395 // Old IE doesn't have hover on all elements
396 if (tinymce.isIE6 || !DOM.boxModel) {
397 Event.add(t.id, 'mouseover', function() {
398 if (!DOM.hasClass(t.id, cp + 'Disabled'))
399 DOM.addClass(t.id, cp + 'Hover');
402 Event.add(t.id, 'mouseout', function() {
403 if (!DOM.hasClass(t.id, cp + 'Disabled'))
404 DOM.removeClass(t.id, cp + 'Hover');
408 t.onPostRender.dispatch(t, DOM.get(t.id));
412 * Destroys the ListBox i.e. clear memory and events.
416 destroy : function() {
419 Event.clear(this.id + '_text');
420 Event.clear(this.id + '_open');