1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates a select element.
 27  * @constructor
 28  * @class
 29  * Widget to replace the native select element.
 30  * <p>
 31  * Note: Currently this does not support multiple selection.
 32  * 
 33  * @param {hash}	params		a hash of parameters
 34  * @param {DwtComposite}      params.parent		the parent widget
 35  * @param {array}      params.options 		a list of options. This can be either an array of {@link DwtSelectOption} or {String} objects.
 36  * @param {string}      params.className		the CSS class
 37  * @param {constant}      params.posStyle		the positioning style (see {@link DwtControl})
 38  * @param {boolean}      [layout=true]		layout to use: DwtMenu.LAYOUT_STACK, DwtMenu.LAYOUT_CASCADE or DwtMenu.LAYOUT_SCROLL. A value of [true] defaults to DwtMenu.LAYOUT_CASCADE and a value of [false] defaults to DwtMenu.LAYOUT_STACK.
 39  *        
 40  * @extends		DwtButton
 41  *
 42  * TODO: add option to keep options sorted by display text
 43  */
 44 DwtSelect = function(params) {
 45 
 46 	if (arguments.length == 0) { return; }
 47 
 48 	params = Dwt.getParams(arguments, DwtSelect.PARAMS);
 49 	params.className = params.className || "ZSelect";
 50 	params.posStyle = params.posStyle || Dwt.STATIC_STYLE;
 51 	this._legendId = params.legendId;
 52     DwtButton.call(this, params);
 53 
 54 	var events = AjxEnv.isIE ? [DwtEvent.ONMOUSEDOWN, DwtEvent.ONMOUSEUP] :
 55 							   [DwtEvent.ONMOUSEDOWN, DwtEvent.ONMOUSEUP, DwtEvent.ONMOUSEOVER, DwtEvent.ONMOUSEOUT];
 56 	this._setEventHdlrs(events);
 57 	this._hasSetMouseEvents = true;
 58 
 59     // initialize some variables
 60     this._currentSelectedOption = null;
 61     this._options = new AjxVector();
 62     this._optionValuesToIndices = {};
 63     this._selectedValue = this._selectedOption = null;
 64 	this._maxRows = params.maxRows || 0;
 65 	this._layout = params.layout;
 66     this._congruent = params.congruent;
 67     this._hrCount = 0;
 68 
 69     // add options
 70     var options = params.options;
 71     if (options) {
 72         for (var i = 0; i < options.length; ++i) {
 73             this.addOption(options[i]);
 74         }
 75     }
 76 
 77     // setup display
 78     this.setDropDownImages("SelectPullDownArrow",			// normal
 79                            "SelectPullDownArrowDis",		// disabled
 80                            "SelectPullDownArrow",			// hover
 81                            "SelectPullDownArrow");			// down
 82 
 83     // add listeners
 84     this._menuCallback = new AjxListener(this, this._createMenu);
 85     this.setMenu(this._menuCallback, true);
 86 };
 87 
 88 DwtSelect.PARAMS = ["parent", "options", "style", "className", "layout"];
 89 
 90 DwtSelect.prototype = new DwtButton;
 91 DwtSelect.prototype.constructor = DwtSelect;
 92 
 93 DwtSelect.prototype.isDwtSelect = true;
 94 DwtSelect.prototype.toString = function() { return "DwtSelect"; };
 95 
 96 DwtSelect.prototype.role = 'combobox';
 97 
 98 //
 99 // Constants
100 //
101 
102 /**
103  * This template is only used for the auto-sizing of the select width.
104  * 
105  * @private
106  */
107 DwtSelect._CONTAINER_TEMPLATE = "dwt.Widgets#ZSelectAutoSizingContainer";
108 
109 //
110 // Data
111 //
112 
113 // static
114 
115 /**
116  * This keeps track of all instances out there
117  * 
118  * @private
119  */
120 DwtSelect._objectIds = [null];
121 
122 // templates
123 
124 DwtSelect.prototype.TEMPLATE = "dwt.Widgets#ZSelect";
125 
126 //
127 // Public methods
128 //
129 
130 // static
131 
132 DwtSelect.getObjectFromElement =
133 function(element) {
134 	return element && element.dwtObj
135 		? AjxCore.objectWithId(element.dwtObj) : null;
136 };
137 
138 // other
139 
140 /**
141  * Adds an option.
142  * 
143  * @param {string|DwtSelectOption|DwtSelectOptionData}		option			a {String} for the option value or the {@link DwtSelectOption} object
144  * @param {boolean}	[selected]		indicates whether option should be the selected option
145  * @param {Object}	value			if the option parameter is a {@link DwtSelectOption}, this will override the value already set in the option.
146  * @param {String}  image	(optional)
147  * @return 	{number} a handle to the newly added option
148  *
149  * TODO: support adding at an index
150  */
151 DwtSelect.prototype.addOption =
152 function(option, selected, value, image) {
153 
154 	if (!option) { return -1; }
155 	image = image || null;
156 
157 	var opt = null;
158 	var val = null;
159     var id = null;
160 	if (typeof(option) == 'string') {
161 		val = value != null ? value : option;
162 		opt = new DwtSelectOption(val, selected, option, this, null, image);
163 	} else {
164 		if (option instanceof DwtSelectOption) {
165 			opt = option;
166 			if (value) {
167 				opt.setValue(value);
168 			}
169 			selected = opt.isSelected();
170 		} else if(option instanceof DwtSelectOptionData || option.value != null) {
171 			val = value != null ? value : option.value;
172 			opt = new DwtSelectOption(val, option.isSelected, option.displayValue, this, null, option.image, option.selectedValue, false, option.extraData, option.id);
173 			selected = Boolean(option.isSelected);
174             id = option.id;
175 		} else {
176 			return -1;
177 		}
178 	}
179 
180 	this._options.add(opt);
181 	if (this._options.size() == 1 || selected) {
182 		this._setSelectedOption(opt);
183 	}
184 
185 	// Insert the option into the table that's below the button.
186 	// This is what gives the button the same size as the select menu.
187 	var table = this._pseudoItemsEl;
188 	var row = table.insertRow(-1);
189 	var cell = row.insertCell(-1);
190 	cell.className = 'ZSelectPseudoItem';
191 	cell.innerHTML = [
192         "<div class='ZWidgetTitle'>",
193             AjxStringUtil.htmlEncode(opt.getDisplayValue()),
194         "</div>"
195     ].join("");
196 
197 	this.fixedButtonWidth(); //good to call always to prevent future bugs due to the vertical space.
198 
199 	// Register listener to create new menu.
200 	this.setMenu(this._menuCallback, true);
201 
202     // return the index of the option.
203     this._optionValuesToIndices[opt.getValue()] = this._options.size() - 1;
204     return (this._options.size() - 1);
205 };
206 
207 DwtSelect.prototype.addHR =
208 function() {
209     opt = new DwtSelectOption("hr" + this._hrCount.toString(), false, "", this, null, null, null, true);
210     this._hrCount++;
211 	this._options.add(opt);
212 };
213 
214 /**
215  * Removes an option.
216  *
217  * @param {DwtSelectOption}		option			option to remove
218  *
219  * @return {number} index of the option that was removed, or -1 if there was an error
220  */
221 DwtSelect.prototype.removeOption =
222 function(option) {
223 
224 	if (!option) { return -1; }
225 
226 	// Register listener to create new menu.
227 	this.setMenu(this._menuCallback, true);
228 
229 	this._options.remove(option);
230 	var size = this._options.size();
231 
232 	var value = option.getValue();
233 	var index = this._optionValuesToIndices[value];
234 	if (index != null) {
235 		this._pseudoItemsEl.deleteRow(index);
236 		if (this._selectedOption == option) {
237 			if (size > 0) {
238 				var newSelIndex = (index >= size) ? size - 1 : index;
239 				this._setSelectedOption(this._options.get(newSelIndex));
240 			}
241 			this.removeAttribute('aria-activedescendant');
242 		}
243 		this.fixedButtonWidth(); //good to call always to prevent future bugs due to the vertical space.
244 	}
245 
246 	delete this._optionValuesToIndices[value];
247 	for (var i = index; i < size; i++) {
248 		var option = this._options.get(i);
249 		this._optionValuesToIndices[option.getValue()] = i;
250 	}
251 
252 	return index;
253 };
254 
255 /**
256  * Removes an option based on its value.
257  *
258  * @param {string}		value			value of the option to remove
259  *
260  * @return {number} index of the option that was removed, or -1 if there was an error
261  */
262 DwtSelect.prototype.removeOptionWithValue =
263 function(value) {
264 
265 	var option = this.getOptionWithValue(value);
266 	return option ? this.removeOption(option) : -1;
267 };
268 
269 DwtSelect.prototype.popup =
270 function() {
271 	var menu = this.getMenu();
272 	if (!menu) { return; }
273 
274 	var selectElement = this._selectEl;
275 	var selectBounds = Dwt.getBounds(selectElement);
276     
277     // since buttons are often absolutely positioned, and menus aren't, we need x,y relative to window
278 	var verticalBorder = (selectElement.style.borderLeftWidth == "") ? 0 : parseInt(selectElement.style.borderLeftWidth);
279 	var horizontalBorder = (selectElement.style.borderTopWidth == "") ? 0 : parseInt(selectElement.style.borderTopWidth);
280 	horizontalBorder += (selectElement.style.borderBottomWidth == "") ? 0 : parseInt(selectElement.style.borderBottomWidth);
281 
282     var selectLocation = Dwt.toWindow(selectElement, 0, 0);
283     var x = selectLocation.x + verticalBorder;
284     var y = selectLocation.y + selectBounds.height + horizontalBorder;
285     menu.popup(0, x, y);
286     if (this._currentSelectedOption) {
287         menu.setSelectedItem(this._currentSelectedOption.getItem());
288     }
289 };
290 
291 /**
292  * Renames an option.
293  *
294  * @param {Object}	value		the value of the option to rename
295  * @param {string}	newValue	the new display value
296  */
297 DwtSelect.prototype.rename =
298 function(value, newValue) {
299 
300 	var option = this.getOptionWithValue(value);
301 	if (!option) { return; }
302 	option._displayValue = newValue;
303 
304 	if (this._selectedOption && (this._selectedOption._value == value))	{
305 		this.setText(AjxStringUtil.htmlEncode(newValue));
306 	}
307 
308 	// Register listener to create new menu.
309 	this.setMenu(this._menuCallback, true);
310 };
311 
312 /**
313  * Enables or disables an option.
314  *
315  * @param {Object}	value		the value of the option to enable/disable
316  * @param {boolean}	enabled		if <code>true</code>, enable the option
317  */
318 DwtSelect.prototype.enableOption =
319 function(value, enabled) {
320 	var option = this.getOptionWithValue(value);
321 	if (!option) { return; }
322 	if (option.enabled != enabled) {
323 		option.enabled = enabled;
324 		var item = option.getItem();
325 		if (item) {
326 			item.setEnabled(enabled);
327 		}
328 	}
329 };
330 
331 /**
332  * Clears the options.
333  * 
334  */
335 DwtSelect.prototype.clearOptions =
336 function() {
337 	var opts = this._options.getArray();
338 	for (var i = 0; i < opts.length; ++i) {
339 		opts[i] = null;
340 	}
341 	this._options.removeAll();
342 	this._optionValuesToIndices = null;
343 	this._optionValuesToIndices = [];
344 	this._selectedValue = null;
345 	this._selectedOption = null;
346 	this._currentSelectedOption = null;
347 	if (this._pseudoItemsEl) {
348 		try {
349 			this._pseudoItemsEl.innerHTML = ""; //bug 81504
350 		}
351 		catch (e) {
352 			//do nothing - this happens in IE for some reason. Stupid IE. "Unknown runtime error".
353 		}
354 	}
355 };
356 
357 /**
358  * Sets the select name.
359  * 
360  * @param	{string}	name		the name
361  */
362 DwtSelect.prototype.setName =
363 function(name) {
364 	this._name = name;
365 };
366 
367 /**
368  * Gets the select name.
369  * 
370  * @return	{string}	the name
371  */
372 DwtSelect.prototype.getName =
373 function() {
374 	return this._name;
375 };
376 
377 /**
378  * Sets the selected value.
379  * 
380  * @param	{Object}	optionValue		the value of the option to select
381  */
382 DwtSelect.prototype.setSelectedValue =
383 function(optionValue) {
384     var index = this._optionValuesToIndices[optionValue];
385     if (index != null) {
386         this.setSelected(index);
387     }
388 };
389 
390 /**
391  * Sets the option as the selected option.
392  * 
393  * @param {number}	optionHandle 	a handle to the option
394  * 
395  * @see		#addOption
396  */
397 DwtSelect.prototype.setSelected =
398 function(optionHandle) {
399     var optionObj = this.getOptionWithHandle(optionHandle);
400 	this.setSelectedOption(optionObj);
401 };
402 
403 /**
404  * Gets the option count.
405  * 
406  * @return	{number}	the option count
407  */
408 DwtSelect.prototype.getOptionCount =
409 function() {
410 	return this._options.size();
411 };
412 
413 /**
414  * Gets the options.
415  * 
416  * @return	{AjxVector}		a vector of {@link DwtSelectOption} objects
417  */
418 DwtSelect.prototype.getOptions =
419 function() {
420 	return this._options;
421 };
422 
423 /**
424  * Gets the option .
425  * 
426  * @param {number}	optionHandle 	a handle to the option
427  * @return	{DwtSelectOption}	the option
428  * @see		#addOption
429  */
430 DwtSelect.prototype.getOptionWithHandle =
431 function(optionHandle) {
432 	return this._options.get(optionHandle);
433 };
434 
435 DwtSelect.prototype.getOptionAtIndex = DwtSelect.prototype.getOptionWithHandle;
436 
437 /**
438  * Gets the index for a given value.
439  * 
440  * @param	{Object}	value		the value
441  * @return	{number}		the index
442  */
443 DwtSelect.prototype.getIndexForValue =
444 function(value) {
445 	return this._optionValuesToIndices[value];
446 };
447 
448 /**
449  * Gets the option for a given value.
450  * 
451  * @param	{Object}	optionValue		the value
452  * @return	{DwtSelectOption}		the option
453  */
454 DwtSelect.prototype.getOptionWithValue =
455 function(optionValue) {
456 	var index = this._optionValuesToIndices[optionValue];
457 	var option = null;
458     if (index != null) {
459         option = this.getOptionWithHandle(index);
460     }
461 	return option;
462 };
463 
464 /**
465  * Sets the selected option.
466  * 
467  * @param	{Object}	optionObj		the object
468  */
469 DwtSelect.prototype.setSelectedOption =
470 function(optionObj) {
471 	if (optionObj) {
472 		this._setSelectedOption(optionObj);
473 	}
474 };
475 
476 /**
477  * Gets the selected value.
478  * 
479  * @return	{Object}	the value
480  */
481 DwtSelect.prototype.getValue =
482 function() {
483     return this._selectedValue;
484 };
485 
486 /**
487  * Gets the selected option.
488  * 
489  * @return	{DwtSelectOption}	the selected option
490  */
491 DwtSelect.prototype.getSelectedOption =
492 function() {
493 	return this._selectedOption;
494 };
495 
496 /**
497  * Gets the selected option index.
498  * 
499  * @return	{number}	the selected option index
500  */
501 DwtSelect.prototype.getSelectedIndex =
502 function() {
503 	return this.getIndexForValue(this.getValue());
504 };
505 
506 /**
507  * Adds a change listener.
508  * 
509  * @param	{AjxListener}	listener		the listener
510  */
511 DwtSelect.prototype.addChangeListener =
512 function(listener) {
513     this.addListener(DwtEvent.ONCHANGE, listener);
514 };
515 
516 /**
517  * Gets the count of options.
518  * 
519  * @return	{number}	the count
520  */
521 DwtSelect.prototype.size =
522 function() {
523 	return this._options.size();
524 };
525 
526 /**
527  * Disables the select.
528  */
529 DwtSelect.prototype.disable =
530 function() {
531 	this.setEnabled(false);
532 };
533 
534 /**
535  * Enables the select.
536  */
537 DwtSelect.prototype.enable =
538 function() {
539 	this.setEnabled(true);
540 };
541 
542 DwtSelect.prototype.setImage =
543 function(imageInfo) {
544 	// dont call DwtButton base class!
545 	DwtLabel.prototype.setImage.call(this, imageInfo);
546 };
547 
548 DwtSelect.prototype.setText =
549 function(text) {
550 	// dont call DwtButton base class!
551 	DwtLabel.prototype.setText.call(this, text);
552 };
553 
554 DwtSelect.prototype.dispose =
555 function() {
556 	this._selectEl = null;
557 	if (this._pseudoItemsEl) {
558 		this._pseudoItemsEl.outerHTML = "";
559 		this._pseudoItemsEl = null;
560 	}
561 	this._containerEl = null;
562 
563 	DwtButton.prototype.dispose.call(this);
564 
565 	if (this._internalObjectId) {
566 		DwtSelect._unassignId(this._internalObjectId);
567 	}
568 };
569 
570 //
571 // Protected methods
572 //
573 
574 // static
575 
576 DwtSelect._assignId =
577 function(anObject) {
578     var myId = DwtSelect._objectIds.length;
579     DwtSelect._objectIds[myId]= anObject;
580     return myId;
581 };
582 
583 DwtSelect._getObjectWithId =
584 function(anId) {
585     return DwtSelect._objectIds[anId];
586 };
587 
588 DwtSelect._unassignId =
589 function(anId) {
590     DwtSelect._objectIds[anId] = null;
591 };
592 
593 // other
594 
595 /* use this in case you want the button to take as little space as needed, and not be aligned with the size of the drop-down.
596 	Especially useful in cases where we mess up the button (remove the text) such as in ZmFreeBusySchedulerView 
597  */
598 DwtSelect.prototype.dynamicButtonWidth = 
599 function() {
600 	this._isDynamicButtonWidth = true; //if this is set, set this so fixedButtonWidth doesn't change this.
601 	this._selectEl.style.width = "auto"; //set to default in case fixedButtonWidth was called before setting it explicitely.
602 	this._pseudoItemsEl.style.display =  "none";
603 };
604 
605 /*
606  * Use this in case you want the select to be as wide as the widest option and
607  * the options hidden so they don't overflow outside containers.
608  */
609 DwtSelect.prototype.fixedButtonWidth =
610 function(){
611 	if (this._isDynamicButtonWidth) {
612 		return;
613 	}
614 	this._pseudoItemsEl.style.display = "block"; //in case this function was called before. This will fix the width of the _selectEl to match the options.
615     var elm = this._selectEl;
616 	var width = elm.offsetWidth;
617 	//offsetWidth is 0 if some parent (ancestor) has display:none which is the case only in Prefs pages when the select is setup.
618 	//don't set width to 0px in this case as it acts inconsistent - filling the entire space. Better to keep it just dynamic.
619 	if (width) {
620 		elm.style.width = width + "px";
621 	}
622     this._pseudoItemsEl.style.display = "none";
623 };
624 
625 DwtSelect.prototype._createHtmlFromTemplate =
626 function(templateId, data) {
627     // wrap params
628     var containerTemplateId = DwtSelect._CONTAINER_TEMPLATE;
629     var containerData = {
630         id: data.id,
631         selectTemplateId: templateId || this.TEMPLATE,
632         selectData: data
633     };
634 
635     // generate html
636     DwtButton.prototype._createHtmlFromTemplate.call(this, containerTemplateId, containerData);
637     this._selectEl = document.getElementById(data.id+"_select_container");
638     this._pseudoItemsEl = document.getElementById(data.id+"_pseudoitems_container");
639 	// this has to be block for it to affect the layout. it is not seen because its visibility hidden for the TDs
640 	// inside, and also "overflow:hidden" (so mouse over the hidden stuff does not highlight)
641 	this._pseudoItemsEl.style.display = "block";
642     // set classes
643     var el = this._containerEl = this.getHtmlElement();
644     this._selectEl.className = el.className;
645     Dwt.addClass(el, "ZSelectAutoSizingContainer");
646     this.removeAttribute("style");
647     if (AjxEnv.isIE && !AjxEnv.isIE9up) {
648         el.style.overflow = "hidden";
649     }
650 	if (this._legendId) {
651 		this.setAttribute('aria-labelledby', [ this._legendId, this._textEl.id ].join(' '));
652 	}
653 };
654 
655 DwtSelect.prototype._createMenu = function() {
656 
657     var menu = new DwtSelectMenu(this);
658     var mi;
659     for (var i = 0, len = this._options.size(); i < len; ++i) {
660 	    var option = this._options.get(i);
661         if (option._hr) {
662             mi = new DwtMenuItem({parent:menu, style:DwtMenuItem.SEPARATOR_STYLE});
663             mi.setEnabled(false);
664         } else {
665             var mi = new DwtSelectMenuItem(menu, option.id || Dwt.getNextId(menu._htmlElId + '_option_'));
666             var image = option.getImage();
667             if (image) {
668                 mi.setImage(image);
669             }
670             var text = option.getDisplayValue();
671             if (text) {
672                 mi.setText(AjxStringUtil.htmlEncode(text));
673             }
674             mi.setEnabled(option.enabled);
675 
676             mi.addSelectionListener(new AjxListener(this, this._handleOptionSelection));
677             mi._optionIndex = i;
678         }
679         mi._optionIndex = i;
680 		option.setItem(mi);
681     }
682 
683 	// Accessibility
684 	var select = this;
685 	menu.addPopupListener(function() {
686 		select.setAttribute('aria-expanded', true);
687 	});
688 	menu.addPopdownListener(function() {
689 		select.setAttribute('aria-expanded', false);
690 		select.removeAttribute('aria-activedescendant');
691 	});
692 
693 	return menu;
694 };
695 
696 DwtSelect.prototype._handleOptionSelection =
697 function(ev) {
698 	var menuItem = ev.item;
699 	var optionIndex = menuItem._optionIndex;
700 	var opt = this._options.get(optionIndex);
701 	var oldValue = this.getValue();
702 	this._setSelectedOption(opt);
703 
704 	// notify our listeners
705     var args = new Object();
706     args.selectObj = this;
707     args.newValue = opt.getValue();
708     args.oldValue = oldValue;
709     var event = DwtUiEvent.getEvent(ev);
710     event._args = args;
711     this.notifyListeners(DwtEvent.ONCHANGE, event);
712 };
713 
714 DwtSelect.prototype._setSelectedOption =
715 function(option) {
716 	var displayValue = option.getSelectedValue() || option.getDisplayValue();
717 	var image = option.getImage();
718 	if (this._selectedOption != option) {
719  		if (displayValue) {
720  			this.setText(AjxStringUtil.htmlEncode(displayValue));
721  		}
722  		this.setImage(image);
723 		this._selectedValue = option._value;
724 		this._selectedOption = option;
725 	}
726     this._updateSelection(option);
727 
728     this.autoResize();
729 };
730 
731 DwtSelect.prototype.autoResize =
732 function() {
733     /* bug: 21041 */
734     var divElId = this.getHtmlElement();
735     AjxTimedAction.scheduleAction(new AjxTimedAction(this,
736         function(){
737             var divEl = document.getElementById(divElId.id);
738             if (divEl) {
739                 divEl.style.width = divEl.childNodes[0].offsetWidth || "auto"; // offsetWidth doesn't work in IE if the element or one of its parents has display:none
740             }
741     }, 200));
742 };
743 
744 DwtSelect.prototype._updateSelection = 
745 function(newOption) {
746 	var currOption = this._currentSelectedOption;
747 
748 	if (currOption) {
749 		currOption.deSelect();
750 	}
751 	this._currentSelectedOption = newOption;
752 	if (!newOption) {
753 		return;
754 	}
755 	newOption.select();
756 	var menu = this.getMenu(true);
757 	if (!menu) {
758 		return;
759 	}
760 	menu.setSelectedItem(newOption.getItem());
761 };
762 
763 // Call this function to update the rendering of the element
764 // Firefox sometimes renders the element incorrectly on certain DOM updates, so this function rectifies that
765 DwtSelect.prototype.updateRendering = 
766 function() {
767 	var scrollStyle = this.getScrollStyle();
768 	this.setScrollStyle(scrollStyle == Dwt.VISIBLE ? Dwt.CLIP : Dwt.VISIBLE);
769 	var reset = function() {
770 					try {
771 						this.setScrollStyle(scrollStyle);
772 					} catch(e) {}
773 				};
774 	var resetAction = new AjxTimedAction(this, reset);
775 	AjxTimedAction.scheduleAction(resetAction, 4);
776 };
777 
778 // Accessibility - select has role of 'combobox', so 'aria-owns' is used instead of 'aria-haspopup'
779 DwtSelect.prototype._menuAdded = function(menu) {
780 	this.setAttribute('aria-owns', menu._htmlElId);
781 };
782 
783 // Accessibility - with a role of 'combobox' we need to maintain 'aria-activedescendant'
784 DwtSelect.prototype._menuItemSelected = function(menuItem) {
785 	this.setAttribute('aria-activedescendant', menuItem._htmlElId);
786 };
787 
788 
789 //
790 // Class
791 //
792 
793 /**
794  * Greg Solovyev 2/2/2004 added this class to be able to create a list of options 
795  * before creating the DwtSelect control. This is a workaround an IE bug, that 
796  * causes IE to crash with error R6025 when DwtSelectOption object are added to empty DwtSelect
797  * @class
798  * @constructor
799  * 
800  * @private
801  */
802 DwtSelectOptionData = function(value, displayValue, isSelected, selectedValue, image, id, extraData) {
803 	if (value == null || displayValue == null) { return null; }
804 
805 	this.value = value;
806 	this.displayValue = displayValue;
807 	this.isSelected = isSelected;
808 	this.selectedValue = selectedValue;
809 	this.image = image;
810 	this.extraData = extraData;
811     this.id = id || Dwt.getNextId();
812 };
813 
814 //
815 // Class
816 //
817 
818 /**
819  * Creates a select option.
820  * @constructor
821  * @class
822  * This class encapsulates the option object that the {@link DwtSelect} widget uses. 
823  *
824  * @param {String}	value this is the value for the object, it will be returned in any onchange event
825  * @param {Boolean}	selected whether or not the option should be selected to start with
826  * @param {String}	displayValue the value that the user will see (HTML encoding will be done on this value internally)
827  * @param {DwtSelect}	owner 	not used
828  * @param {String}	optionalDOMId		not used
829  * @param {String}	[selectedValue] 	the text value to use when this value is the currently selected value
830  * @param {Boolean}	hr                  True => This option will be usd to create a unselectable horizontal rule
831  * @param {Object} extraData  map of extra name/value pairs
832  */
833 DwtSelectOption = function(value, selected, displayValue, owner, optionalDOMId, image, selectedValue, hr, extraData, id) {
834 	this._value = value;
835 	this._selected = selected;
836 	this._displayValue = displayValue;
837 	this._image = image;
838 	this._selectedValue = selectedValue;
839     this._hr = hr;
840 	this._extraData = extraData;
841 
842 	this.id = id;
843 
844 	this._internalObjectId = DwtSelect._assignId(this);
845 	this.enabled = true;
846 };
847 
848 DwtSelectOption.prototype.toString =
849 function() {
850     return "DwtSelectOption";
851 };
852 
853 /**
854  * Sets the item.
855  * 
856  * @param	{DwtSelectMenuItem}	menuItem		the menu item
857  */
858 DwtSelectOption.prototype.setItem = 
859 function(menuItem) {
860 	this._menuItem = menuItem;
861 };
862 
863 /**
864  * Gets the item.
865  * 
866  * @return	{DwtSelectMenuItem}	the menu item
867  */
868 DwtSelectOption.prototype.getItem = 
869 function(menuItem) {
870 	return this._menuItem;
871 };
872 
873 /**
874  * Gets the display value.
875  * 
876  * @return	{String}	the display value
877  */
878 DwtSelectOption.prototype.getDisplayValue = 
879 function() {
880 	return this._displayValue;
881 };
882 
883 /**
884  * Gets the image.
885  * 
886  * @return	{String}	the image
887  */
888 DwtSelectOption.prototype.getImage = 
889 function() {
890 	return this._image;
891 };
892 
893 /**
894  * Gets the selected value.
895  * 
896  * @return	{String}	the selected value
897  */
898 DwtSelectOption.prototype.getSelectedValue =
899 function() {
900 	return this._selectedValue;
901 };
902 
903 /**
904  * Gets the value.
905  * 
906  * @return	{String}	the value
907  */
908 DwtSelectOption.prototype.getValue = 
909 function() {
910 	return this._value;
911 };
912 
913 /**
914  * Sets the value.
915  * 
916  * @param	{String|Number}	stringOrNumber	the value
917  */
918 DwtSelectOption.prototype.setValue = 
919 function(stringOrNumber) {
920 	this._value = stringOrNumber;
921 };
922 
923 /**
924  * Selects the option.
925  */
926 DwtSelectOption.prototype.select = 
927 function() {
928 	this._selected = true;
929 };
930 
931 /**
932  * De-selects the option.
933  */
934 DwtSelectOption.prototype.deSelect = 
935 function() {
936 	this._selected = false;
937 };
938 
939 /**
940  * Checks if the option is selected.
941  * 
942  * @return	{Boolean}	<code>true</code> if the option is selected
943  */
944 DwtSelectOption.prototype.isSelected = 
945 function() {
946 	return this._selected;
947 };
948 
949 /**
950  * Gets the id.
951  * 
952  * @return	{String}	the id
953  */
954 DwtSelectOption.prototype.getIdentifier = 
955 function() {
956 	return this._internalObjectId;
957 };
958 
959 DwtSelectOption.prototype.getExtraData =
960 function(key) {
961 	return this._extraData && this._extraData[key];
962 };
963 
964 
965 
966 /**
967  * Creates a select menu.
968  * @constructor
969  * @class
970  * This class represents a select menu.
971  * 
972  * @param	{DwtComposite}	parent		the parent
973  * 
974  * @extends		DwtMenu
975  */
976 DwtSelectMenu = function(parent) {
977     DwtMenu.call(this, {parent:parent, style:DwtMenu.DROPDOWN_STYLE, className:"DwtMenu", layout:parent._layout,
978         maxRows:parent._maxRows, congruent:parent._congruent,
979         id:Dwt.getNextId(parent.getHTMLElId() + "_Menu_")});
980 // Dwt.getNextId should be removed once Bug 66510 is fixed
981 };
982 DwtSelectMenu.prototype = new DwtMenu;
983 DwtSelectMenu.prototype.constructor = DwtSelectMenu;
984 
985 DwtSelectMenu.prototype.TEMPLATE = "dwt.Widgets#ZSelectMenu";
986 
987 DwtSelectMenu.prototype.toString =
988 function() {
989     return "DwtSelectMenu";
990 };
991 
992 /**
993  * Creates a select menu item.
994  * @constructor
995  * @class
996  * This class represents a menu item.
997  * 
998  * @param	{DwtComposite}	parent		the parent
999  * 
1000  * @extends 	DwtMenuItem
1001  */
1002 DwtSelectMenuItem = function(parent, id) {
1003     DwtMenuItem.call(this, {parent:parent, style:DwtMenuItem.SELECT_STYLE, className:"ZSelectMenuItem", id: id});
1004 };
1005 DwtSelectMenuItem.prototype = new DwtMenuItem;
1006 DwtSelectMenuItem.prototype.constructor = DwtSelectMenuItem;
1007 
1008 DwtSelectMenuItem.prototype.TEMPLATE = "dwt.Widgets#ZSelectMenuItem";
1009 
1010 DwtSelectMenuItem.prototype.isDwtSelectMenuItem = true;
1011 DwtSelectMenuItem.prototype.toString = function() { return "DwtSelectMenuItem"; };
1012 
1013 DwtSelectMenuItem.prototype.role = 'option';
1014 
1015 DwtLabel.prototype._textSet = function(text) {};
1016