Bumped the version up to 1.9.3
[moodle.git] / lib / yui / editor / simpleeditor.js
blob07e83c6cb7e64a5b1f437dcf98f7fc1679c8bda8
1 /*
2 Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.6.0
6 */
7 (function() {
8     /**
9     * @private
10     **/
11 var Dom = YAHOO.util.Dom,
12     Event = YAHOO.util.Event,
13     Lang = YAHOO.lang;
14     /**
15      * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
16      * @class ToolbarButtonAdvanced
17      * @namespace YAHOO.widget
18      * @requires yahoo, dom, element, event, container_core, menu, button
19      * @beta
20      * 
21      * Provides a toolbar button based on the button and menu widgets.
22      * @constructor
23      * @param {String/HTMLElement} el The element to turn into a button.
24      * @param {Object} attrs Object liternal containing configuration parameters.
25     */
26     if (YAHOO.widget.Button) {
27         YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button;
28         /**
29         * @property buttonType
30         * @private
31         * @description Tells if the Button is a Rich Button or a Simple Button
32         */
33         YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich';
34         /**
35         * @method checkValue
36         * @param {String} value The value of the option that we want to mark as selected
37         * @description Select an option by value
38         */
39         YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) {
40             var _menuItems = this.getMenu().getItems();
41             if (_menuItems.length === 0) {
42                 this.getMenu()._onBeforeShow();
43                 _menuItems = this.getMenu().getItems();
44             }
45             for (var i = 0; i < _menuItems.length; i++) {
46                 _menuItems[i].cfg.setProperty('checked', false);
47                 if (_menuItems[i].value == value) {
48                     _menuItems[i].cfg.setProperty('checked', true);
49                 }
50             }      
51         };
52     } else {
53         YAHOO.widget.ToolbarButtonAdvanced = function() {};
54     }
57     /**
58      * @description <p>Creates a basic custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
59      * @class ToolbarButton
60      * @namespace YAHOO.widget
61      * @requires yahoo, dom, element, event
62      * @Extends YAHOO.util.Element
63      * @beta
64      * 
65      * Provides a toolbar button based on the button and menu widgets, <select> elements are used in place of menu's.
66      * @constructor
67      * @param {String/HTMLElement} el The element to turn into a button.
68      * @param {Object} attrs Object liternal containing configuration parameters.
69     */
71     YAHOO.widget.ToolbarButton = function(el, attrs) {
72         
73         if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
74             attrs = el;
75         }
76         var local_attrs = (attrs || {});
78         var oConfig = {
79             element: null,
80             attributes: local_attrs
81         };
83         if (!oConfig.attributes.type) {
84             oConfig.attributes.type = 'push';
85         }
86         
87         oConfig.element = document.createElement('span');
88         oConfig.element.setAttribute('unselectable', 'on');
89         oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
90         oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
91         oConfig.element.firstChild.firstChild.tabIndex = '-1';
92         oConfig.attributes.id = Dom.generateId();
94         YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
95     };
97     YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
98         /**
99         * @property buttonType
100         * @private
101         * @description Tells if the Button is a Rich Button or a Simple Button
102         */
103         buttonType: 'normal',
104         /**
105         * @method _handleMouseOver
106         * @private
107         * @description Adds classes to the button elements on mouseover (hover)
108         */
109         _handleMouseOver: function() {
110             if (!this.get('disabled')) {
111                 this.addClass('yui-button-hover');
112                 this.addClass('yui-' + this.get('type') + '-button-hover');
113             }
114         },
115         /**
116         * @method _handleMouseOut
117         * @private
118         * @description Removes classes from the button elements on mouseout (hover)
119         */
120         _handleMouseOut: function() {
121             this.removeClass('yui-button-hover');
122             this.removeClass('yui-' + this.get('type') + '-button-hover');
123         },
124         /**
125         * @method checkValue
126         * @param {String} value The value of the option that we want to mark as selected
127         * @description Select an option by value
128         */
129         checkValue: function(value) {
130             if (this.get('type') == 'menu') {
131                 var opts = this._button.options;
132                 for (var i = 0; i < opts.length; i++) {
133                     if (opts[i].value == value) {
134                         opts.selectedIndex = i;
135                     }
136                 }
137             }
138         },
139         /** 
140         * @method init
141         * @description The ToolbarButton class's initialization method
142         */        
143         init: function(p_oElement, p_oAttributes) {
144             YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
146             this.on('mouseover', this._handleMouseOver, this, true);
147             this.on('mouseout', this._handleMouseOut, this, true);
148         },
149         /**
150         * @method initAttributes
151         * @description Initializes all of the configuration attributes used to create 
152         * the toolbar.
153         * @param {Object} attr Object literal specifying a set of 
154         * configuration attributes used to create the toolbar.
155         */        
156         initAttributes: function(attr) {
157             YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
158             /**
159             * @attribute value
160             * @description The value of the button
161             * @type String
162             */            
163             this.setAttributeConfig('value', {
164                 value: attr.value
165             });
166             /**
167             * @attribute menu
168             * @description The menu attribute, see YAHOO.widget.Button
169             * @type Object
170             */            
171             this.setAttributeConfig('menu', {
172                 value: attr.menu || false
173             });
174             /**
175             * @attribute type
176             * @description The type of button to create: push, menu, color, select, spin
177             * @type String
178             */            
179             this.setAttributeConfig('type', {
180                 value: attr.type,
181                 writeOnce: true,
182                 method: function(type) {
183                     var el, opt;
184                     if (!this._button) {
185                         this._button = this.get('element').getElementsByTagName('a')[0];
186                     }
187                     switch (type) {
188                         case 'select':
189                         case 'menu':
190                             el = document.createElement('select');
191                             var menu = this.get('menu');
192                             for (var i = 0; i < menu.length; i++) {
193                                 opt = document.createElement('option');
194                                 opt.innerHTML = menu[i].text;
195                                 opt.value = menu[i].value;
196                                 if (menu[i].checked) {
197                                     opt.selected = true;
198                                 }
199                                 el.appendChild(opt);
200                             }
201                             this._button.parentNode.replaceChild(el, this._button);
202                             Event.on(el, 'change', this._handleSelect, this, true);
203                             this._button = el;
204                             break;
205                     }
206                 }
207             });
209             /**
210             * @attribute disabled
211             * @description Set the button into a disabled state
212             * @type String
213             */            
214             this.setAttributeConfig('disabled', {
215                 value: attr.disabled || false,
216                 method: function(disabled) {
217                     if (disabled) {
218                         this.addClass('yui-button-disabled');
219                         this.addClass('yui-' + this.get('type') + '-button-disabled');
220                     } else {
221                         this.removeClass('yui-button-disabled');
222                         this.removeClass('yui-' + this.get('type') + '-button-disabled');
223                     }
224                     if (this.get('type') == 'menu') {
225                         this._button.disabled = disabled;
226                     }
227                 }
228             });
230             /**
231             * @attribute label
232             * @description The text label for the button
233             * @type String
234             */            
235             this.setAttributeConfig('label', {
236                 value: attr.label,
237                 method: function(label) {
238                     if (!this._button) {
239                         this._button = this.get('element').getElementsByTagName('a')[0];
240                     }
241                     if (this.get('type') == 'push') {
242                         this._button.innerHTML = label;
243                     }
244                 }
245             });
247             /**
248             * @attribute title
249             * @description The title of the button
250             * @type String
251             */            
252             this.setAttributeConfig('title', {
253                 value: attr.title
254             });
256             /**
257             * @config container
258             * @description The container that the button is rendered to, handled by Toolbar
259             * @type String
260             */            
261             this.setAttributeConfig('container', {
262                 value: null,
263                 writeOnce: true,
264                 method: function(cont) {
265                     this.appendTo(cont);
266                 }
267             });
269         },
270         /** 
271         * @private
272         * @method _handleSelect
273         * @description The event fired when a change event gets fired on a select element
274         * @param {Event} ev The change event.
275         */        
276         _handleSelect: function(ev) {
277             var tar = Event.getTarget(ev);
278             var value = tar.options[tar.selectedIndex].value;
279             this.fireEvent('change', {type: 'change', value: value });
280         },
281         /** 
282         * @method getMenu
283         * @description A stub function to mimic YAHOO.widget.Button's getMenu method
284         */        
285         getMenu: function() {
286             return this.get('menu');
287         },
288         /** 
289         * @method destroy
290         * @description Destroy the button
291         */        
292         destroy: function() {
293             Event.purgeElement(this.get('element'), true);
294             this.get('element').parentNode.removeChild(this.get('element'));
295             //Brutal Object Destroy
296             for (var i in this) {
297                 if (Lang.hasOwnProperty(this, i)) {
298                     this[i] = null;
299                 }
300             }       
301         },
302         /** 
303         * @method fireEvent
304         * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
305         */        
306         fireEvent: function (p_sType , p_aArgs) {
307             //  Disabled buttons should not respond to DOM events
308             if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
309                 return;
310             }
311         
312             YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
313         },
314         /**
315         * @method toString
316         * @description Returns a string representing the toolbar.
317         * @return {String}
318         */        
319         toString: function() {
320             return 'ToolbarButton (' + this.get('id') + ')';
321         }
322         
323     });
324 })();
326  * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
327  * @namespace YAHOO.widget
328  * @requires yahoo, dom, element, event, toolbarbutton
329  * @optional container_core, dragdrop
330  * @beta
331  */
332 (function() {
333     /**
334     * @private
335     **/
336 var Dom = YAHOO.util.Dom,
337     Event = YAHOO.util.Event,
338     Lang = YAHOO.lang;
339     
340     var getButton = function(id) {
341         var button = id;
342         if (Lang.isString(id)) {
343             button = this.getButtonById(id);
344         }
345         if (Lang.isNumber(id)) {
346             button = this.getButtonByIndex(id);
347         }
348         if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
349             button = this.getButtonByValue(id);
350         }
351         if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
352             return button;
353         }
354         return false;
355     };
357     /**
358      * Provides a rich toolbar widget based on the button and menu widgets
359      * @constructor
360      * @class Toolbar
361      * @extends YAHOO.util.Element
362      * @param {String/HTMLElement} el The element to turn into a toolbar.
363      * @param {Object} attrs Object liternal containing configuration parameters.
364     */
365     YAHOO.widget.Toolbar = function(el, attrs) {
366         
367         if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
368             attrs = el;
369         }
370         var local_attrs = {};
371         if (attrs) {
372             Lang.augmentObject(local_attrs, attrs); //Break the config reference
373         }
374         
376         var oConfig = {
377             element: null,
378             attributes: local_attrs
379         };
380         
381         
382         if (Lang.isString(el) && Dom.get(el)) {
383             oConfig.element = Dom.get(el);
384         } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {  
385             oConfig.element = Dom.get(el);
386         }
387         
389         if (!oConfig.element) {
390             oConfig.element = document.createElement('DIV');
391             oConfig.element.id = Dom.generateId();
392             
393             if (local_attrs.container && Dom.get(local_attrs.container)) {
394                 Dom.get(local_attrs.container).appendChild(oConfig.element);
395             }
396         }
397         
399         if (!oConfig.element.id) {
400             oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
401         }
402         
403         var fs = document.createElement('fieldset');
404         var lg = document.createElement('legend');
405         lg.innerHTML = 'Toolbar';
406         fs.appendChild(lg);
407         
408         var cont = document.createElement('DIV');
409         oConfig.attributes.cont = cont;
410         Dom.addClass(cont, 'yui-toolbar-subcont');
411         fs.appendChild(cont);
412         oConfig.element.appendChild(fs);
414         oConfig.element.tabIndex = -1;
416         
417         oConfig.attributes.element = oConfig.element;
418         oConfig.attributes.id = oConfig.element.id;
420         YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
421          
422     };
424     YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
425         /**
426         * @method _addMenuClasses
427         * @private
428         * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
429         * @param {String} ev The event that fired.
430         * @param {Array} na Array of event information.
431         * @param {Object} o Button config object. 
432         */
433         _addMenuClasses: function(ev, na, o) {
434             Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
435             if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
436                 Dom.addClass(this.element, 'yui-toolbar-select-menu');
437             }
438             var items = this.getItems();
439             for (var i = 0; i < items.length; i++) {
440                 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-').toLowerCase() : items[i]._oText.nodeValue.replace(/ /g, '-').toLowerCase()));
441                 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
442             }
443         },
444         /** 
445         * @property buttonType
446         * @description The default button to use
447         * @type Object
448         */
449         buttonType: YAHOO.widget.ToolbarButton,
450         /** 
451         * @property dd
452         * @description The DragDrop instance associated with the Toolbar
453         * @type Object
454         */
455         dd: null,
456         /** 
457         * @property _colorData
458         * @description Object reference containing colors hex and text values.
459         * @type Object
460         */
461         _colorData: {
462 /* {{{ _colorData */
463     '#111111': 'Obsidian',
464     '#2D2D2D': 'Dark Gray',
465     '#434343': 'Shale',
466     '#5B5B5B': 'Flint',
467     '#737373': 'Gray',
468     '#8B8B8B': 'Concrete',
469     '#A2A2A2': 'Gray',
470     '#B9B9B9': 'Titanium',
471     '#000000': 'Black',
472     '#D0D0D0': 'Light Gray',
473     '#E6E6E6': 'Silver',
474     '#FFFFFF': 'White',
475     '#BFBF00': 'Pumpkin',
476     '#FFFF00': 'Yellow',
477     '#FFFF40': 'Banana',
478     '#FFFF80': 'Pale Yellow',
479     '#FFFFBF': 'Butter',
480     '#525330': 'Raw Siena',
481     '#898A49': 'Mildew',
482     '#AEA945': 'Olive',
483     '#7F7F00': 'Paprika',
484     '#C3BE71': 'Earth',
485     '#E0DCAA': 'Khaki',
486     '#FCFAE1': 'Cream',
487     '#60BF00': 'Cactus',
488     '#80FF00': 'Chartreuse',
489     '#A0FF40': 'Green',
490     '#C0FF80': 'Pale Lime',
491     '#DFFFBF': 'Light Mint',
492     '#3B5738': 'Green',
493     '#668F5A': 'Lime Gray',
494     '#7F9757': 'Yellow',
495     '#407F00': 'Clover',
496     '#8A9B55': 'Pistachio',
497     '#B7C296': 'Light Jade',
498     '#E6EBD5': 'Breakwater',
499     '#00BF00': 'Spring Frost',
500     '#00FF80': 'Pastel Green',
501     '#40FFA0': 'Light Emerald',
502     '#80FFC0': 'Sea Foam',
503     '#BFFFDF': 'Sea Mist',
504     '#033D21': 'Dark Forrest',
505     '#438059': 'Moss',
506     '#7FA37C': 'Medium Green',
507     '#007F40': 'Pine',
508     '#8DAE94': 'Yellow Gray Green',
509     '#ACC6B5': 'Aqua Lung',
510     '#DDEBE2': 'Sea Vapor',
511     '#00BFBF': 'Fog',
512     '#00FFFF': 'Cyan',
513     '#40FFFF': 'Turquoise Blue',
514     '#80FFFF': 'Light Aqua',
515     '#BFFFFF': 'Pale Cyan',
516     '#033D3D': 'Dark Teal',
517     '#347D7E': 'Gray Turquoise',
518     '#609A9F': 'Green Blue',
519     '#007F7F': 'Seaweed',
520     '#96BDC4': 'Green Gray',
521     '#B5D1D7': 'Soapstone',
522     '#E2F1F4': 'Light Turquoise',
523     '#0060BF': 'Summer Sky',
524     '#0080FF': 'Sky Blue',
525     '#40A0FF': 'Electric Blue',
526     '#80C0FF': 'Light Azure',
527     '#BFDFFF': 'Ice Blue',
528     '#1B2C48': 'Navy',
529     '#385376': 'Biscay',
530     '#57708F': 'Dusty Blue',
531     '#00407F': 'Sea Blue',
532     '#7792AC': 'Sky Blue Gray',
533     '#A8BED1': 'Morning Sky',
534     '#DEEBF6': 'Vapor',
535     '#0000BF': 'Deep Blue',
536     '#0000FF': 'Blue',
537     '#4040FF': 'Cerulean Blue',
538     '#8080FF': 'Evening Blue',
539     '#BFBFFF': 'Light Blue',
540     '#212143': 'Deep Indigo',
541     '#373E68': 'Sea Blue',
542     '#444F75': 'Night Blue',
543     '#00007F': 'Indigo Blue',
544     '#585E82': 'Dockside',
545     '#8687A4': 'Blue Gray',
546     '#D2D1E1': 'Light Blue Gray',
547     '#6000BF': 'Neon Violet',
548     '#8000FF': 'Blue Violet',
549     '#A040FF': 'Violet Purple',
550     '#C080FF': 'Violet Dusk',
551     '#DFBFFF': 'Pale Lavender',
552     '#302449': 'Cool Shale',
553     '#54466F': 'Dark Indigo',
554     '#655A7F': 'Dark Violet',
555     '#40007F': 'Violet',
556     '#726284': 'Smoky Violet',
557     '#9E8FA9': 'Slate Gray',
558     '#DCD1DF': 'Violet White',
559     '#BF00BF': 'Royal Violet',
560     '#FF00FF': 'Fuchsia',
561     '#FF40FF': 'Magenta',
562     '#FF80FF': 'Orchid',
563     '#FFBFFF': 'Pale Magenta',
564     '#4A234A': 'Dark Purple',
565     '#794A72': 'Medium Purple',
566     '#936386': 'Cool Granite',
567     '#7F007F': 'Purple',
568     '#9D7292': 'Purple Moon',
569     '#C0A0B6': 'Pale Purple',
570     '#ECDAE5': 'Pink Cloud',
571     '#BF005F': 'Hot Pink',
572     '#FF007F': 'Deep Pink',
573     '#FF409F': 'Grape',
574     '#FF80BF': 'Electric Pink',
575     '#FFBFDF': 'Pink',
576     '#451528': 'Purple Red',
577     '#823857': 'Purple Dino',
578     '#A94A76': 'Purple Gray',
579     '#7F003F': 'Rose',
580     '#BC6F95': 'Antique Mauve',
581     '#D8A5BB': 'Cool Marble',
582     '#F7DDE9': 'Pink Granite',
583     '#C00000': 'Apple',
584     '#FF0000': 'Fire Truck',
585     '#FF4040': 'Pale Red',
586     '#FF8080': 'Salmon',
587     '#FFC0C0': 'Warm Pink',
588     '#441415': 'Sepia',
589     '#82393C': 'Rust',
590     '#AA4D4E': 'Brick',
591     '#800000': 'Brick Red',
592     '#BC6E6E': 'Mauve',
593     '#D8A3A4': 'Shrimp Pink',
594     '#F8DDDD': 'Shell Pink',
595     '#BF5F00': 'Dark Orange',
596     '#FF7F00': 'Orange',
597     '#FF9F40': 'Grapefruit',
598     '#FFBF80': 'Canteloupe',
599     '#FFDFBF': 'Wax',
600     '#482C1B': 'Dark Brick',
601     '#855A40': 'Dirt',
602     '#B27C51': 'Tan',
603     '#7F3F00': 'Nutmeg',
604     '#C49B71': 'Mustard',
605     '#E1C4A8': 'Pale Tan',
606     '#FDEEE0': 'Marble'
607 /* }}} */
608         },
609         /** 
610         * @property _colorPicker
611         * @description The HTML Element containing the colorPicker
612         * @type HTMLElement
613         */
614         _colorPicker: null,
615         /** 
616         * @property STR_COLLAPSE
617         * @description String for Toolbar Collapse Button
618         * @type String
619         */
620         STR_COLLAPSE: 'Collapse Toolbar',
621         /** 
622         * @property STR_SPIN_LABEL
623         * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
624         * @type String
625         */
626         STR_SPIN_LABEL: 'Spin Button with value {VALUE}. Use Control Shift Up Arrow and Control Shift Down arrow keys to increase or decrease the value.',
627         /** 
628         * @property STR_SPIN_UP
629         * @description String for spinbutton up
630         * @type String
631         */
632         STR_SPIN_UP: 'Click to increase the value of this input',
633         /** 
634         * @property STR_SPIN_DOWN
635         * @description String for spinbutton down
636         * @type String
637         */
638         STR_SPIN_DOWN: 'Click to decrease the value of this input',
639         /** 
640         * @property _titlebar
641         * @description Object reference to the titlebar
642         * @type HTMLElement
643         */
644         _titlebar: null,
645         /** 
646         * @property browser
647         * @description Standard browser detection
648         * @type Object
649         */
650         browser: YAHOO.env.ua,
651         /**
652         * @protected
653         * @property _buttonList
654         * @description Internal property list of current buttons in the toolbar
655         * @type Array
656         */
657         _buttonList: null,
658         /**
659         * @protected
660         * @property _buttonGroupList
661         * @description Internal property list of current button groups in the toolbar
662         * @type Array
663         */
664         _buttonGroupList: null,
665         /**
666         * @protected
667         * @property _sep
668         * @description Internal reference to the separator HTML Element for cloning
669         * @type HTMLElement
670         */
671         _sep: null,
672         /**
673         * @protected
674         * @property _sepCount
675         * @description Internal refernce for counting separators, so we can give them a useful class name for styling
676         * @type Number
677         */
678         _sepCount: null,
679         /**
680         * @protected
681         * @property draghandle
682         * @type HTMLElement
683         */
684         _dragHandle: null,
685         /**
686         * @protected
687         * @property _toolbarConfigs
688         * @type Object
689         */
690         _toolbarConfigs: {
691             renderer: true
692         },
693         /**
694         * @protected
695         * @property CLASS_CONTAINER
696         * @description Default CSS class to apply to the toolbar container element
697         * @type String
698         */
699         CLASS_CONTAINER: 'yui-toolbar-container',
700         /**
701         * @protected
702         * @property CLASS_DRAGHANDLE
703         * @description Default CSS class to apply to the toolbar's drag handle element
704         * @type String
705         */
706         CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
707         /**
708         * @protected
709         * @property CLASS_SEPARATOR
710         * @description Default CSS class to apply to all separators in the toolbar
711         * @type String
712         */
713         CLASS_SEPARATOR: 'yui-toolbar-separator',
714         /**
715         * @protected
716         * @property CLASS_DISABLED
717         * @description Default CSS class to apply when the toolbar is disabled
718         * @type String
719         */
720         CLASS_DISABLED: 'yui-toolbar-disabled',
721         /**
722         * @protected
723         * @property CLASS_PREFIX
724         * @description Default prefix for dynamically created class names
725         * @type String
726         */
727         CLASS_PREFIX: 'yui-toolbar',
728         /** 
729         * @method init
730         * @description The Toolbar class's initialization method
731         */
732         init: function(p_oElement, p_oAttributes) {
733             YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
735         },
736         /**
737         * @method initAttributes
738         * @description Initializes all of the configuration attributes used to create 
739         * the toolbar.
740         * @param {Object} attr Object literal specifying a set of 
741         * configuration attributes used to create the toolbar.
742         */
743         initAttributes: function(attr) {
744             YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
745             this.addClass(this.CLASS_CONTAINER);
747             /**
748             * @attribute buttonType
749             * @description The buttonType to use (advanced or basic)
750             * @type String
751             */
752             this.setAttributeConfig('buttonType', {
753                 value: attr.buttonType || 'basic',
754                 writeOnce: true,
755                 validator: function(type) {
756                     switch (type) {
757                         case 'advanced':
758                         case 'basic':
759                             return true;
760                     }
761                     return false;
762                 },
763                 method: function(type) {
764                     if (type == 'advanced') {
765                         if (YAHOO.widget.Button) {
766                             this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
767                         } else {
768                             this.buttonType = YAHOO.widget.ToolbarButton;
769                         }
770                     } else {
771                         this.buttonType = YAHOO.widget.ToolbarButton;
772                     }
773                 }
774             });
777             /**
778             * @attribute buttons
779             * @description Object specifying the buttons to include in the toolbar
780             * Example:
781             * <code><pre>
782             * {
783             *   { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
784             *   { type: 'separator' },
785             *   { id: 'b4', type: 'menu', label: 'Align', value: 'align',
786             *       menu: [
787             *           { text: "Left", value: 'alignleft' },
788             *           { text: "Center", value: 'aligncenter' },
789             *           { text: "Right", value: 'alignright' }
790             *       ]
791             *   }
792             * }
793             * </pre></code>
794             * @type Array
795             */
796             
797             this.setAttributeConfig('buttons', {
798                 value: [],
799                 writeOnce: true,
800                 method: function(data) {
801                     for (var i in data) {
802                         if (Lang.hasOwnProperty(data, i)) {
803                             if (data[i].type == 'separator') {
804                                 this.addSeparator();
805                             } else if (data[i].group !== undefined) {
806                                 this.addButtonGroup(data[i]);
807                             } else {
808                                 this.addButton(data[i]);
809                             }
810                         }
811                     }
812                 }
813             });
815             /**
816             * @attribute disabled
817             * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
818             * @default false
819             * @type Boolean
820             */
821             this.setAttributeConfig('disabled', {
822                 value: false,
823                 method: function(disabled) {
824                     if (this.get('disabled') === disabled) {
825                         return false;
826                     }
827                     if (disabled) {
828                         this.addClass(this.CLASS_DISABLED);
829                         this.set('draggable', false);
830                         this.disableAllButtons();
831                     } else {
832                         this.removeClass(this.CLASS_DISABLED);
833                         if (this._configs.draggable._initialConfig.value) {
834                             //Draggable by default, set it back
835                             this.set('draggable', true);
836                         }
837                         this.resetAllButtons();
838                     }
839                 }
840             });
842             /**
843             * @config cont
844             * @description The container for the toolbar.
845             * @type HTMLElement
846             */
847             this.setAttributeConfig('cont', {
848                 value: attr.cont,
849                 readOnly: true
850             });
853             /**
854             * @attribute grouplabels
855             * @description Boolean indicating if the toolbar should show the group label's text string.
856             * @default true
857             * @type Boolean
858             */
859             this.setAttributeConfig('grouplabels', {
860                 value: ((attr.grouplabels === false) ? false : true),
861                 method: function(grouplabels) {
862                     if (grouplabels) {
863                         Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
864                     } else {
865                         Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
866                     }
867                 }
868             });
869             /**
870             * @attribute titlebar
871             * @description Boolean indicating if the toolbar should have a titlebar. If
872             * passed a string, it will use that as the titlebar text
873             * @default false
874             * @type Boolean or String
875             */
876             this.setAttributeConfig('titlebar', {
877                 value: false,
878                 method: function(titlebar) {
879                     if (titlebar) {
880                         if (this._titlebar && this._titlebar.parentNode) {
881                             this._titlebar.parentNode.removeChild(this._titlebar);
882                         }
883                         this._titlebar = document.createElement('DIV');
884                         this._titlebar.tabIndex = '-1';
885                         Event.on(this._titlebar, 'focus', function() {
886                             this._handleFocus();
887                         }, this, true);
888                         Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
889                         if (Lang.isString(titlebar)) {
890                             var h2 = document.createElement('h2');
891                             h2.tabIndex = '-1';
892                             h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
893                             this._titlebar.appendChild(h2);
894                             Event.on(h2.firstChild, 'click', function(ev) {
895                                 Event.stopEvent(ev);
896                             });
897                             Event.on([h2, h2.firstChild], 'focus', function() {
898                                 this._handleFocus();
899                             }, this, true);
900                         }
901                         if (this.get('firstChild')) {
902                             this.insertBefore(this._titlebar, this.get('firstChild'));
903                         } else {
904                             this.appendChild(this._titlebar);
905                         }
906                         if (this.get('collapse')) {
907                             this.set('collapse', true);
908                         }
909                     } else if (this._titlebar) {
910                         if (this._titlebar && this._titlebar.parentNode) {
911                             this._titlebar.parentNode.removeChild(this._titlebar);
912                         }
913                     }
914                 }
915             });
918             /**
919             * @attribute collapse
920             * @description Boolean indicating if the the titlebar should have a collapse button.
921             * The collapse button will not remove the toolbar, it will minimize it to the titlebar
922             * @default false
923             * @type Boolean
924             */
925             this.setAttributeConfig('collapse', {
926                 value: false,
927                 method: function(collapse) {
928                     if (this._titlebar) {
929                         var collapseEl = null;
930                         var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
931                         if (collapse) {
932                             if (el.length > 0) {
933                                 //There is already a collapse button
934                                 return true;
935                             }
936                             collapseEl = document.createElement('SPAN');
937                             collapseEl.innerHTML = 'X';
938                             collapseEl.title = this.STR_COLLAPSE;
940                             Dom.addClass(collapseEl, 'collapse');
941                             this._titlebar.appendChild(collapseEl);
942                             Event.addListener(collapseEl, 'click', function() {
943                                 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
944                                     this.collapse(false); //Expand Toolbar
945                                 } else {
946                                     this.collapse(); //Collapse Toolbar
947                                 }
948                             }, this, true);
949                         } else {
950                             collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
951                             if (collapseEl[0]) {
952                                 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
953                                     //We are closed, reopen the titlebar..
954                                     this.collapse(false); //Expand Toolbar
955                                 }
956                                 collapseEl[0].parentNode.removeChild(collapseEl[0]);
957                             }
958                         }
959                     }
960                 }
961             });
963             /**
964             * @attribute draggable
965             * @description Boolean indicating if the toolbar should be draggable.  
966             * @default false
967             * @type Boolean
968             */
970             this.setAttributeConfig('draggable', {
971                 value: (attr.draggable || false),
972                 method: function(draggable) {
973                     if (draggable && !this.get('titlebar')) {
974                         if (!this._dragHandle) {
975                             this._dragHandle = document.createElement('SPAN');
976                             this._dragHandle.innerHTML = '|';
977                             this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
978                             this._dragHandle.id = this.get('id') + '_draghandle';
979                             Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
980                             if (this.get('cont').hasChildNodes()) {
981                                 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
982                             } else {
983                                 this.get('cont').appendChild(this._dragHandle);
984                             }
985                             /**
986                             * @property dd
987                             * @description The DragDrop instance associated with the Toolbar
988                             * @type Object
989                             */
990                             this.dd = new YAHOO.util.DD(this.get('id'));
991                             this.dd.setHandleElId(this._dragHandle.id);
992                             
993                         }
994                     } else {
995                         if (this._dragHandle) {
996                             this._dragHandle.parentNode.removeChild(this._dragHandle);
997                             this._dragHandle = null;
998                             this.dd = null;
999                         }
1000                     }
1001                     if (this._titlebar) {
1002                         if (draggable) {
1003                             this.dd = new YAHOO.util.DD(this.get('id'));
1004                             this.dd.setHandleElId(this._titlebar);
1005                             Dom.addClass(this._titlebar, 'draggable');
1006                         } else {
1007                             Dom.removeClass(this._titlebar, 'draggable');
1008                             if (this.dd) {
1009                                 this.dd.unreg();
1010                                 this.dd = null;
1011                             }
1012                         }
1013                     }
1014                 },
1015                 validator: function(value) {
1016                     var ret = true;
1017                     if (!YAHOO.util.DD) {
1018                         ret = false;
1019                     }
1020                     return ret;
1021                 }
1022             });
1024         },
1025         /**
1026         * @method addButtonGroup
1027         * @description Add a new button group to the toolbar. (uses addButton)
1028         * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1029         */
1030         addButtonGroup: function(oGroup) {
1031             if (!this.get('element')) {
1032                 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1033                 return false;
1034             }
1035             
1036             if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1037                 this.addClass(this.CLASS_PREFIX + '-grouped');
1038             }
1039             var div = document.createElement('DIV');
1040             Dom.addClass(div, this.CLASS_PREFIX + '-group');
1041             Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1042             if (oGroup.label) {
1043                 var label = document.createElement('h3');
1044                 label.innerHTML = oGroup.label;
1045                 div.appendChild(label);
1046             }
1047             if (!this.get('grouplabels')) {
1048                 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1049             }
1051             this.get('cont').appendChild(div);
1053             //For accessibility, let's put all of the group buttons in an Unordered List
1054             var ul = document.createElement('ul');
1055             div.appendChild(ul);
1057             if (!this._buttonGroupList) {
1058                 this._buttonGroupList = {};
1059             }
1060             
1061             this._buttonGroupList[oGroup.group] = ul;
1063             for (var i = 0; i < oGroup.buttons.length; i++) {
1064                 var li = document.createElement('li');
1065                 li.className = this.CLASS_PREFIX + '-groupitem';
1066                 ul.appendChild(li);
1067                 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1068                     this.addSeparator(li);
1069                 } else {
1070                     oGroup.buttons[i].container = li;
1071                     this.addButton(oGroup.buttons[i]);
1072                 }
1073             }
1074         },
1075         /**
1076         * @method addButtonToGroup
1077         * @description Add a new button to a toolbar group. Buttons supported:
1078         *   push, split, menu, select, color, spin
1079         * @param {Object} oButton Object literal reference to the Button's Config
1080         * @param {String} group The Group identifier passed into the initial config
1081         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1082         */
1083         addButtonToGroup: function(oButton, group, after) {
1084             var groupCont = this._buttonGroupList[group];
1085             var li = document.createElement('li');
1086             li.className = this.CLASS_PREFIX + '-groupitem';
1087             oButton.container = li;
1088             this.addButton(oButton, after);
1089             groupCont.appendChild(li);
1090         },
1091         /**
1092         * @method addButton
1093         * @description Add a new button to the toolbar. Buttons supported:
1094         *   push, split, menu, select, color, spin
1095         * @param {Object} oButton Object literal reference to the Button's Config
1096         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1097         */
1098         addButton: function(oButton, after) {
1099             if (!this.get('element')) {
1100                 this._queue[this._queue.length] = ['addButton', arguments];
1101                 return false;
1102             }
1103             if (!this._buttonList) {
1104                 this._buttonList = [];
1105             }
1106             if (!oButton.container) {
1107                 oButton.container = this.get('cont');
1108             }
1110             if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1111                 if (Lang.isArray(oButton.menu)) {
1112                     for (var i in oButton.menu) {
1113                         if (Lang.hasOwnProperty(oButton.menu, i)) {
1114                             var funcObject = {
1115                                 fn: function(ev, x, oMenu) {
1116                                     if (!oButton.menucmd) {
1117                                         oButton.menucmd = oButton.value;
1118                                     }
1119                                     oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1120                                 },
1121                                 scope: this
1122                             };
1123                             oButton.menu[i].onclick = funcObject;
1124                         }
1125                     }
1126                 }
1127             }
1128             var _oButton = {}, skip = false;
1129             for (var o in oButton) {
1130                 if (Lang.hasOwnProperty(oButton, o)) {
1131                     if (!this._toolbarConfigs[o]) {
1132                         _oButton[o] = oButton[o];
1133                     }
1134                 }
1135             }
1136             if (oButton.type == 'select') {
1137                 _oButton.type = 'menu';
1138             }
1139             if (oButton.type == 'spin') {
1140                 _oButton.type = 'push';
1141             }
1142             if (_oButton.type == 'color') {
1143                 if (YAHOO.widget.Overlay) {
1144                     _oButton = this._makeColorButton(_oButton);
1145                 } else {
1146                     skip = true;
1147                 }
1148             }
1149             if (_oButton.menu) {
1150                 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1151                     oButton.menu.showEvent.subscribe(function() {
1152                         this._button = _oButton;
1153                     });
1154                 } else {
1155                     for (var m = 0; m < _oButton.menu.length; m++) {
1156                         if (!_oButton.menu[m].value) {
1157                             _oButton.menu[m].value = _oButton.menu[m].text;
1158                         }
1159                     }
1160                     if (this.browser.webkit) {
1161                         _oButton.focusmenu = false;
1162                     }
1163                 }
1164             }
1165             if (skip) {
1166                 oButton = false;
1167             } else {
1168                 //Add to .get('buttons') manually
1169                 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1170                 
1171                 var tmp = new this.buttonType(_oButton);
1172                 tmp.get('element').tabIndex = '-1';
1173                 tmp.get('element').setAttribute('role', 'button');
1174                 tmp._selected = true;
1175                 
1176                 if (this.get('disabled')) {
1177                     //Toolbar is disabled, disable the new button too!
1178                     tmp.set('disabled', true);
1179                 }
1180                 if (!oButton.id) {
1181                     oButton.id = tmp.get('id');
1182                 }
1183                 
1184                 if (after) {
1185                     var el = tmp.get('element');
1186                     var nextSib = null;
1187                     if (after.get) {
1188                         nextSib = after.get('element').nextSibling;
1189                     } else if (after.nextSibling) {
1190                         nextSib = after.nextSibling;
1191                     }
1192                     if (nextSib) {
1193                         nextSib.parentNode.insertBefore(el, nextSib);
1194                     }
1195                 }
1196                 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1198                 var icon = document.createElement('span');
1199                 icon.className = this.CLASS_PREFIX + '-icon';
1200                 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1201                 if (tmp._button.tagName.toLowerCase() == 'button') {
1202                     tmp.get('element').setAttribute('unselectable', 'on');
1203                     //Replace the Button HTML Element with an a href if it exists
1204                     var a = document.createElement('a');
1205                     a.innerHTML = tmp._button.innerHTML;
1206                     a.href = '#';
1207                     a.tabIndex = '-1';
1208                     Event.on(a, 'click', function(ev) {
1209                         Event.stopEvent(ev);
1210                     });
1211                     tmp._button.parentNode.replaceChild(a, tmp._button);
1212                     tmp._button = a;
1213                 }
1215                 if (oButton.type == 'select') {
1216                     if (tmp._button.tagName.toLowerCase() == 'select') {
1217                         icon.parentNode.removeChild(icon);
1218                         var iel = tmp._button;
1219                         var parEl = tmp.get('element');
1220                         parEl.parentNode.replaceChild(iel, parEl);
1221                     } else {
1222                         //Don't put a class on it if it's a real select element
1223                         tmp.addClass(this.CLASS_PREFIX + '-select');
1224                     }
1225                 }
1226                 if (oButton.type == 'spin') {
1227                     if (!Lang.isArray(oButton.range)) {
1228                         oButton.range = [ 10, 100 ];
1229                     }
1230                     this._makeSpinButton(tmp, oButton);
1231                 }
1232                 tmp.get('element').setAttribute('title', tmp.get('label'));
1233                 if (oButton.type != 'spin') {
1234                     if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1235                         var showPicker = function(ev) {
1236                             var exec = true;
1237                             if (ev.keyCode && (ev.keyCode == 9)) {
1238                                 exec = false;
1239                             }
1240                             if (exec) {
1241                                 if (this._colorPicker) {
1242                                     this._colorPicker._button = oButton.value;
1243                                 }
1244                                 var menuEL = tmp.getMenu().element;
1245                                 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1246                                     tmp.getMenu().show();
1247                                 } else {
1248                                     tmp.getMenu().hide();
1249                                 }
1250                             }
1251                             YAHOO.util.Event.stopEvent(ev);
1252                         };
1253                         tmp.on('mousedown', showPicker, oButton, this);
1254                         tmp.on('keydown', showPicker, oButton, this);
1255                         
1256                     } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1257                         tmp.on('keypress', this._buttonClick, oButton, this);
1258                         tmp.on('mousedown', function(ev) {
1259                             YAHOO.util.Event.stopEvent(ev);
1260                             this._buttonClick(ev, oButton);
1261                         }, oButton, this);
1262                         tmp.on('click', function(ev) {
1263                             YAHOO.util.Event.stopEvent(ev);
1264                         });
1265                     } else {
1266                         //Stop the mousedown event so we can trap the selection in the editor!
1267                         tmp.on('mousedown', function(ev) {
1268                             YAHOO.util.Event.stopEvent(ev);
1269                         });
1270                         tmp.on('click', function(ev) {
1271                             YAHOO.util.Event.stopEvent(ev);
1272                         });
1273                         tmp.on('change', function(ev) {
1274                             if (!oButton.menucmd) {
1275                                 oButton.menucmd = oButton.value;
1276                             }
1277                             oButton.value = ev.value;
1278                             this._buttonClick(ev, oButton);
1279                         }, this, true);
1281                         var self = this;
1282                         //Hijack the mousedown event in the menu and make it fire a button click..
1283                         tmp.on('appendTo', function() {
1284                             var tmp = this;
1285                             if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1286                                 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1287                                     var oMenu = args[1];
1288                                     YAHOO.util.Event.stopEvent(args[0]);
1289                                     tmp._onMenuClick(args[0], tmp);
1290                                     if (!oButton.menucmd) {
1291                                         oButton.menucmd = oButton.value;
1292                                     }
1293                                     oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1294                                     self._buttonClick.call(self, args[1], oButton);
1295                                     tmp._hideMenu();
1296                                     return false;
1297                                 });
1298                                 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1299                                     YAHOO.util.Event.stopEvent(args[0]);
1300                                 });
1301                                 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1302                                     YAHOO.util.Event.stopEvent(args[0]);
1303                                 });
1304                             }
1305                         });
1306                         
1307                     }
1308                 } else {
1309                     //Stop the mousedown event so we can trap the selection in the editor!
1310                     tmp.on('mousedown', function(ev) {
1311                         YAHOO.util.Event.stopEvent(ev);
1312                     });
1313                     tmp.on('click', function(ev) {
1314                         YAHOO.util.Event.stopEvent(ev);
1315                     });
1316                 }
1317                 if (this.browser.ie) {
1318                     /*
1319                     //Add a couple of new events for IE
1320                     tmp.DOM_EVENTS.focusin = true;
1321                     tmp.DOM_EVENTS.focusout = true;
1322                     
1323                     //Stop them so we don't loose focus in the Editor
1324                     tmp.on('focusin', function(ev) {
1325                         YAHOO.util.Event.stopEvent(ev);
1326                     }, oButton, this);
1327                     
1328                     tmp.on('focusout', function(ev) {
1329                         YAHOO.util.Event.stopEvent(ev);
1330                     }, oButton, this);
1331                     tmp.on('click', function(ev) {
1332                         YAHOO.util.Event.stopEvent(ev);
1333                     }, oButton, this);
1334                     */
1335                 }
1336                 if (this.browser.webkit) {
1337                     //This will keep the document from gaining focus and the editor from loosing it..
1338                     //Forcefully remove the focus calls in button!
1339                     tmp.hasFocus = function() {
1340                         return true;
1341                     };
1342                 }
1343                 this._buttonList[this._buttonList.length] = tmp;
1344                 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1345                     if (Lang.isArray(oButton.menu)) {
1346                         var menu = tmp.getMenu();
1347                         if (menu && menu.renderEvent) {
1348                             menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1349                             if (oButton.renderer) {
1350                                 menu.renderEvent.subscribe(oButton.renderer, tmp);
1351                             }
1352                         }
1353                     }
1354                 }
1355             }
1356             return oButton;
1357         },
1358         /**
1359         * @method addSeparator
1360         * @description Add a new button separator to the toolbar.
1361         * @param {HTMLElement} cont Optional HTML element to insert this button into.
1362         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1363         */
1364         addSeparator: function(cont, after) {
1365             if (!this.get('element')) {
1366                 this._queue[this._queue.length] = ['addSeparator', arguments];
1367                 return false;
1368             }
1369             var sepCont = ((cont) ? cont : this.get('cont'));
1370             if (!this.get('element')) {
1371                 this._queue[this._queue.length] = ['addSeparator', arguments];
1372                 return false;
1373             }
1374             if (this._sepCount === null) {
1375                 this._sepCount = 0;
1376             }
1377             if (!this._sep) {
1378                 this._sep = document.createElement('SPAN');
1379                 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1380                 this._sep.innerHTML = '|';
1381             }
1382             var _sep = this._sep.cloneNode(true);
1383             this._sepCount++;
1384             Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1385             if (after) {
1386                 var nextSib = null;
1387                 if (after.get) {
1388                     nextSib = after.get('element').nextSibling;
1389                 } else if (after.nextSibling) {
1390                     nextSib = after.nextSibling;
1391                 } else {
1392                     nextSib = after;
1393                 }
1394                 if (nextSib) {
1395                     if (nextSib == after) {
1396                         nextSib.parentNode.appendChild(_sep);
1397                     } else {
1398                         nextSib.parentNode.insertBefore(_sep, nextSib);
1399                     }
1400                 }
1401             } else {
1402                 sepCont.appendChild(_sep);
1403             }
1404             return _sep;
1405         },
1406         /**
1407         * @method _createColorPicker
1408         * @private
1409         * @description Creates the core DOM reference to the color picker menu item.
1410         * @param {String} id the id of the toolbar to prefix this DOM container with.
1411         */
1412         _createColorPicker: function(id) {
1413             if (Dom.get(id + '_colors')) {
1414                Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1415             }
1416             var picker = document.createElement('div');
1417             picker.className = 'yui-toolbar-colors';
1418             picker.id = id + '_colors';
1419             picker.style.display = 'none';
1420             Event.on(window, 'load', function() {
1421                 document.body.appendChild(picker);
1422             }, this, true);
1424             this._colorPicker = picker;
1426             var html = '';
1427             for (var i in this._colorData) {
1428                 if (Lang.hasOwnProperty(this._colorData, i)) {
1429                     html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1430                 }
1431             }
1432             html += '<span><em>X</em><strong></strong></span>';
1433             window.setTimeout(function() {
1434                 picker.innerHTML = html;
1435             }, 0);
1437             Event.on(picker, 'mouseover', function(ev) {
1438                 var picker = this._colorPicker;
1439                 var em = picker.getElementsByTagName('em')[0];
1440                 var strong = picker.getElementsByTagName('strong')[0];
1441                 var tar = Event.getTarget(ev);
1442                 if (tar.tagName.toLowerCase() == 'a') {
1443                     em.style.backgroundColor = tar.style.backgroundColor;
1444                     strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1445                 }
1446             }, this, true);
1447             Event.on(picker, 'focus', function(ev) {
1448                 Event.stopEvent(ev);
1449             });
1450             Event.on(picker, 'click', function(ev) {
1451                 Event.stopEvent(ev);
1452             });
1453             Event.on(picker, 'mousedown', function(ev) {
1454                 Event.stopEvent(ev);
1455                 var tar = Event.getTarget(ev);
1456                 if (tar.tagName.toLowerCase() == 'a') {
1457                     var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1458                     if (retVal !== false) {
1459                         var info = {
1460                             color: tar.innerHTML,
1461                             colorName: this._colorData['#' + tar.innerHTML],
1462                             value: this._colorPicker._button 
1463                         };
1464                     
1465                         this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1466                     }
1467                     this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1468                 }
1469             }, this, true);
1470         },
1471         /**
1472         * @method _resetColorPicker
1473         * @private
1474         * @description Clears the currently selected color or mouseover color in the color picker.
1475         */
1476         _resetColorPicker: function() {
1477             var em = this._colorPicker.getElementsByTagName('em')[0];
1478             var strong = this._colorPicker.getElementsByTagName('strong')[0];
1479             em.style.backgroundColor = 'transparent';
1480             strong.innerHTML = '';
1481         },
1482         /**
1483         * @method _makeColorButton
1484         * @private
1485         * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1486         * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1487         */
1488         _makeColorButton: function(_oButton) {
1489             if (!this._colorPicker) {   
1490                 this._createColorPicker(this.get('id'));
1491             }
1492             _oButton.type = 'color';
1493             _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1494             _oButton.menu.setBody('');
1495             _oButton.menu.render(this.get('cont'));
1496             Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1497             Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1498             _oButton.menu.beforeShowEvent.subscribe(function() {
1499                 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1500                 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1501                 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1502                 this._resetColorPicker();
1503                 var _p = this._colorPicker;
1504                 if (_p.parentNode) {
1505                     _p.parentNode.removeChild(_p);
1506                 }
1507                 _oButton.menu.setBody('');
1508                 _oButton.menu.appendToBody(_p);
1509                 this._colorPicker.style.display = 'block';
1510             }, this, true);
1511             return _oButton;
1512         },
1513         /**
1514         * @private
1515         * @method _makeSpinButton
1516         * @description Create a button similar to an OS Spin button.. It has an up/down arrow combo to scroll through a range of int values.
1517         * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1518         * @param {Object} oButton Object literal containing the buttons initial config
1519         */
1520         _makeSpinButton: function(_button, oButton) {
1521             _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1522             var self = this,
1523                 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1524                 range = oButton.range,
1525                 _b1 = document.createElement('a'),
1526                 _b2 = document.createElement('a');
1527                 _b1.href = '#';
1528                 _b2.href = '#';
1529                 _b1.tabIndex = '-1';
1530                 _b2.tabIndex = '-1';
1531             
1532             //Setup the up and down arrows
1533             _b1.className = 'up';
1534             _b1.title = this.STR_SPIN_UP;
1535             _b1.innerHTML = this.STR_SPIN_UP;
1536             _b2.className = 'down';
1537             _b2.title = this.STR_SPIN_DOWN;
1538             _b2.innerHTML = this.STR_SPIN_DOWN;
1540             //Append them to the container
1541             _par.appendChild(_b1);
1542             _par.appendChild(_b2);
1543             
1544             var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1545             _button.set('title', label);
1547             var cleanVal = function(value) {
1548                 value = ((value < range[0]) ? range[0] : value);
1549                 value = ((value > range[1]) ? range[1] : value);
1550                 return value;
1551             };
1553             var br = this.browser;
1554             var tbar = false;
1555             var strLabel = this.STR_SPIN_LABEL;
1556             if (this._titlebar && this._titlebar.firstChild) {
1557                 tbar = this._titlebar.firstChild;
1558             }
1559             
1560             var _intUp = function(ev) {
1561                 YAHOO.util.Event.stopEvent(ev);
1562                 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1563                     var value = parseInt(_button.get('label'), 10);
1564                     value++;
1565                     value = cleanVal(value);
1566                     _button.set('label', ''+value);
1567                     var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1568                     _button.set('title', label);
1569                     if (!br.webkit && tbar) {
1570                         //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1571                         //_button.focus();
1572                     }
1573                     self._buttonClick(ev, oButton);
1574                 }
1575             };
1577             var _intDown = function(ev) {
1578                 YAHOO.util.Event.stopEvent(ev);
1579                 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1580                     var value = parseInt(_button.get('label'), 10);
1581                     value--;
1582                     value = cleanVal(value);
1584                     _button.set('label', ''+value);
1585                     var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1586                     _button.set('title', label);
1587                     if (!br.webkit && tbar) {
1588                         //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1589                         //_button.focus();
1590                     }
1591                     self._buttonClick(ev, oButton);
1592                 }
1593             };
1595             var _intKeyUp = function(ev) {
1596                 if (ev.keyCode == 38) {
1597                     _intUp(ev);
1598                 } else if (ev.keyCode == 40) {
1599                     _intDown(ev);
1600                 } else if (ev.keyCode == 107 && ev.shiftKey) {  //Plus Key
1601                     _intUp(ev);
1602                 } else if (ev.keyCode == 109 && ev.shiftKey) {  //Minus Key
1603                     _intDown(ev);
1604                 }
1605             };
1607             //Handle arrow keys..
1608             _button.on('keydown', _intKeyUp, this, true);
1610             //Listen for the click on the up button and act on it
1611             //Listen for the click on the down button and act on it
1612             Event.on(_b1, 'mousedown',function(ev) {
1613                 Event.stopEvent(ev);
1614             }, this, true);
1615             Event.on(_b2, 'mousedown', function(ev) {
1616                 Event.stopEvent(ev);
1617             }, this, true);
1618             Event.on(_b1, 'click', _intUp, this, true);
1619             Event.on(_b2, 'click', _intDown, this, true);
1620         },
1621         /**
1622         * @protected
1623         * @method _buttonClick
1624         * @description Click handler for all buttons in the toolbar.
1625         * @param {String} ev The event that was passed in.
1626         * @param {Object} info Object literal of information about the button that was clicked.
1627         */
1628         _buttonClick: function(ev, info) {
1629             var doEvent = true;
1630             
1631             if (ev && ev.type == 'keypress') {
1632                 if (ev.keyCode == 9) {
1633                     doEvent = false;
1634                 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1635                 } else {
1636                     doEvent = false;
1637                 }
1638             }
1640             if (doEvent) {
1641                 var fireNextEvent = true,
1642                     retValue = false;
1643                     
1644                 info.isSelected = this.isSelected(info.id);
1646                 if (info.value) {
1647                     retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1648                     if (retValue === false) {
1649                         fireNextEvent = false;
1650                     }
1651                 }
1652                 
1653                 if (info.menucmd && fireNextEvent) {
1654                     retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1655                     if (retValue === false) {
1656                         fireNextEvent = false;
1657                     }
1658                 }
1659                 if (fireNextEvent) {
1660                     this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1661                 }
1663                 if (info.type == 'select') {
1664                     var button = this.getButtonById(info.id);
1665                     if (button.buttonType == 'rich') {
1666                         var txt = info.value;
1667                         for (var i = 0; i < info.menu.length; i++) {
1668                             if (info.menu[i].value == info.value) {
1669                                 txt = info.menu[i].text;
1670                                 break;
1671                             }
1672                         }
1673                         button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1674                         var _items = button.getMenu().getItems();
1675                         for (var m = 0; m < _items.length; m++) {
1676                             if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1677                                 _items[m].cfg.setProperty('checked', true);
1678                             } else {
1679                                 _items[m].cfg.setProperty('checked', false);
1680                             }
1681                         }
1682                     }
1683                 }
1684                 if (ev) {
1685                     Event.stopEvent(ev);
1686                 }
1687             }
1688         },
1689         /**
1690         * @private
1691         * @property _keyNav
1692         * @description Flag to determine if the arrow nav listeners have been attached
1693         * @type Boolean
1694         */
1695         _keyNav: null,
1696         /**
1697         * @private
1698         * @property _navCounter
1699         * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1700         * @type Number
1701         */
1702         _navCounter: null,
1703         /**
1704         * @private
1705         * @method _navigateButtons
1706         * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1707         * @param {Event} ev The Key Event
1708         */
1709         _navigateButtons: function(ev) {
1710             switch (ev.keyCode) {
1711                 case 37:
1712                 case 39:
1713                     if (ev.keyCode == 37) {
1714                         this._navCounter--;
1715                     } else {
1716                         this._navCounter++;
1717                     }
1718                     if (this._navCounter > (this._buttonList.length - 1)) {
1719                         this._navCounter = 0;
1720                     }
1721                     if (this._navCounter < 0) {
1722                         this._navCounter = (this._buttonList.length - 1);
1723                     }
1724                     if (this._buttonList[this._navCounter]) {
1725                         var el = this._buttonList[this._navCounter].get('element');
1726                         if (this.browser.ie) {
1727                             el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1728                         }
1729                         if (this._buttonList[this._navCounter].get('disabled')) {
1730                             this._navigateButtons(ev);
1731                         } else {
1732                             el.focus();
1733                         }
1734                     }
1735                     break;
1736             }
1737         },
1738         /**
1739         * @private
1740         * @method _handleFocus
1741         * @description Sets up the listeners for the arrow key navigation
1742         */
1743         _handleFocus: function() {
1744             if (!this._keyNav) {
1745                 var ev = 'keypress';
1746                 if (this.browser.ie) {
1747                     ev = 'keydown';
1748                 }
1749                 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1750                 this._keyNav = true;
1751                 this._navCounter = -1;
1752             }
1753         },
1754         /**
1755         * @method getButtonById
1756         * @description Gets a button instance from the toolbar by is Dom id.
1757         * @param {String} id The Dom id to query for.
1758         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1759         */
1760         getButtonById: function(id) {
1761             var len = this._buttonList.length;
1762             for (var i = 0; i < len; i++) {
1763                 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1764                     return this._buttonList[i];
1765                 }
1766             }
1767             return false;
1768         },
1769         /**
1770         * @method getButtonByValue
1771         * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1772         * @param {String} value The button value to query for.
1773         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1774         */
1775         getButtonByValue: function(value) {
1776             var _buttons = this.get('buttons');
1777             var len = _buttons.length;
1778             for (var i = 0; i < len; i++) {
1779                 if (_buttons[i].group !== undefined) {
1780                     for (var m = 0; m < _buttons[i].buttons.length; m++) {
1781                         if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1782                             return this.getButtonById(_buttons[i].buttons[m].id);
1783                         }
1784                         if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1785                             for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1786                                 if (_buttons[i].buttons[m].menu[s].value == value) {
1787                                     return this.getButtonById(_buttons[i].buttons[m].id);
1788                                 }
1789                             }
1790                         }
1791                     }
1792                 } else {
1793                     if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1794                         return this.getButtonById(_buttons[i].id);
1795                     }
1796                     if (_buttons[i].menu) { //Menu Button, loop through the values
1797                         for (var j = 0; j < _buttons[i].menu.length; j++) {
1798                             if (_buttons[i].menu[j].value == value) {
1799                                 return this.getButtonById(_buttons[i].id);
1800                             }
1801                         }
1802                     }
1803                 }
1804             }
1805             return false;
1806         },
1807         /**
1808         * @method getButtonByIndex
1809         * @description Gets a button instance from the toolbar by is index in _buttonList.
1810         * @param {Number} index The index of the button in _buttonList.
1811         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1812         */
1813         getButtonByIndex: function(index) {
1814             if (this._buttonList[index]) {
1815                 return this._buttonList[index];
1816             } else {
1817                 return false;
1818             }
1819         },
1820         /**
1821         * @method getButtons
1822         * @description Returns an array of buttons in the current toolbar
1823         * @return {Array}
1824         */
1825         getButtons: function() {
1826             return this._buttonList;
1827         },
1828         /**
1829         * @method disableButton
1830         * @description Disables a button in the toolbar.
1831         * @param {String/Number} id Disable a button by it's id, index or value.
1832         * @return {Boolean}
1833         */
1834         disableButton: function(id) {
1835             var button = getButton.call(this, id);
1836             if (button) {
1837                 button.set('disabled', true);
1838             } else {
1839                 return false;
1840             }
1841         },
1842         /**
1843         * @method enableButton
1844         * @description Enables a button in the toolbar.
1845         * @param {String/Number} id Enable a button by it's id, index or value.
1846         * @return {Boolean}
1847         */
1848         enableButton: function(id) {
1849             if (this.get('disabled')) {
1850                 return false;
1851             }
1852             var button = getButton.call(this, id);
1853             if (button) {
1854                 if (button.get('disabled')) {
1855                     button.set('disabled', false);
1856                 }
1857             } else {
1858                 return false;
1859             }
1860         },
1861         /**
1862         * @method isSelected
1863         * @description Tells if a button is selected or not.
1864         * @param {String/Number} id A button by it's id, index or value.
1865         * @return {Boolean}
1866         */
1867         isSelected: function(id) {
1868             var button = getButton.call(this, id);
1869             if (button) {
1870                 return button._selected;
1871             }
1872             return false;
1873         },
1874         /**
1875         * @method selectButton
1876         * @description Selects a button in the toolbar.
1877         * @param {String/Number} id Select a button by it's id, index or value.
1878         * @param {String} value If this is a Menu Button, check this item in the menu
1879         * @return {Boolean}
1880         */
1881         selectButton: function(id, value) {
1882             var button = getButton.call(this, id);
1883             if (button) {
1884                 button.addClass('yui-button-selected');
1885                 button.addClass('yui-button-' + button.get('value') + '-selected');
1886                 button._selected = true;
1887                 if (value) {
1888                     if (button.buttonType == 'rich') {
1889                         var _items = button.getMenu().getItems();
1890                         for (var m = 0; m < _items.length; m++) {
1891                             if (_items[m].value == value) {
1892                                 _items[m].cfg.setProperty('checked', true);
1893                                 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1894                             } else {
1895                                 _items[m].cfg.setProperty('checked', false);
1896                             }
1897                         }
1898                     }
1899                 }
1900             } else {
1901                 return false;
1902             }
1903         },
1904         /**
1905         * @method deselectButton
1906         * @description Deselects a button in the toolbar.
1907         * @param {String/Number} id Deselect a button by it's id, index or value.
1908         * @return {Boolean}
1909         */
1910         deselectButton: function(id) {
1911             var button = getButton.call(this, id);
1912             if (button) {
1913                 button.removeClass('yui-button-selected');
1914                 button.removeClass('yui-button-' + button.get('value') + '-selected');
1915                 button.removeClass('yui-button-hover');
1916                 button._selected = false;
1917             } else {
1918                 return false;
1919             }
1920         },
1921         /**
1922         * @method deselectAllButtons
1923         * @description Deselects all buttons in the toolbar.
1924         * @return {Boolean}
1925         */
1926         deselectAllButtons: function() {
1927             var len = this._buttonList.length;
1928             for (var i = 0; i < len; i++) {
1929                 this.deselectButton(this._buttonList[i]);
1930             }
1931         },
1932         /**
1933         * @method disableAllButtons
1934         * @description Disables all buttons in the toolbar.
1935         * @return {Boolean}
1936         */
1937         disableAllButtons: function() {
1938             if (this.get('disabled')) {
1939                 return false;
1940             }
1941             var len = this._buttonList.length;
1942             for (var i = 0; i < len; i++) {
1943                 this.disableButton(this._buttonList[i]);
1944             }
1945         },
1946         /**
1947         * @method enableAllButtons
1948         * @description Enables all buttons in the toolbar.
1949         * @return {Boolean}
1950         */
1951         enableAllButtons: function() {
1952             if (this.get('disabled')) {
1953                 return false;
1954             }
1955             var len = this._buttonList.length;
1956             for (var i = 0; i < len; i++) {
1957                 this.enableButton(this._buttonList[i]);
1958             }
1959         },
1960         /**
1961         * @method resetAllButtons
1962         * @description Resets all buttons to their initial state.
1963         * @param {Object} _ex Except these buttons
1964         * @return {Boolean}
1965         */
1966         resetAllButtons: function(_ex) {
1967             if (!Lang.isObject(_ex)) {
1968                 _ex = {};
1969             }
1970             if (this.get('disabled')) {
1971                 return false;
1972             }
1973             var len = this._buttonList.length;
1974             for (var i = 0; i < len; i++) {
1975                 var _button = this._buttonList[i];
1976                 if (_button) {
1977                     var disabled = _button._configs.disabled._initialConfig.value;
1978                     if (_ex[_button.get('id')]) {
1979                         this.enableButton(_button);
1980                         this.selectButton(_button);
1981                     } else {
1982                         if (disabled) {
1983                             this.disableButton(_button);
1984                         } else {
1985                             this.enableButton(_button);
1986                         }
1987                         this.deselectButton(_button);
1988                     }
1989                 }
1990             }
1991         },
1992         /**
1993         * @method destroyButton
1994         * @description Destroy a button in the toolbar.
1995         * @param {String/Number} id Destroy a button by it's id or index.
1996         * @return {Boolean}
1997         */
1998         destroyButton: function(id) {
1999             var button = getButton.call(this, id);
2000             if (button) {
2001                 var thisID = button.get('id');
2002                 button.destroy();
2004                 var len = this._buttonList.length;
2005                 for (var i = 0; i < len; i++) {
2006                     if (this._buttonList[i] && this._buttonList[i].get('id') == thisID) {
2007                         this._buttonList[i] = null;
2008                     }
2009                 }
2010             } else {
2011                 return false;
2012             }
2013         },
2014         /**
2015         * @method destroy
2016         * @description Destroys the toolbar, all of it's elements and objects.
2017         * @return {Boolean}
2018         */
2019         destroy: function() {
2020             this.get('element').innerHTML = '';
2021             this.get('element').className = '';
2022             //Brutal Object Destroy
2023             for (var i in this) {
2024                 if (Lang.hasOwnProperty(this, i)) {
2025                     this[i] = null;
2026                 }
2027             }
2028             return true;
2029         },
2030         /**
2031         * @method collapse
2032         * @description Programatically collapse the toolbar.
2033         * @param {Boolean} collapse True to collapse, false to expand.
2034         */
2035         collapse: function(collapse) {
2036             var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2037             if (collapse === false) {
2038                 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2039                 if (el[0]) {
2040                     Dom.removeClass(el[0], 'collapsed');
2041                 }
2042                 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2043             } else {
2044                 if (el[0]) {
2045                     Dom.addClass(el[0], 'collapsed');
2046                 }
2047                 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2048                 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2049             }
2050         },
2051         /**
2052         * @method toString
2053         * @description Returns a string representing the toolbar.
2054         * @return {String}
2055         */
2056         toString: function() {
2057             return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2058         }
2059     });
2061 * @event buttonClick
2062 * @param {Object} o The object passed to this handler is the button config used to create the button.
2063 * @description Fires when any botton receives a click event. Passes back a single object representing the buttons config object. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2064 * @type YAHOO.util.CustomEvent
2067 * @event valueClick
2068 * @param {Object} o The object passed to this handler is the button config used to create the button.
2069 * @description This is a special dynamic event that is created and dispatched based on the value property
2070 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2071 * Example:
2072 * <code><pre>
2073 * buttons : [
2074 *   { type: 'button', value: 'test', value: 'testButton' }
2075 * ]</pre>
2076 * </code>
2077 * With the valueClick event you could subscribe to this buttons click event with this:
2078 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2079 * @type YAHOO.util.CustomEvent
2082 * @event toolbarExpanded
2083 * @description Fires when the toolbar is expanded via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2084 * @type YAHOO.util.CustomEvent
2087 * @event toolbarCollapsed
2088 * @description Fires when the toolbar is collapsed via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2089 * @type YAHOO.util.CustomEvent
2091 })();
2093  * @description <p>The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.</p>
2094  * @namespace YAHOO.widget
2095  * @requires yahoo, dom, element, event, toolbar
2096  * @optional animation, container_core, resize, dragdrop
2097  * @beta
2098  */
2100 (function() {
2101 var Dom = YAHOO.util.Dom,
2102     Event = YAHOO.util.Event,
2103     Lang = YAHOO.lang,
2104     Toolbar = YAHOO.widget.Toolbar;
2106     /**
2107      * The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.
2108      * @constructor
2109      * @class SimpleEditor
2110      * @extends YAHOO.util.Element
2111      * @param {String/HTMLElement} el The textarea element to turn into an editor.
2112      * @param {Object} attrs Object liternal containing configuration parameters.
2113     */
2114     
2115     YAHOO.widget.SimpleEditor = function(el, attrs) {
2116         
2117         var o = {};
2118         if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2119             Lang.augmentObject(o, el); //Break the config reference
2120             el = document.createElement('textarea');
2121             this.DOMReady = true;
2122             if (o.container) {
2123                 var c = Dom.get(o.container);
2124                 c.appendChild(el);
2125             } else {
2126                 document.body.appendChild(el);
2127             }
2128         } else {
2129             if (attrs) {
2130                 Lang.augmentObject(o, attrs); //Break the config reference
2131             }
2132         }
2134         var oConfig = {
2135             element: null,
2136             attributes: o
2137         }, id = null;
2139         if (Lang.isString(el)) {
2140             id = el;
2141         } else {
2142             if (oConfig.attributes.id) {
2143                 id = oConfig.attributes.id;
2144             } else {
2145                 this.DOMReady = true;
2146                 id = Dom.generateId(el);
2147             }
2148         }
2149         oConfig.element = el;
2151         var element_cont = document.createElement('DIV');
2152         oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2153             id: id + '_container'
2154         });
2155         var div = document.createElement('div');
2156         Dom.addClass(div, 'first-child');
2157         oConfig.attributes.element_cont.appendChild(div);
2158         
2159         if (!oConfig.attributes.toolbar_cont) {
2160             oConfig.attributes.toolbar_cont = document.createElement('DIV');
2161             oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2162             div.appendChild(oConfig.attributes.toolbar_cont);
2163         }
2164         var editorWrapper = document.createElement('DIV');
2165         div.appendChild(editorWrapper);
2166         oConfig.attributes.editor_wrapper = editorWrapper;
2168         YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2169     };
2172     YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2173         /**
2174         * @private
2175         * @property _resizeConfig
2176         * @description The default config for the Resize Utility
2177         */
2178         _resizeConfig: {
2179             handles: ['br'],
2180             autoRatio: true,
2181             status: true,
2182             proxy: true,
2183             useShim: true,
2184             setSize: false
2185         },
2186         /**
2187         * @private
2188         * @method _setupResize
2189         * @description Creates the Resize instance and binds its events.
2190         */
2191         _setupResize: function() {
2192             if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2193             if (this.get('resize')) {
2194                 var config = {};
2195                 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2196                 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2197                 this.resize.on('resize', function(args) {
2198                     var anim = this.get('animate');
2199                     this.set('animate', false);
2200                     this.set('width', args.width + 'px');
2201                     var h = args.height,
2202                         th = (this.toolbar.get('element').clientHeight + 2),
2203                         dh = 0;
2204                     if (this.dompath) {
2205                         dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2206                     }
2207                     var newH = (h - th - dh);
2208                     this.set('height', newH + 'px');
2209                     this.get('element_cont').setStyle('height', '');
2210                     this.set('animate', anim);
2211                 }, this, true);
2212             }
2213         },
2214         /**
2215         * @property resize
2216         * @description A reference to the Resize object
2217         * @type YAHOO.util.Resize
2218         */
2219         resize: null,
2220         _setupDD: function() {
2221             if (!YAHOO.util.DD) { return false; }
2222             if (this.get('drag')) {
2223                 var d = this.get('drag'),
2224                     dd = YAHOO.util.DD;
2225                 if (d === 'proxy') {
2226                     dd = YAHOO.util.DDProxy;
2227                 }
2229                 this.dd = new dd(this.get('element_cont').get('element'));
2230                 this.toolbar.addClass('draggable'); 
2231                 this.dd.setHandleElId(this.toolbar._titlebar); 
2232             }
2233         },
2234         /**
2235         * @property dd
2236         * @description A reference to the DragDrop object.
2237         * @type YAHOO.util.DD/YAHOO.util.DDProxy
2238         */
2239         dd: null,
2240         /**
2241         * @private
2242         * @property _lastCommand
2243         * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2244         * @type String
2245         */
2246         _lastCommand: null,
2247         _undoNodeChange: function() {},
2248         _storeUndo: function() {},
2249         /**
2250         * @private
2251         * @method _checkKey
2252         * @description Checks a keyMap entry against a key event
2253         * @param {Object} k The _keyMap object
2254         * @param {Event} e The Mouse Event
2255         * @return {Boolean}
2256         */
2257         _checkKey: function(k, e) {
2258             var ret = false;
2259             if ((e.keyCode === k.key)) {
2260                 if (k.mods && (k.mods.length > 0)) {
2261                     var val = 0;
2262                     for (var i = 0; i < k.mods.length; i++) {
2263                         if (this.browser.mac) {
2264                             if (k.mods[i] == 'ctrl') {
2265                                 k.mods[i] = 'meta';
2266                             }
2267                         }
2268                         if (e[k.mods[i] + 'Key'] === true) {
2269                             val++;
2270                         }
2271                     }
2272                     if (val === k.mods.length) {
2273                         ret = true;
2274                     }
2275                 } else {
2276                     ret = true;
2277                 }
2278             }
2279             return ret;
2280         },
2281         /**
2282         * @private
2283         * @property _keyMap
2284         * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>. 
2285         * This entry shows that when key 87 (W) is found with the modifiers of shift and control, the window will close. You can customize this object to tweak keyboard shortcuts.
2286         * @type {Object/Mixed}
2287         */
2288         _keyMap: {
2289             SELECT_ALL: {
2290                 key: 65, //A key
2291                 mods: ['ctrl']
2292             },
2293             CLOSE_WINDOW: {
2294                 key: 87, //W key
2295                 mods: ['shift', 'ctrl']
2296             },
2297             FOCUS_TOOLBAR: {
2298                 key: 27,
2299                 mods: ['shift']
2300             },
2301             FOCUS_AFTER: {
2302                 key: 27
2303             },
2304             CREATE_LINK: {
2305                 key: 76,
2306                 mods: ['shift', 'ctrl']
2307             },
2308             BOLD: {
2309                 key: 66,
2310                 mods: ['shift', 'ctrl']
2311             },
2312             ITALIC: {
2313                 key: 73,
2314                 mods: ['shift', 'ctrl']
2315             },
2316             UNDERLINE: {
2317                 key: 85,
2318                 mods: ['shift', 'ctrl']
2319             },
2320             UNDO: {
2321                 key: 90,
2322                 mods: ['ctrl']
2323             },
2324             REDO: {
2325                 key: 90,
2326                 mods: ['shift', 'ctrl']
2327             },
2328             JUSTIFY_LEFT: {
2329                 key: 219,
2330                 mods: ['shift', 'ctrl']
2331             },
2332             JUSTIFY_CENTER: {
2333                 key: 220,
2334                 mods: ['shift', 'ctrl']
2335             },
2336             JUSTIFY_RIGHT: {
2337                 key: 221,
2338                 mods: ['shift', 'ctrl']
2339             }
2340         },
2341         /**
2342         * @private
2343         * @method _cleanClassName
2344         * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2345         * @param {String} str The classname to clean up
2346         * @return {String}
2347         */
2348         _cleanClassName: function(str) {
2349             return str.replace(/ /g, '-').toLowerCase();
2350         },
2351         /**
2352         * @property _textarea
2353         * @description Flag to determine if we are using a textarea or an HTML Node.
2354         * @type Boolean
2355         */
2356         _textarea: null,
2357         /**
2358         * @property _docType
2359         * @description The DOCTYPE to use in the editable container.
2360         * @type String
2361         */
2362         _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2363         /**
2364         * @property editorDirty
2365         * @description This flag will be set when certain things in the Editor happen. It is to be used by the developer to check to see if content has changed.
2366         * @type Boolean
2367         */
2368         editorDirty: null,
2369         /**
2370         * @property _defaultCSS
2371         * @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' }
2372         * @type String
2373         */
2374         _defaultCSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } .warning-localfile { border-bottom: 1px dashed red !important; } .yui-busy { cursor: wait !important; } img.selected { border: 2px dotted #808080; } img { cursor: pointer !important; border: none; } body.ptags.webkit div { margin: 11px 0; }',
2375         /**
2376         * @property _defaultToolbar
2377         * @private
2378         * @description Default toolbar config.
2379         * @type Object
2380         */
2381         _defaultToolbar: null,
2382         /**
2383         * @property _lastButton
2384         * @private
2385         * @description The last button pressed, so we don't disable it.
2386         * @type Object
2387         */
2388         _lastButton: null,
2389         /**
2390         * @property _baseHREF
2391         * @private
2392         * @description The base location of the editable page (this page) so that relative paths for image work.
2393         * @type String
2394         */
2395         _baseHREF: function() {
2396             var href = document.location.href;
2397             if (href.indexOf('?') !== -1) { //Remove the query string
2398                 href = href.substring(0, href.indexOf('?'));
2399             }
2400             href = href.substring(0, href.lastIndexOf('/')) + '/';
2401             return href;
2402         }(),
2403         /**
2404         * @property _lastImage
2405         * @private
2406         * @description Safari reference for the last image selected (for styling as selected).
2407         * @type HTMLElement
2408         */
2409         _lastImage: null,
2410         /**
2411         * @property _blankImageLoaded
2412         * @private
2413         * @description Don't load the blank image more than once..
2414         * @type Boolean
2415         */
2416         _blankImageLoaded: null,
2417         /**
2418         * @property _fixNodesTimer
2419         * @private
2420         * @description Holder for the fixNodes timer
2421         * @type Date
2422         */
2423         _fixNodesTimer: null,
2424         /**
2425         * @property _nodeChangeTimer
2426         * @private
2427         * @description Holds a reference to the nodeChange setTimeout call
2428         * @type Number
2429         */
2430         _nodeChangeTimer: null,
2431         /**
2432         * @property _lastNodeChangeEvent
2433         * @private
2434         * @description Flag to determine the last event that fired a node change
2435         * @type Event
2436         */
2437         _lastNodeChangeEvent: null,
2438         /**
2439         * @property _lastNodeChange
2440         * @private
2441         * @description Flag to determine when the last node change was fired
2442         * @type Date
2443         */
2444         _lastNodeChange: 0,
2445         /**
2446         * @property _rendered
2447         * @private
2448         * @description Flag to determine if editor has been rendered or not
2449         * @type Boolean
2450         */
2451         _rendered: null,
2452         /**
2453         * @property DOMReady
2454         * @private
2455         * @description Flag to determine if DOM is ready or not
2456         * @type Boolean
2457         */
2458         DOMReady: null,
2459         /**
2460         * @property _selection
2461         * @private
2462         * @description Holder for caching iframe selections
2463         * @type Object
2464         */
2465         _selection: null,
2466         /**
2467         * @property _mask
2468         * @private
2469         * @description DOM Element holder for the editor Mask when disabled
2470         * @type Object
2471         */
2472         _mask: null,
2473         /**
2474         * @property _showingHiddenElements
2475         * @private
2476         * @description Status of the hidden elements button
2477         * @type Boolean
2478         */
2479         _showingHiddenElements: null,
2480         /**
2481         * @property currentWindow
2482         * @description A reference to the currently open EditorWindow
2483         * @type Object
2484         */
2485         currentWindow: null,
2486         /**
2487         * @property currentEvent
2488         * @description A reference to the current editor event
2489         * @type Event
2490         */
2491         currentEvent: null,
2492         /**
2493         * @property operaEvent
2494         * @private
2495         * @description setTimeout holder for Opera and Image DoubleClick event..
2496         * @type Object
2497         */
2498         operaEvent: null,
2499         /**
2500         * @property currentFont
2501         * @description A reference to the last font selected from the Toolbar
2502         * @type HTMLElement
2503         */
2504         currentFont: null,
2505         /**
2506         * @property currentElement
2507         * @description A reference to the current working element in the editor
2508         * @type Array
2509         */
2510         currentElement: null,
2511         /**
2512         * @property dompath
2513         * @description A reference to the dompath container for writing the current working dom path to.
2514         * @type HTMLElement
2515         */
2516         dompath: null,
2517         /**
2518         * @property beforeElement
2519         * @description A reference to the H2 placed before the editor for Accessibilty.
2520         * @type HTMLElement
2521         */
2522         beforeElement: null,
2523         /**
2524         * @property afterElement
2525         * @description A reference to the H2 placed after the editor for Accessibilty.
2526         * @type HTMLElement
2527         */
2528         afterElement: null,
2529         /**
2530         * @property invalidHTML
2531         * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found. If you set the value of a key to "{ keepContents: true }", then the element will be replaced with a yui-non span to be filtered out when cleanHTML is called. The only tag that is ignored here is the span tag as it will force the Editor into a loop and freeze the browser. However.. all of these tags will be removed in the cleanHTML routine.
2532         * @type Object
2533         */
2534         invalidHTML: {
2535             form: true,
2536             input: true,
2537             button: true,
2538             select: true,
2539             link: true,
2540             html: true,
2541             body: true,
2542             iframe: true,
2543             script: true,
2544             style: true,
2545             textarea: true
2546         },
2547         /**
2548         * @property toolbar
2549         * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2550         * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2551         */
2552         toolbar: null,
2553         /**
2554         * @private
2555         * @property _contentTimer
2556         * @description setTimeout holder for documentReady check
2557         */
2558         _contentTimer: null,
2559         /**
2560         * @private
2561         * @property _contentTimerCounter
2562         * @description Counter to check the number of times the body is polled for before giving up
2563         * @type Number
2564         */
2565         _contentTimerCounter: 0,
2566         /**
2567         * @private
2568         * @property _disabled
2569         * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2570         * @type Array
2571         */
2572         _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2573         /**
2574         * @private
2575         * @property _alwaysDisabled
2576         * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2577         * @type Object
2578         */
2579         _alwaysDisabled: { undo: true, redo: true },
2580         /**
2581         * @private
2582         * @property _alwaysEnabled
2583         * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2584         * @type Object
2585         */
2586         _alwaysEnabled: { },
2587         /**
2588         * @private
2589         * @property _semantic
2590         * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2591         * @type Object
2592         */
2593         _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2594         /**
2595         * @private
2596         * @property _tag2cmd
2597         * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2598         * @type Object
2599         */
2600         _tag2cmd: {
2601             'b': 'bold',
2602             'strong': 'bold',
2603             'i': 'italic',
2604             'em': 'italic',
2605             'u': 'underline',
2606             'sup': 'superscript',
2607             'sub': 'subscript',
2608             'img': 'insertimage',
2609             'a' : 'createlink',
2610             'ul' : 'insertunorderedlist',
2611             'ol' : 'insertorderedlist'
2612         },
2614         /**
2615         * @private _createIframe
2616         * @description Creates the DOM and YUI Element for the iFrame editor area.
2617         * @param {String} id The string ID to prefix the iframe with
2618         * @return {Object} iFrame object
2619         */
2620         _createIframe: function() {
2621             var ifrmDom = document.createElement('iframe');
2622             ifrmDom.id = this.get('id') + '_editor';
2623             var config = {
2624                 border: '0',
2625                 frameBorder: '0',
2626                 marginWidth: '0',
2627                 marginHeight: '0',
2628                 leftMargin: '0',
2629                 topMargin: '0',
2630                 allowTransparency: 'true',
2631                 width: '100%'
2632             };
2633             if (this.get('autoHeight')) {
2634                 config.scrolling = 'no';
2635             }
2636             for (var i in config) {
2637                 if (Lang.hasOwnProperty(config, i)) {
2638                     ifrmDom.setAttribute(i, config[i]);
2639                 }
2640             }
2641             var isrc = 'javascript:;';
2642             if (this.browser.ie) {
2643                 //isrc = 'about:blank';
2644                 //TODO - Check this, I have changed it before..
2645                 isrc = 'javascript:false;';
2646             }
2647             ifrmDom.setAttribute('src', isrc);
2648             var ifrm = new YAHOO.util.Element(ifrmDom);
2649             ifrm.setStyle('visibility', 'hidden');
2650             return ifrm;
2651         },
2652         /**
2653         * @private _isElement
2654         * @description Checks to see if an Element reference is a valid one and has a certain tag type
2655         * @param {HTMLElement} el The element to check
2656         * @param {String} tag The tag that the element needs to be
2657         * @return {Boolean}
2658         */
2659         _isElement: function(el, tag) {
2660             if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2661                 return true;
2662             }
2663             if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2664                 return true;
2665             }
2666             return false;
2667         },
2668         /**
2669         * @private _hasParent
2670         * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2671         * @param {HTMLElement} el The element to check
2672         * @param {String} tag The tag that the element needs to be
2673         * @return HTMLElement
2674         */
2675         _hasParent: function(el, tag) {
2676             if (!el || !el.parentNode) {
2677                 return false;
2678             }
2679             
2680             while (el.parentNode) {
2681                 if (this._isElement(el, tag)) {
2682                     return el;
2683                 }
2684                 if (el.parentNode) {
2685                     el = el.parentNode;
2686                 } else {
2687                     return false;
2688                 }
2689             }
2690             return false;
2691         },
2692         /**
2693         * @private
2694         * @method _getDoc
2695         * @description Get the Document of the IFRAME
2696         * @return {Object}
2697         */
2698         _getDoc: function() {
2699             var value = false;
2700             if (this.get) {
2701                 if (this.get('iframe')) {
2702                     if (this.get('iframe').get) {
2703                         if (this.get('iframe').get('element')) {
2704                             try {
2705                                 if (this.get('iframe').get('element').contentWindow) {
2706                                     if (this.get('iframe').get('element').contentWindow.document) {
2707                                         value = this.get('iframe').get('element').contentWindow.document;
2708                                         return value;
2709                                     }
2710                                 }
2711                             } catch (e) {}
2712                         }
2713                     }
2714                 }
2715             }
2716             return false;
2717         },
2718         /**
2719         * @private
2720         * @method _getWindow
2721         * @description Get the Window of the IFRAME
2722         * @return {Object}
2723         */
2724         _getWindow: function() {
2725             return this.get('iframe').get('element').contentWindow;
2726         },
2727         /**
2728         * @private
2729         * @method _focusWindow
2730         * @description Attempt to set the focus of the iframes window.
2731         * @param {Boolean} onLoad Safari needs some special care to set the cursor in the iframe
2732         */
2733         _focusWindow: function(onLoad) {
2734             if (this.browser.webkit) {
2735                 if (onLoad) {
2736                     /**
2737                     * @knownissue Safari Cursor Position
2738                     * @browser Safari 2.x
2739                     * @description Can't get Safari to place the cursor at the beginning of the text..
2740                     * This workaround at least set's the toolbar into the proper state.
2741                     */
2742                     this._getSelection().setBaseAndExtent(this._getDoc().body.firstChild, 0, this._getDoc().body.firstChild, 1);
2743                     if (this.browser.webkit3) {
2744                         this._getSelection().collapseToStart();
2745                     } else {
2746                         this._getSelection().collapse(false);
2747                     }
2748                 } else {
2749                     this._getSelection().setBaseAndExtent(this._getDoc().body, 1, this._getDoc().body, 1);
2750                     if (this.browser.webkit3) {
2751                         this._getSelection().collapseToStart();
2752                     } else {
2753                         this._getSelection().collapse(false);
2754                     }
2755                 }
2756                 this._getWindow().focus();
2757             } else {
2758                 this._getWindow().focus();
2759             }
2760         },
2761         /**
2762         * @private
2763         * @method _hasSelection
2764         * @description Determines if there is a selection in the editor document.
2765         * @return {Boolean}
2766         */
2767         _hasSelection: function() {
2768             var sel = this._getSelection();
2769             var range = this._getRange();
2770             var hasSel = false;
2772             if (!sel || !range) {
2773                 return hasSel;
2774             }
2776             //Internet Explorer
2777             if (this.browser.ie || this.browser.opera) {
2778                 if (range.text) {
2779                     hasSel = true;
2780                 }
2781                 if (range.html) {
2782                     hasSel = true;
2783                 }
2784             } else {
2785                 if (this.browser.webkit) {
2786                     if (sel+'' !== '') {
2787                         hasSel = true;
2788                     }
2789                 } else {
2790                     if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2791                         hasSel = true;
2792                     }
2793                 }
2794             }
2795             return hasSel;
2796         },
2797         /**
2798         * @private
2799         * @method _getSelection
2800         * @description Handles the different selection objects across the A-Grade list.
2801         * @return {Object} Selection Object
2802         */
2803         _getSelection: function() {
2804             var _sel = null;
2805             if (this._getDoc() && this._getWindow()) {
2806                 if (this._getDoc().selection) {
2807                     _sel = this._getDoc().selection;
2808                 } else {
2809                     _sel = this._getWindow().getSelection();
2810                 }
2811                 //Handle Safari's lack of Selection Object
2812                 if (this.browser.webkit) {
2813                     if (_sel.baseNode) {
2814                             this._selection = {};
2815                             this._selection.baseNode = _sel.baseNode;
2816                             this._selection.baseOffset = _sel.baseOffset;
2817                             this._selection.extentNode = _sel.extentNode;
2818                             this._selection.extentOffset = _sel.extentOffset;
2819                     } else if (this._selection !== null) {
2820                         _sel = this._getWindow().getSelection();
2821                         _sel.setBaseAndExtent(
2822                             this._selection.baseNode,
2823                             this._selection.baseOffset,
2824                             this._selection.extentNode,
2825                             this._selection.extentOffset);
2826                         this._selection = null;
2827                     }
2828                 }
2829             }
2830             return _sel;
2831         },
2832         /**
2833         * @private
2834         * @method _selectNode
2835         * @description Places the highlight around a given node
2836         * @param {HTMLElement} node The node to select
2837         */
2838         _selectNode: function(node, collapse) {
2839             if (!node) {
2840                 return false;
2841             }
2842             var sel = this._getSelection(),
2843                 range = null;
2845             if (this.browser.ie) {
2846                 try { //IE freaks out here sometimes..
2847                     range = this._getDoc().body.createTextRange();
2848                     range.moveToElementText(node);
2849                     range.select();
2850                 } catch (e) {
2851                 }
2852             } else if (this.browser.webkit) {
2853                 if (collapse) {
2854                                     sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2855                 } else {
2856                                     sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2857                 }
2858             } else if (this.browser.opera) {
2859                 sel = this._getWindow().getSelection();
2860                 range = this._getDoc().createRange();
2861                 range.selectNode(node);
2862                 sel.removeAllRanges();
2863                 sel.addRange(range);
2864             } else {
2865                 range = this._getDoc().createRange();
2866                 range.selectNodeContents(node);
2867                 sel.removeAllRanges();
2868                 sel.addRange(range);
2869             }
2870             //TODO - Check Performance
2871             this.nodeChange();
2872         },
2873         /**
2874         * @private
2875         * @method _getRange
2876         * @description Handles the different range objects across the A-Grade list.
2877         * @return {Object} Range Object
2878         */
2879         _getRange: function() {
2880             var sel = this._getSelection();
2882             if (sel === null) {
2883                 return null;
2884             }
2886             if (this.browser.webkit && !sel.getRangeAt) {
2887                 var _range = this._getDoc().createRange();
2888                 try {
2889                     _range.setStart(sel.anchorNode, sel.anchorOffset);
2890                     _range.setEnd(sel.focusNode, sel.focusOffset);
2891                 } catch (e) {
2892                     _range = this._getWindow().getSelection()+'';
2893                 }
2894                 return _range;
2895             }
2897             if (this.browser.ie || this.browser.opera) {
2898                 try {
2899                     return sel.createRange();
2900                 } catch (e2) {
2901                     return null;
2902                 }
2903             }
2905             if (sel.rangeCount > 0) {
2906                 return sel.getRangeAt(0);
2907             }
2908             return null;
2909         },
2910         /**
2911         * @private
2912         * @method _setDesignMode
2913         * @description Sets the designMode of the iFrame document.
2914         * @param {String} state This should be either on or off
2915         */
2916         _setDesignMode: function(state) {
2917             try {
2918                 var set = true;
2919                 //SourceForge Bug #1807057
2920                 if (this.browser.ie && (state.toLowerCase() == 'off')) {
2921                     set = false;
2922                 }
2923                 if (set) {
2924                     this._getDoc().designMode = state;
2925                 }
2926             } catch(e) { }
2927         },
2928         /**
2929         * @private
2930         * @method _toggleDesignMode
2931         * @description Toggles the designMode of the iFrame document on and off.
2932         * @return {String} The state that it was set to.
2933         */
2934         _toggleDesignMode: function() {
2935             var _dMode = this._getDoc().designMode.toLowerCase(),
2936                 _state = 'on';
2937             if (_dMode == 'on') {
2938                 _state = 'off';
2939             }
2940             this._setDesignMode(_state);
2941             return _state;
2942         },
2943         /**
2944         * @private
2945         * @method _initEditorEvents
2946         * @description This method sets up the listeners on the Editors document.
2947         */
2948         _initEditorEvents: function() {
2949             //Setup Listeners on iFrame
2950             var doc = this._getDoc();
2951             Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
2952             Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
2953             Event.on(doc, 'click', this._handleClick, this, true);
2954             Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
2955             Event.on(doc, 'keypress', this._handleKeyPress, this, true);
2956             Event.on(doc, 'keyup', this._handleKeyUp, this, true);
2957             Event.on(doc, 'keydown', this._handleKeyDown, this, true);
2958         },
2959         /**
2960         * @private
2961         * @method _removeEditorEvents
2962         * @description This method removes the listeners on the Editors document (for disabling).
2963         */
2964         _removeEditorEvents: function() {
2965             //Remove Listeners on iFrame
2966             var doc = this._getDoc();
2967             Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
2968             Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
2969             Event.removeListener(doc, 'click', this._handleClick, this, true);
2970             Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
2971             Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
2972             Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
2973             Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
2974         },
2975         /**
2976         * @private
2977         * @method _initEditor
2978         * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
2979         */
2980         _initEditor: function() {
2981             if (this.browser.ie) {
2982                 this._getDoc().body.style.margin = '0';
2983             }
2984             if (!this.get('disabled')) {
2985                 if (this._getDoc().designMode.toLowerCase() != 'on') {
2986                     this._setDesignMode('on');
2987                     this._contentTimerCounter = 0;
2988                 }
2989             }
2990             if (!this._getDoc().body) {
2991                 this._contentTimerCounter = 0;
2992                 this._checkLoaded();
2993                 return false;
2994             }
2995             
2996             this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
2997             if (!this.get('disabled')) {
2998                 this._initEditorEvents();
2999                 this.toolbar.set('disabled', false);
3000             }
3002             this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3003             if (this.get('dompath')) {
3004                 var self = this;
3005                 setTimeout(function() {
3006                     self._writeDomPath.call(self);
3007                     self._setupResize.call(self);
3008                 }, 150);
3009             }
3010             var br = [];
3011             for (var i in this.browser) {
3012                 if (this.browser[i]) {
3013                     br.push(i);
3014                 }
3015             }
3016             if (this.get('ptags')) {
3017                 br.push('ptags');
3018             }
3019             Dom.addClass(this._getDoc().body, br.join(' '));
3020             this.nodeChange(true);
3021         },
3022         /**
3023         * @private
3024         * @method _checkLoaded
3025         * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3026         */
3027         _checkLoaded: function() {
3028             this._contentTimerCounter++;
3029             if (this._contentTimer) {
3030                 clearTimeout(this._contentTimer);
3031             }
3032             if (this._contentTimerCounter > 500) {
3033                 return false;
3034             }
3035             var init = false;
3036             try {
3037                 if (this._getDoc() && this._getDoc().body) {
3038                     if (this.browser.ie) {
3039                         if (this._getDoc().body.readyState == 'complete') {
3040                             init = true;
3041                         }
3042                     } else {
3043                         if (this._getDoc().body._rteLoaded === true) {
3044                             init = true;
3045                         }
3046                     }
3047                 }
3048             } catch (e) {
3049                 init = false;
3050             }
3052             if (init === true) {
3053                 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3054                 this._initEditor();
3055             } else {
3056                 var self = this;
3057                 this._contentTimer = setTimeout(function() {
3058                     self._checkLoaded.call(self);
3059                 }, 20);
3060             }
3061         },
3062         /**
3063         * @private
3064         * @method _setInitialContent
3065         * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3066         */
3067         _setInitialContent: function() {
3069             var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3070                 doc = null;
3072             var html = Lang.substitute(this.get('html'), {
3073                 TITLE: this.STR_TITLE,
3074                 CONTENT: this._cleanIncomingHTML(value),
3075                 CSS: this.get('css'),
3076                 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3077                 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3078             }),
3079             check = true;
3080             if (document.compatMode != 'BackCompat') {
3081                 html = this._docType + "\n" + html;
3082             } else {
3083             }
3085             if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3086                 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3087                 try {
3088                     //Adobe AIR Code
3089                     if (this.browser.air) {
3090                         doc = this._getDoc().implementation.createHTMLDocument();
3091                         var origDoc = this._getDoc();
3092                         origDoc.open();
3093                         origDoc.close();
3094                         doc.open();
3095                         doc.write(html);
3096                         doc.close();
3097                         var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3098                         origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3099                         origDoc.body._rteLoaded = true;
3100                     } else {
3101                         doc = this._getDoc();
3102                         doc.open();
3103                         doc.write(html);
3104                         doc.close();
3105                     }
3106                 } catch (e) {
3107                     //Safari will only be here if we are hidden
3108                     check = false;
3109                 }
3110             } else {
3111                 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3112                 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3113             }
3114             this.get('iframe').setStyle('visibility', '');
3115             if (check) {
3116                 this._checkLoaded();
3117             }            
3118         },
3119         /**
3120         * @private
3121         * @method _setMarkupType
3122         * @param {String} action The action to take. Possible values are: css, default or semantic
3123         * @description This method will turn on/off the useCSS execCommand.
3124         */
3125         _setMarkupType: function(action) {
3126             switch (this.get('markup')) {
3127                 case 'css':
3128                     this._setEditorStyle(true);
3129                     break;
3130                 case 'default':
3131                     this._setEditorStyle(false);
3132                     break;
3133                 case 'semantic':
3134                 case 'xhtml':
3135                     if (this._semantic[action]) {
3136                         this._setEditorStyle(false);
3137                     } else {
3138                         this._setEditorStyle(true);
3139                     }
3140                     break;
3141             }
3142         },
3143         /**
3144         * Set the editor to use CSS instead of HTML
3145         * @param {Booleen} stat True/False
3146         */
3147         _setEditorStyle: function(stat) {
3148             try {
3149                 this._getDoc().execCommand('useCSS', false, !stat);
3150             } catch (ex) {
3151             }
3152         },
3153         /**
3154         * @private
3155         * @method _getSelectedElement
3156         * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3157         * @return {HTMLElement} The currently selected element.
3158         */
3159         _getSelectedElement: function() {
3160             var doc = this._getDoc(),
3161                 range = null,
3162                 sel = null,
3163                 elm = null,
3164                 check = true;
3166             if (this.browser.ie) {
3167                 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3168                 range = this._getRange();
3169                 if (range) {
3170                     elm = range.item ? range.item(0) : range.parentElement();
3171                     if (this._hasSelection()) {
3172                         //TODO
3173                         //WTF.. Why can't I get an element reference here?!??!
3174                     }
3175                     if (elm === doc.body) {
3176                         elm = null;
3177                     }
3178                 }
3179                 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3180                     elm = Event.getTarget(this.currentEvent);
3181                 }
3182             } else {
3183                 sel = this._getSelection();
3184                 range = this._getRange();
3186                 if (!sel || !range) {
3187                     return null;
3188                 }
3189                 //TODO
3190                 if (!this._hasSelection() && this.browser.webkit3) {
3191                     //check = false;
3192                 }
3193                 if (this.browser.gecko) {
3194                     //Added in 2.6.0
3195                     if (range.startContainer) {
3196                         check = false;
3197                         if (range.startContainer.nodeType === 3) {
3198                             elm = range.startContainer.parentNode;
3199                         } else if (range.startContainer.nodeType === 1) {
3200                             elm = range.startContainer;
3201                         } else {
3202                             check = true;
3203                         }
3204                         if (!check) {
3205                             this.currentEvent = null;
3206                         }
3207                     }
3208                 }
3209                 if (check) {
3210                     if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3211                         if (sel.anchorNode.parentNode) { //next check parentNode
3212                             elm = sel.anchorNode.parentNode;
3213                         }
3214                         if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3215                             elm = sel.anchorNode.nextSibling;
3216                         }
3217                     }
3218                     if (this._isElement(elm, 'br')) {
3219                         elm = null;
3220                     }
3221                     if (!elm) {
3222                         elm = range.commonAncestorContainer;
3223                         if (!range.collapsed) {
3224                             if (range.startContainer == range.endContainer) {
3225                                 if (range.startOffset - range.endOffset < 2) {
3226                                     if (range.startContainer.hasChildNodes()) {
3227                                         elm = range.startContainer.childNodes[range.startOffset];
3228                                     }
3229                                 }
3230                             }
3231                         }
3232                     }
3233                }
3234             }
3235             
3236             if (this.currentEvent !== null) {
3237                 try {
3238                     switch (this.currentEvent.type) {
3239                         case 'click':
3240                         case 'mousedown':
3241                         case 'mouseup':
3242                             if (this.browser.webkit) {
3243                                 elm = Event.getTarget(this.currentEvent);
3244                             }
3245                             break;
3246                         default:
3247                             //Do nothing
3248                             break;
3249                     }
3250                 } catch (e) {
3251                 }
3252             } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3253                 //TODO is this still needed?
3254                 //elm = this.currentElement[0];
3255             }
3258             if (this.browser.opera || this.browser.webkit) {
3259                 if (this.currentEvent && !elm) {
3260                     elm = YAHOO.util.Event.getTarget(this.currentEvent);
3261                 }
3262             }
3263             if (!elm || !elm.tagName) {
3264                 elm = doc.body;
3265             }
3266             if (this._isElement(elm, 'html')) {
3267                 //Safari sometimes gives us the HTML node back..
3268                 elm = doc.body;
3269             }
3270             if (this._isElement(elm, 'body')) {
3271                 //make sure that body means this body not the parent..
3272                 elm = doc.body;
3273             }
3274             if (elm && !elm.parentNode) { //Not in document
3275                 elm = doc.body;
3276             }
3277             if (elm === undefined) {
3278                 elm = null;
3279             }
3280             return elm;
3281         },
3282         /**
3283         * @private
3284         * @method _getDomPath
3285         * @description This method will attempt to build the DOM path from the currently selected element.
3286         * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3287         * @return {Array} An array of node references that will create the DOM Path.
3288         */
3289         _getDomPath: function(el) {
3290             if (!el) {
3291                             el = this._getSelectedElement();
3292             }
3293                         var domPath = [];
3294             while (el !== null) {
3295                 if (el.ownerDocument != this._getDoc()) {
3296                     el = null;
3297                     break;
3298                 }
3299                 //Check to see if we get el.nodeName and nodeType
3300                 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3301                     domPath[domPath.length] = el;
3302                 }
3304                 if (this._isElement(el, 'body')) {
3305                     break;
3306                 }
3308                 el = el.parentNode;
3309             }
3310             if (domPath.length === 0) {
3311                 if (this._getDoc() && this._getDoc().body) {
3312                     domPath[0] = this._getDoc().body;
3313                 }
3314             }
3315             return domPath.reverse();
3316         },
3317         /**
3318         * @private
3319         * @method _writeDomPath
3320         * @description Write the current DOM path out to the dompath container below the editor.
3321         */
3322         _writeDomPath: function() { 
3323             var path = this._getDomPath(),
3324                 pathArr = [],
3325                 classPath = '',
3326                 pathStr = '';
3327             for (var i = 0; i < path.length; i++) {
3328                 var tag = path[i].tagName.toLowerCase();
3329                 if ((tag == 'ol') && (path[i].type)) {
3330                     tag += ':' + path[i].type;
3331                 }
3332                 if (Dom.hasClass(path[i], 'yui-tag')) {
3333                     tag = path[i].getAttribute('tag');
3334                 }
3335                 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3336                     switch (tag) {
3337                         case 'b': tag = 'strong'; break;
3338                         case 'i': tag = 'em'; break;
3339                     }
3340                 }
3341                 if (!Dom.hasClass(path[i], 'yui-non')) {
3342                     if (Dom.hasClass(path[i], 'yui-tag')) {
3343                         pathStr = tag;
3344                     } else {
3345                         classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3346                         if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3347                             classPath = '';
3348                         }
3349                         pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3350                     }
3351                     switch (tag) {
3352                         case 'body':
3353                             pathStr = 'body';
3354                             break;
3355                         case 'a':
3356                             if (path[i].getAttribute('href', 2)) {
3357                                 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3358                             }
3359                             break;
3360                         case 'img':
3361                             var h = path[i].height;
3362                             var w = path[i].width;
3363                             if (path[i].style.height) {
3364                                 h = parseInt(path[i].style.height, 10);
3365                             }
3366                             if (path[i].style.width) {
3367                                 w = parseInt(path[i].style.width, 10);
3368                             }
3369                             pathStr += '(' + w + 'x' + h + ')';
3370                         break;
3371                     }
3373                     if (pathStr.length > 10) {
3374                         pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3375                     } else {
3376                         pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3377                     }
3378                     pathArr[pathArr.length] = pathStr;
3379                 }
3380             }
3381             var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3382             //Prevent flickering
3383             if (this.dompath.innerHTML != str) {
3384                 this.dompath.innerHTML = str;
3385             }
3386         },
3387         /**
3388         * @private
3389         * @method _fixNodes
3390         * @description Fix href and imgs as well as remove invalid HTML.
3391         */
3392         _fixNodes: function() {
3393             var doc = this._getDoc(),
3394                 els = [];
3396             for (var v in this.invalidHTML) {
3397                 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3398                     if (v.toLowerCase() != 'span') {
3399                         var tags = doc.body.getElementsByTagName(v);
3400                         if (tags.length) {
3401                             for (var i = 0; i < tags.length; i++) {
3402                                 els.push(tags[i]);
3403                             }
3404                         }
3405                     }
3406                 }
3407             }
3408             for (var h = 0; h < els.length; h++) {
3409                 if (els[h].parentNode) {
3410                     if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3411                         this._swapEl(els[h], 'span', function(el) {
3412                             el.className = 'yui-non';
3413                         });
3414                     } else {
3415                         els[h].parentNode.removeChild(els[h]);
3416                     }
3417                 }
3418             }
3419             var imgs = this._getDoc().getElementsByTagName('img');
3420             Dom.addClass(imgs, 'yui-img');   
3421         },
3422         /**
3423         * @private
3424         * @method _isNonEditable
3425         * @param Event ev The Dom event being checked
3426         * @description Method is called at the beginning of all event handlers to check if this element or a parent element has the class yui-noedit (this.CLASS_NOEDIT) applied.
3427         * If it does, then this method will stop the event and return true. The event handlers will then return false and stop the nodeChange from occuring. This method will also
3428         * disable and enable the Editor's toolbar based on the noedit state.
3429         * @return Boolean
3430         */
3431         _isNonEditable: function(ev) {
3432             if (this.get('allowNoEdit')) {
3433                 var el = Event.getTarget(ev);
3434                 if (this._isElement(el, 'html')) {
3435                     el = null;
3436                 }
3437                 var path = this._getDomPath(el);
3438                 for (var i = (path.length - 1); i > -1; i--) {
3439                     if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3440                         //if (this.toolbar.get('disabled') === false) {
3441                         //    this.toolbar.set('disabled', true);
3442                         //}
3443                         try {
3444                              this._getDoc().execCommand('enableObjectResizing', false, 'false');
3445                         } catch (e) {}
3446                         this.nodeChange();
3447                         Event.stopEvent(ev);
3448                         return true;
3449                     }
3450                 }
3451                 //if (this.toolbar.get('disabled') === true) {
3452                     //Should only happen once..
3453                     //this.toolbar.set('disabled', false);
3454                     try {
3455                          this._getDoc().execCommand('enableObjectResizing', false, 'true');
3456                     } catch (e2) {}
3457                 //}
3458             }
3459             return false;
3460         },
3461         /**
3462         * @private
3463         * @method _setCurrentEvent
3464         * @param {Event} ev The event to cache
3465         * @description Sets the current event property
3466         */
3467         _setCurrentEvent: function(ev) {
3468             this.currentEvent = ev;
3469         },
3470         /**
3471         * @private
3472         * @method _handleClick
3473         * @param {Event} ev The event we are working on.
3474         * @description Handles all click events inside the iFrame document.
3475         */
3476         _handleClick: function(ev) {
3477             var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3478             if (ret === false) {
3479                 return false;
3480             }
3481             if (this._isNonEditable(ev)) {
3482                 return false;
3483             }
3484             this._setCurrentEvent(ev);
3485             if (this.currentWindow) {
3486                 this.closeWindow();
3487             }
3488             if (this.currentWindow) {
3489                 this.closeWindow();
3490             }
3491             if (this.browser.webkit) {
3492                 var tar =Event.getTarget(ev);
3493                 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3494                     Event.stopEvent(ev);
3495                     this.nodeChange();
3496                 }
3497             } else {
3498                 this.nodeChange();
3499             }
3500             this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3501         },
3502         /**
3503         * @private
3504         * @method _handleMouseUp
3505         * @param {Event} ev The event we are working on.
3506         * @description Handles all mouseup events inside the iFrame document.
3507         */
3508         _handleMouseUp: function(ev) {
3509             var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3510             if (ret === false) {
3511                 return false;
3512             }
3513             if (this._isNonEditable(ev)) {
3514                 return false;
3515             }
3516             //Don't set current event for mouseup.
3517             //It get's fired after a menu is closed and gives up a bogus event to work with
3518             //this._setCurrentEvent(ev);
3519             var self = this;
3520             if (this.browser.opera) {
3521                 /**
3522                 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3523                 * @browser Opera
3524                 * @description This work around traps the MouseUp event and sets a timer to check if another MouseUp event fires in so many seconds. If another event is fired, they we internally fire the DoubleClick event.
3525                 */
3526                 var sel = Event.getTarget(ev);
3527                 if (this._isElement(sel, 'img')) {
3528                     this.nodeChange();
3529                     if (this.operaEvent) {
3530                         clearTimeout(this.operaEvent);
3531                         this.operaEvent = null;
3532                         this._handleDoubleClick(ev);
3533                     } else {
3534                         this.operaEvent = window.setTimeout(function() {
3535                             self.operaEvent = false;
3536                         }, 700);
3537                     }
3538                 }
3539             }
3540             //This will stop Safari from selecting the entire document if you select all the text in the editor
3541             if (this.browser.webkit || this.browser.opera) {
3542                 if (this.browser.webkit) {
3543                     Event.stopEvent(ev);
3544                 }
3545             }
3546             this.nodeChange();
3547             this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3548         },
3549         /**
3550         * @private
3551         * @method _handleMouseDown
3552         * @param {Event} ev The event we are working on.
3553         * @description Handles all mousedown events inside the iFrame document.
3554         */
3555         _handleMouseDown: function(ev) {
3556             var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3557             if (ret === false) {
3558                 return false;
3559             }
3560             if (this._isNonEditable(ev)) {
3561                 return false;
3562             }
3563             this._setCurrentEvent(ev);
3564             var sel = Event.getTarget(ev);
3565             if (this.browser.webkit && this._hasSelection()) {
3566                 var _sel = this._getSelection();
3567                 if (!this.browser.webkit3) {
3568                     _sel.collapse(true);
3569                 } else {
3570                     _sel.collapseToStart();
3571                 }
3572             }
3573             if (this.browser.webkit && this._lastImage) {
3574                 Dom.removeClass(this._lastImage, 'selected');
3575                 this._lastImage = null;
3576             }
3577             if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3578                 if (this.browser.webkit) {
3579                     Event.stopEvent(ev);
3580                     if (this._isElement(sel, 'img')) {
3581                         Dom.addClass(sel, 'selected');
3582                         this._lastImage = sel;
3583                     }
3584                 }
3585                 if (this.currentWindow) {
3586                     this.closeWindow();
3587                 }
3588                 this.nodeChange();
3589             }
3590             this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3591         },
3592         /**
3593         * @private
3594         * @method _handleDoubleClick
3595         * @param {Event} ev The event we are working on.
3596         * @description Handles all doubleclick events inside the iFrame document.
3597         */
3598         _handleDoubleClick: function(ev) {
3599             var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3600             if (ret === false) {
3601                 return false;
3602             }
3603             if (this._isNonEditable(ev)) {
3604                 return false;
3605             }
3606             this._setCurrentEvent(ev);
3607             var sel = Event.getTarget(ev);
3608             if (this._isElement(sel, 'img')) {
3609                 this.currentElement[0] = sel;
3610                 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3611                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3612             } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3613                 this.currentElement[0] = this._hasParent(sel, 'a');
3614                 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3615                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3616             }
3617             this.nodeChange();
3618             this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3619         },
3620         /**
3621         * @private
3622         * @method _handleKeyUp
3623         * @param {Event} ev The event we are working on.
3624         * @description Handles all keyup events inside the iFrame document.
3625         */
3626         _handleKeyUp: function(ev) {
3627             var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3628             if (ret === false) {
3629                 return false;
3630             }
3631             if (this._isNonEditable(ev)) {
3632                 return false;
3633             }
3634             this._setCurrentEvent(ev);
3635             switch (ev.keyCode) {
3636                 case this._keyMap.SELECT_ALL.key:
3637                     if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3638                         this.nodeChange();
3639                     }
3640                     break;
3641                 case 32: //Space Bar
3642                 case 35: //End
3643                 case 36: //Home
3644                 case 37: //Left Arrow
3645                 case 38: //Up Arrow
3646                 case 39: //Right Arrow
3647                 case 40: //Down Arrow
3648                 case 46: //Forward Delete
3649                 case 8: //Delete
3650                 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3651                     if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3652                         if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3653                             this.closeWindow();
3654                         }
3655                     } else {
3656                         if (!this.browser.ie) {
3657                             if (this._nodeChangeTimer) {
3658                                 clearTimeout(this._nodeChangeTimer);
3659                             }
3660                             var self = this;
3661                             this._nodeChangeTimer = setTimeout(function() {
3662                                 self._nodeChangeTimer = null;
3663                                 self.nodeChange.call(self);
3664                             }, 100);
3665                         } else {
3666                             this.nodeChange();
3667                         }
3668                         this.editorDirty = true;
3669                     }
3670                     break;
3671             }
3672             this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3673             this._storeUndo();
3674         },
3675         /**
3676         * @private
3677         * @method _handleKeyPress
3678         * @param {Event} ev The event we are working on.
3679         * @description Handles all keypress events inside the iFrame document.
3680         */
3681         _handleKeyPress: function(ev) {
3682             var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3683             if (ret === false) {
3684                 return false;
3685             }
3687             if (this.get('allowNoEdit')) {
3688                 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3689                 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3690                     //Forward delete key
3691                     Event.stopEvent(ev);
3692                 }
3693             }
3694             if (this._isNonEditable(ev)) {
3695                 return false;
3696             }
3697             this._setCurrentEvent(ev);
3698             if (this.browser.opera) {
3699                 if (ev.keyCode === 13) {
3700                     var tar = this._getSelectedElement();
3701                     if (!this._isElement(tar, 'li')) {
3702                         this.execCommand('inserthtml', '<br>');
3703                         Event.stopEvent(ev);
3704                     }
3705                 }
3706             }
3707             if (this.browser.webkit) {
3708                 if (!this.browser.webkit3) {
3709                     if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3710                         //This is CMD + z (for undo)
3711                         if (this._hasParent(this._getSelectedElement(), 'li')) {
3712                             Event.stopEvent(ev);
3713                         }
3714                     }
3715                 }
3716                 this._listFix(ev);
3717             }
3718             this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3719         },
3720         /**
3721         * @private
3722         * @method _handleKeyDown
3723         * @param {Event} ev The event we are working on.
3724         * @description Handles all keydown events inside the iFrame document.
3725         */
3726         _handleKeyDown: function(ev) {
3727             var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3728             if (ret === false) {
3729                 return false;
3730             }
3731             var tar = null, _range = null;
3732             if (this._isNonEditable(ev)) {
3733                 return false;
3734             }
3735             this._setCurrentEvent(ev);
3736             if (this.currentWindow) {
3737                 this.closeWindow();
3738             }
3739             if (this.currentWindow) {
3740                 this.closeWindow();
3741             }
3742             var doExec = false,
3743                 action = null,
3744                 exec = false;
3747             switch (ev.keyCode) {
3748                 case this._keyMap.FOCUS_TOOLBAR.key:
3749                     if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3750                         var h = this.toolbar.getElementsByTagName('h2')[0];
3751                         if (h && h.firstChild) {
3752                             h.firstChild.focus();
3753                         }
3754                     } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3755                         //Focus After Element - Esc
3756                         this.afterElement.focus();
3757                     }
3758                     Event.stopEvent(ev);
3759                     doExec = false;
3760                     break;
3761                 //case 76: //L
3762                 case this._keyMap.CREATE_LINK.key: //L
3763                     if (this._hasSelection()) {
3764                         if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3765                             var makeLink = true;
3766                             if (this.get('limitCommands')) {
3767                                 if (!this.toolbar.getButtonByValue('createlink')) {
3768                                     makeLink = false;
3769                                 }
3770                             }
3771                             if (makeLink) {
3772                                 this.execCommand('createlink', '');
3773                                 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3774                                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3775                                 doExec = false;
3776                             }
3777                         }
3778                     }
3779                     break;
3780                 //case 90: //Z
3781                 case this._keyMap.UNDO.key:
3782                 case this._keyMap.REDO.key:
3783                     if (this._checkKey(this._keyMap.REDO, ev)) {
3784                         action = 'redo';
3785                         doExec = true;
3786                     } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3787                         action = 'undo';
3788                         doExec = true;
3789                     }
3790                     break;
3791                 //case 66: //B
3792                 case this._keyMap.BOLD.key:
3793                     if (this._checkKey(this._keyMap.BOLD, ev)) {
3794                         action = 'bold';
3795                         doExec = true;
3796                     }
3797                     break;
3798                 //case 73: //I
3799                 case this._keyMap.ITALIC.key:
3800                     if (this._checkKey(this._keyMap.ITALIC, ev)) {
3801                         action = 'italic';
3802                         doExec = true;
3803                     }
3804                     break;
3805                 //case 85: //U
3806                 case this._keyMap.UNDERLINE.key:
3807                     if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3808                         action = 'underline';
3809                         doExec = true;
3810                     }
3811                     break;
3812                 case 9:
3813                     if (this.browser.ie) {
3814                         //Insert a tab in Internet Explorer
3815                         _range = this._getRange();
3816                         tar = this._getSelectedElement();
3817                         if (!this._isElement(tar, 'li')) {
3818                             if (_range) {
3819                                 _range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
3820                                 _range.collapse(false);
3821                                 _range.select();
3822                             }
3823                             Event.stopEvent(ev);
3824                         }
3825                     }
3826                     //Firefox 3 code
3827                     if (this.browser.gecko > 1.8) {
3828                         tar = this._getSelectedElement();
3829                         if (this._isElement(tar, 'li')) {
3830                             if (ev.shiftKey) {
3831                                 this._getDoc().execCommand('outdent', null, '');
3832                             } else {
3833                                 this._getDoc().execCommand('indent', null, '');
3834                             }
3835                             
3836                         } else if (!this._hasSelection()) {
3837                             this.execCommand('inserthtml', '&nbsp;&nbsp;&nbsp;&nbsp;');
3838                         }
3839                         Event.stopEvent(ev);
3840                     }
3841                     break;
3842                 case 13:
3843                     if (this.get('ptags') && !ev.shiftKey) {
3844                         if (this.browser.gecko) {
3845                             tar = this._getSelectedElement();
3846                             if (!this._isElement(tar, 'li')) {
3847                                 doExec = true;
3848                                 action = 'insertparagraph';
3849                                 Event.stopEvent(ev);
3850                             }
3851                         }
3852                         if (this.browser.webkit) {
3853                             tar = this._getSelectedElement();
3854                             if (!this._hasParent(tar, 'li')) {
3855                                 doExec = true;
3856                                 action = 'insertparagraph';
3857                                 Event.stopEvent(ev);
3858                             }
3859                         }
3860                     } else {
3861                         if (this.browser.ie) {
3862                             //Insert a <br> instead of a <p></p> in Internet Explorer
3863                             _range = this._getRange();
3864                             tar = this._getSelectedElement();
3865                             if (!this._isElement(tar, 'li')) {
3866                                 if (_range) {
3867                                     _range.pasteHTML('<br>');
3868                                     _range.collapse(false);
3869                                     _range.select();
3870                                 }
3871                                 Event.stopEvent(ev);
3872                             }
3873                         }
3874                     }
3875                     break;
3876             }
3877             if (this.browser.ie) {
3878                 this._listFix(ev);
3879             }
3880             if (doExec && action) {
3881                 this.execCommand(action, null);
3882                 Event.stopEvent(ev);
3883                 this.nodeChange();
3884             }
3885             this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
3886         },
3887         /**
3888         * @private
3889         * @method _listFix
3890         * @param {Event} ev The event we are working on.
3891         * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
3892         */
3893         _listFix: function(ev) {
3894             var testLi = null, par = null, preContent = false, range = null;
3895             //Enter Key
3896             if (this.browser.webkit) {
3897                 if (ev.keyCode && (ev.keyCode == 13)) {
3898                     if (this._hasParent(this._getSelectedElement(), 'li')) {
3899                         var tar = this._hasParent(this._getSelectedElement(), 'li');
3900                         if (tar.previousSibling) {
3901                             if (tar.firstChild && (tar.firstChild.length == 1)) {
3902                                 this._selectNode(tar);
3903                             }
3904                         }
3905                     }
3906                 }
3907             }
3908             //Shift + Tab Key
3909             if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
3910                 testLi = this._getSelectedElement();
3911                 if (this._hasParent(testLi, 'li')) {
3912                     testLi = this._hasParent(testLi, 'li');
3913                     if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
3914                         par = this._hasParent(testLi, 'ul');
3915                         if (!par) {
3916                             par = this._hasParent(testLi, 'ol');
3917                         }
3918                         if (this._isElement(par.previousSibling, 'li')) {
3919                             par.removeChild(testLi);
3920                             par.parentNode.insertBefore(testLi, par.nextSibling);
3921                             if (this.browser.ie) {
3922                                 range = this._getDoc().body.createTextRange();
3923                                 range.moveToElementText(testLi);
3924                                 range.collapse(false);
3925                                 range.select();
3926                             }
3927                             if (this.browser.webkit) {
3928                                 this._selectNode(testLi.firstChild);
3929                             }
3930                             Event.stopEvent(ev);
3931                         }
3932                     }
3933                 }
3934             }
3935             //Tab Key
3936             if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
3937                 var preLi = this._getSelectedElement();
3938                 if (this._hasParent(preLi, 'li')) {
3939                     preContent = this._hasParent(preLi, 'li').innerHTML;
3940                 }
3941                 if (this.browser.webkit) {
3942                     this._getDoc().execCommand('inserttext', false, '\t');
3943                 }
3944                 testLi = this._getSelectedElement();
3945                 if (this._hasParent(testLi, 'li')) {
3946                     par = this._hasParent(testLi, 'li');
3947                     var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
3948                     if (this.browser.webkit) {
3949                         var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
3950                         //Remove the span element that Safari puts in
3951                         if (span[0]) {
3952                             par.removeChild(span[0]);
3953                             par.innerHTML = Lang.trim(par.innerHTML);
3954                             //Put the HTML from the LI into this new LI
3955                             if (preContent) {
3956                                 par.innerHTML = '<span class="yui-non">' + preContent + '</span>&nbsp;';
3957                             } else {
3958                                 par.innerHTML = '<span class="yui-non">&nbsp;</span>&nbsp;';
3959                             }
3960                         }
3961                     } else {
3962                         if (preContent) {
3963                             par.innerHTML = preContent + '&nbsp;';
3964                         } else {
3965                             par.innerHTML = '&nbsp;';
3966                         }
3967                     }
3969                     par.parentNode.replaceChild(newUl, par);
3970                     newUl.appendChild(par);
3971                     if (this.browser.webkit) {
3972                         this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
3973                         if (!this.browser.webkit3) {
3974                             par.parentNode.parentNode.style.display = 'list-item';
3975                             setTimeout(function() {
3976                                 par.parentNode.parentNode.style.display = 'block';
3977                             }, 1);
3978                         }
3979                     } else if (this.browser.ie) {
3980                         range = this._getDoc().body.createTextRange();
3981                         range.moveToElementText(par);
3982                         range.collapse(false);
3983                         range.select();
3984                     } else {
3985                         this._selectNode(par);
3986                     }
3987                     Event.stopEvent(ev);
3988                 }
3989                 if (this.browser.webkit) {
3990                     Event.stopEvent(ev);
3991                 }
3992                 this.nodeChange();
3993             }
3994         },
3995         /**
3996         * @method nodeChange
3997         * @param {Boolean} force Optional paramenter to skip the threshold counter
3998         * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
3999         */
4000         nodeChange: function(force) {
4001             var NCself = this;
4002             this._storeUndo();
4003             if (this.get('nodeChangeDelay')) {
4004                 window.setTimeout(function() {
4005                     NCself._nodeChange.apply(NCself, arguments);
4006                 }, 0);
4007             } else {
4008                 this._nodeChange();
4009             }
4010         },
4011         /**
4012         * @private
4013         * @method _nodeChange
4014         * @param {Boolean} force Optional paramenter to skip the threshold counter
4015         * @description Fired from nodeChange in a setTimeout.
4016         */
4017         _nodeChange: function(force) {
4018             var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4019                 thisNodeChange = Math.round(new Date().getTime() / 1000),
4020                 self = this;
4022             if (force === true) {
4023                 this._lastNodeChange = 0;
4024             }
4025             
4026             if ((this._lastNodeChange + threshold) < thisNodeChange) {
4027                 if (this._fixNodesTimer === null) {
4028                     this._fixNodesTimer = window.setTimeout(function() {
4029                         self._fixNodes.call(self);
4030                         self._fixNodesTimer = null;
4031                     }, 0);
4032                 }
4033             }
4034             this._lastNodeChange = thisNodeChange;
4035             if (this.currentEvent) {
4036                 try {
4037                     this._lastNodeChangeEvent = this.currentEvent.type;
4038                 } catch (e) {}
4039             }
4041             var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4042             if (beforeNodeChange === false) {
4043                 return false;
4044             }
4045             if (this.get('dompath')) {
4046                 window.setTimeout(function() {
4047                     self._writeDomPath.call(self);
4048                 }, 0);
4049             }
4050             //Check to see if we are disabled before continuing
4051             if (!this.get('disabled')) {
4052                 if (this.STOP_NODE_CHANGE) {
4053                     //Reset this var for next action
4054                     this.STOP_NODE_CHANGE = false;
4055                     return false;
4056                 } else {
4057                     var sel = this._getSelection(),
4058                         range = this._getRange(),
4059                         el = this._getSelectedElement(),
4060                         fn_button = this.toolbar.getButtonByValue('fontname'),
4061                         fs_button = this.toolbar.getButtonByValue('fontsize'),
4062                         undo_button = this.toolbar.getButtonByValue('undo'),
4063                         redo_button = this.toolbar.getButtonByValue('redo');
4065                     //Handle updating the toolbar with active buttons
4066                     var _ex = {};
4067                     if (this._lastButton) {
4068                         _ex[this._lastButton.id] = true;
4069                         //this._lastButton = null;
4070                     }
4071                     if (!this._isElement(el, 'body')) {
4072                         if (fn_button) {
4073                             _ex[fn_button.get('id')] = true;
4074                         }
4075                         if (fs_button) {
4076                             _ex[fs_button.get('id')] = true;
4077                         }
4078                     }
4079                     if (redo_button) {
4080                         delete _ex[redo_button.get('id')];
4081                     }
4082                     this.toolbar.resetAllButtons(_ex);
4084                     //Handle disabled buttons
4085                     for (var d = 0; d < this._disabled.length; d++) {
4086                         var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4087                         if (_button && _button.get) {
4088                             if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4089                                 //Skip
4090                             } else {
4091                                 if (!this._hasSelection() && !this.get('insert')) {
4092                                     switch (this._disabled[d]) {
4093                                         case 'fontname':
4094                                         case 'fontsize':
4095                                             break;
4096                                         default:
4097                                             //No Selection - disable
4098                                             this.toolbar.disableButton(_button);
4099                                     }
4100                                 } else {
4101                                     if (!this._alwaysDisabled[this._disabled[d]]) {
4102                                         this.toolbar.enableButton(_button);
4103                                     }
4104                                 }
4105                                 if (!this._alwaysEnabled[this._disabled[d]]) {
4106                                     this.toolbar.deselectButton(_button);
4107                                 }
4108                             }
4109                         }
4110                     }
4111                     var path = this._getDomPath();
4112                     var tag = null, cmd = null;
4113                     for (var i = 0; i < path.length; i++) {
4114                         tag = path[i].tagName.toLowerCase();
4115                         if (path[i].getAttribute('tag')) {
4116                             tag = path[i].getAttribute('tag').toLowerCase();
4117                         }
4118                         cmd = this._tag2cmd[tag];
4119                         if (cmd === undefined) {
4120                             cmd = [];
4121                         }
4122                         if (!Lang.isArray(cmd)) {
4123                             cmd = [cmd];
4124                         }
4126                         //Bold and Italic styles
4127                         if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4128                             cmd[cmd.length] = 'bold';
4129                         }
4130                         if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4131                             cmd[cmd.length] = 'italic';
4132                         }
4133                         if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4134                             cmd[cmd.length] = 'underline';
4135                         }
4136                         if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4137                             cmd[cmd.length] = 'strikethrough';
4138                         }
4139                         if (cmd.length > 0) {
4140                             for (var j = 0; j < cmd.length; j++) {
4141                                 this.toolbar.selectButton(cmd[j]);
4142                                 this.toolbar.enableButton(cmd[j]);
4143                             }
4144                         }
4145                         //Handle Alignment
4146                         switch (path[i].style.textAlign.toLowerCase()) {
4147                             case 'left':
4148                             case 'right':
4149                             case 'center':
4150                             case 'justify':
4151                                 var alignType = path[i].style.textAlign.toLowerCase();
4152                                 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4153                                     alignType = 'full';
4154                                 }
4155                                 this.toolbar.selectButton('justify' + alignType);
4156                                 this.toolbar.enableButton('justify' + alignType);
4157                                 break;
4158                         }
4159                     }
4160                     //After for loop
4162                     //Reset Font Family and Size to the inital configs
4163                     if (fn_button) {
4164                         var family = fn_button._configs.label._initialConfig.value;
4165                         fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4166                         this._updateMenuChecked('fontname', family);
4167                     }
4169                     if (fs_button) {
4170                         fs_button.set('label', fs_button._configs.label._initialConfig.value);
4171                     }
4173                     var hd_button = this.toolbar.getButtonByValue('heading');
4174                     if (hd_button) {
4175                         hd_button.set('label', hd_button._configs.label._initialConfig.value);
4176                         this._updateMenuChecked('heading', 'none');
4177                     }
4178                     var img_button = this.toolbar.getButtonByValue('insertimage');
4179                     if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4180                         this.toolbar.disableButton(img_button);
4181                     }
4182                     if (this._lastButton && this._lastButton.isSelected) {
4183                         this.toolbar.deselectButton(this._lastButton.id);
4184                     }
4185                     this._undoNodeChange();
4186                 }
4187             }
4189             this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4190         },
4191         /**
4192         * @private
4193         * @method _updateMenuChecked
4194         * @param {Object} button The command identifier of the button you want to check
4195         * @param {String} value The value of the menu item you want to check
4196         * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar) 
4197         * @description Gets the menu from a button instance, if the menu is not rendered it will render it. It will then search the menu for the specified value, unchecking all other items and checking the specified on.
4198         */
4199         _updateMenuChecked: function(button, value, tbar) {
4200             if (!tbar) {
4201                 tbar = this.toolbar;
4202             }
4203             var _button = tbar.getButtonByValue(button);
4204             _button.checkValue(value);
4205         },
4206         /**
4207         * @private
4208         * @method _handleToolbarClick
4209         * @param {Event} ev The event that triggered the button click
4210         * @description This is an event handler attached to the Toolbar's buttonClick event. It will fire execCommand with the command identifier from the Toolbar Button.
4211         */
4212         _handleToolbarClick: function(ev) {
4213             var value = '';
4214             var str = '';
4215             var cmd = ev.button.value;
4216             if (ev.button.menucmd) {
4217                 value = cmd;
4218                 cmd = ev.button.menucmd;
4219             }
4220             this._lastButton = ev.button;
4221             if (this.STOP_EXEC_COMMAND) {
4222                 this.STOP_EXEC_COMMAND = false;
4223                 return false;
4224             } else {
4225                 this.execCommand(cmd, value);
4226                 if (!this.browser.webkit) {
4227                      var Fself = this;
4228                      setTimeout(function() {
4229                          Fself._focusWindow.call(Fself);
4230                      }, 5);
4231                  }
4232             }
4233             Event.stopEvent(ev);
4234         },
4235         /**
4236         * @private
4237         * @method _setupAfterElement
4238         * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4239         */
4240         _setupAfterElement: function() {
4241             if (!this.beforeElement) {
4242                 this.beforeElement = document.createElement('h2');
4243                 this.beforeElement.className = 'yui-editor-skipheader';
4244                 this.beforeElement.tabIndex = '-1';
4245                 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4246                 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4247             }
4248             if (!this.afterElement) {
4249                 this.afterElement = document.createElement('h2');
4250                 this.afterElement.className = 'yui-editor-skipheader';
4251                 this.afterElement.tabIndex = '-1';
4252                 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4253                 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4254             }
4255         },
4256         /**
4257         * @private
4258         * @method _disableEditor
4259         * @param {Boolean} disabled Pass true to disable, false to enable
4260         * @description Creates a mask to place over the Editor.
4261         */
4262         _disableEditor: function(disabled) {
4263             if (disabled) {
4264                 this._removeEditorEvents();
4265                 if (!this._mask) {
4266                     if (!!this.browser.ie) {
4267                         this._setDesignMode('off');
4268                     }
4269                     if (this.toolbar) {
4270                         this.toolbar.set('disabled', true);
4271                     }
4272                     this._mask = document.createElement('DIV');
4273                     Dom.setStyle(this._mask, 'height', '100%');
4274                     Dom.setStyle(this._mask, 'width', '100%');
4275                     Dom.setStyle(this._mask, 'position', 'absolute');
4276                     Dom.setStyle(this._mask, 'top', '0');
4277                     Dom.setStyle(this._mask, 'left', '0');
4278                     Dom.setStyle(this._mask, 'opacity', '.5');
4279                     Dom.addClass(this._mask, 'yui-editor-masked');
4280                     this.get('iframe').get('parentNode').appendChild(this._mask);
4281                 }
4282             } else {
4283                 this._initEditorEvents();
4284                 if (this._mask) {
4285                     this._mask.parentNode.removeChild(this._mask);
4286                     this._mask = null;
4287                     if (this.toolbar) {
4288                         this.toolbar.set('disabled', false);
4289                     }
4290                     this._setDesignMode('on');
4291                     this._focusWindow();
4292                     var self = this;
4293                     window.setTimeout(function() {
4294                         self.nodeChange.call(self);
4295                     }, 100);
4296                 }
4297             }
4298         },
4299         /**
4300         * @property SEP_DOMPATH
4301         * @description The value to place in between the Dom path items
4302         * @type String
4303         */
4304         SEP_DOMPATH: '<',
4305         /**
4306         * @property STR_LEAVE_EDITOR
4307         * @description The accessibility string for the element after the iFrame
4308         * @type String
4309         */
4310         STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4311         /**
4312         * @property STR_BEFORE_EDITOR
4313         * @description The accessibility string for the element before the iFrame
4314         * @type String
4315         */
4316         STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Shift + Escape to place focus on the toolbar and navigate between options with your arrow keys. To exit this text editor use the Escape key and continue tabbing. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift L adds an HTML link</li></ul>',
4317         /**
4318         * @property STR_TITLE
4319         * @description The Title of the HTML document that is created in the iFrame
4320         * @type String
4321         */
4322         STR_TITLE: 'Rich Text Area.',
4323         /**
4324         * @property STR_IMAGE_HERE
4325         * @description The text to place in the URL textbox when using the blankimage.
4326         * @type String
4327         */
4328         STR_IMAGE_HERE: 'Image URL Here',
4329         /**
4330         * @property STR_LINK_URL
4331         * @description The label string for the Link URL.
4332         * @type String
4333         */
4334         STR_LINK_URL: 'Link URL',
4335         /**
4336         * @protected
4337         * @property STOP_EXEC_COMMAND
4338         * @description Set to true when you want the default execCommand function to not process anything
4339         * @type Boolean
4340         */
4341         STOP_EXEC_COMMAND: false,
4342         /**
4343         * @protected
4344         * @property STOP_NODE_CHANGE
4345         * @description Set to true when you want the default nodeChange function to not process anything
4346         * @type Boolean
4347         */
4348         STOP_NODE_CHANGE: false,
4349         /**
4350         * @protected
4351         * @property CLASS_NOEDIT
4352         * @description CSS class applied to elements that are not editable.
4353         * @type String
4354         */
4355         CLASS_NOEDIT: 'yui-noedit',
4356         /**
4357         * @protected
4358         * @property CLASS_CONTAINER
4359         * @description Default CSS class to apply to the editors container element
4360         * @type String
4361         */
4362         CLASS_CONTAINER: 'yui-editor-container',
4363         /**
4364         * @protected
4365         * @property CLASS_EDITABLE
4366         * @description Default CSS class to apply to the editors iframe element
4367         * @type String
4368         */
4369         CLASS_EDITABLE: 'yui-editor-editable',
4370         /**
4371         * @protected
4372         * @property CLASS_EDITABLE_CONT
4373         * @description Default CSS class to apply to the editors iframe's parent element
4374         * @type String
4375         */
4376         CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4377         /**
4378         * @protected
4379         * @property CLASS_PREFIX
4380         * @description Default prefix for dynamically created class names
4381         * @type String
4382         */
4383         CLASS_PREFIX: 'yui-editor',
4384         /** 
4385         * @property browser
4386         * @description Standard browser detection
4387         * @type Object
4388         */
4389         browser: function() {
4390             var br = YAHOO.env.ua;
4391             //Check for webkit3
4392             if (br.webkit >= 420) {
4393                 br.webkit3 = br.webkit;
4394             } else {
4395                 br.webkit3 = 0;
4396             }
4397             br.mac = false;
4398             //Check for Mac
4399             if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4400                 br.mac = true;
4401             }
4403             return br;
4404         }(),
4405         /** 
4406         * @method init
4407         * @description The Editor class' initialization method
4408         */
4409         init: function(p_oElement, p_oAttributes) {
4411             if (!this._defaultToolbar) {
4412                 this._defaultToolbar = {
4413                     collapse: true,
4414                     titlebar: 'Text Editing Tools',
4415                     draggable: false,
4416                     buttons: [
4417                         { group: 'fontstyle', label: 'Font Name and Size',
4418                             buttons: [
4419                                 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4420                                     menu: [
4421                                         { text: 'Arial', checked: true },
4422                                         { text: 'Arial Black' },
4423                                         { text: 'Comic Sans MS' },
4424                                         { text: 'Courier New' },
4425                                         { text: 'Lucida Console' },
4426                                         { text: 'Tahoma' },
4427                                         { text: 'Times New Roman' },
4428                                         { text: 'Trebuchet MS' },
4429                                         { text: 'Verdana' }
4430                                     ]
4431                                 },
4432                                 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4433                             ]
4434                         },
4435                         { type: 'separator' },
4436                         { group: 'textstyle', label: 'Font Style',
4437                             buttons: [
4438                                 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4439                                 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4440                                 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4441                                 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4442                                 { type: 'separator' },
4443                                 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4444                                 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4445                                 
4446                             ]
4447                         },
4448                         { type: 'separator' },
4449                         { group: 'indentlist', label: 'Lists',
4450                             buttons: [
4451                                 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4452                                 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4453                             ]
4454                         },
4455                         { type: 'separator' },
4456                         { group: 'insertitem', label: 'Insert Item',
4457                             buttons: [
4458                                 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4459                                 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4460                             ]
4461                         }
4462                     ]
4463                 };
4464             }
4466             YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4467             YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4470             this.currentElement = [];
4471             this.on('contentReady', function() {
4472                 this.DOMReady = true;
4473                 this.fireQueue();
4474             }, this, true);
4476         },
4477         /**
4478         * @method initAttributes
4479         * @description Initializes all of the configuration attributes used to create 
4480         * the editor.
4481         * @param {Object} attr Object literal specifying a set of 
4482         * configuration attributes used to create the editor.
4483         */
4484         initAttributes: function(attr) {
4485             YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4486             var self = this;
4488             /**
4489             * @config nodeChangeDelay
4490             * @description Do we wrap the nodeChange method in a timeout for performance, default: true.
4491             * @default true
4492             * @type Number
4493             */
4494             this.setAttributeConfig('nodeChangeDelay', {
4495                 value: ((attr.nodeChangeDelay === false) ? false : true)
4496             });
4497             /**
4498             * @config maxUndo
4499             * @description The max number of undo levels to store.
4500             * @default 30
4501             * @type Number
4502             */
4503             this.setAttributeConfig('maxUndo', {
4504                 writeOnce: true,
4505                 value: attr.maxUndo || 30
4506             });
4508             /**
4509             * @config ptags
4510             * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4511             * @default false
4512             * @type Boolean
4513             */
4514             this.setAttributeConfig('ptags', {
4515                 writeOnce: true,
4516                 value: attr.ptags || false
4517             });
4518             /**
4519             * @config insert
4520             * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4521             * @default false
4522             * @type Boolean
4523             */
4524             this.setAttributeConfig('insert', {
4525                 writeOnce: true,
4526                 value: attr.insert || false,
4527                 method: function(insert) {
4528                     if (insert) {
4529                         var buttons = {
4530                             fontname: true,
4531                             fontsize: true,
4532                             forecolor: true,
4533                             backcolor: true
4534                         };
4535                         var tmp = this._defaultToolbar.buttons;
4536                         for (var i = 0; i < tmp.length; i++) {
4537                             if (tmp[i].buttons) {
4538                                 for (var a = 0; a < tmp[i].buttons.length; a++) {
4539                                     if (tmp[i].buttons[a].value) {
4540                                         if (buttons[tmp[i].buttons[a].value]) {
4541                                             delete tmp[i].buttons[a].disabled;
4542                                         }
4543                                     }
4544                                 }
4545                             }
4546                         }
4547                     }
4548                 }
4549             });
4550             /**
4551             * @config container
4552             * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4553             * We will create one and place it in this container. If no container is passed we will append to document.body.
4554             * @default false
4555             * @type HTMLElement
4556             */
4557             this.setAttributeConfig('container', {
4558                 writeOnce: true,
4559                 value: attr.container || false
4560             });
4561             /**
4562             * @config plainText
4563             * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4564             * @default false
4565             * @type Boolean
4566             */
4567             this.setAttributeConfig('plainText', {
4568                 writeOnce: true,
4569                 value: attr.plainText || false
4570             });
4571             /**
4572             * @private
4573             * @config iframe
4574             * @description Internal config for holding the iframe element.
4575             * @default null
4576             * @type HTMLElement
4577             */
4578             this.setAttributeConfig('iframe', {
4579                 value: null
4580             });
4581             /**
4582             * @private
4583             * @depreciated
4584             * @config textarea
4585             * @description Internal config for holding the textarea element (replaced with element).
4586             * @default null
4587             * @type HTMLElement
4588             */
4589             this.setAttributeConfig('textarea', {
4590                 value: null,
4591                 writeOnce: true
4592             });
4593             /**
4594             * @private
4595             * @config container
4596             * @description Internal config for holding a reference to the container to append a dynamic editor to.
4597             * @default null
4598             * @type HTMLElement
4599             */
4600             this.setAttributeConfig('container', {
4601                 readOnly: true,
4602                 value: null
4603             });
4604             /**
4605             * @config nodeChangeThreshold
4606             * @description The number of seconds that need to be in between nodeChange processing
4607             * @default 3
4608             * @type Number
4609             */            
4610             this.setAttributeConfig('nodeChangeThreshold', {
4611                 value: attr.nodeChangeThreshold || 3,
4612                 validator: YAHOO.lang.isNumber
4613             });
4614             /**
4615             * @config allowNoEdit
4616             * @description Should the editor check for non-edit fields. It should be noted that this technique is not perfect. If the user does the right things, they will still be able to make changes.
4617             * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4618             * @default false
4619             * @type Boolean
4620             */            
4621             this.setAttributeConfig('allowNoEdit', {
4622                 value: attr.allowNoEdit || false,
4623                 validator: YAHOO.lang.isBoolean
4624             });
4625             /**
4626             * @config limitCommands
4627             * @description Should the Editor limit the allowed execCommands to the ones available in the toolbar. If true, then execCommand and keyboard shortcuts will fail if they are not defined in the toolbar.
4628             * @default false
4629             * @type Boolean
4630             */            
4631             this.setAttributeConfig('limitCommands', {
4632                 value: attr.limitCommands || false,
4633                 validator: YAHOO.lang.isBoolean
4634             });
4635             /**
4636             * @config element_cont
4637             * @description Internal config for the editors container
4638             * @default false
4639             * @type HTMLElement
4640             */
4641             this.setAttributeConfig('element_cont', {
4642                 value: attr.element_cont
4643             });
4644             /**
4645             * @private
4646             * @config editor_wrapper
4647             * @description The outter wrapper for the entire editor.
4648             * @default null
4649             * @type HTMLElement
4650             */
4651             this.setAttributeConfig('editor_wrapper', {
4652                 value: attr.editor_wrapper || null,
4653                 writeOnce: true
4654             });
4655             /**
4656             * @attribute height
4657             * @description The height of the editor iframe container, not including the toolbar..
4658             * @default Best guessed size of the textarea, for best results use CSS to style the height of the textarea or pass it in as an argument
4659             * @type String
4660             */
4661             this.setAttributeConfig('height', {
4662                 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4663                 method: function(height) {
4664                     if (this._rendered) {
4665                         //We have been rendered, change the height
4666                         if (this.get('animate')) {
4667                             var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4668                                 height: {
4669                                     to: parseInt(height, 10)
4670                                 }
4671                             }, 0.5);
4672                             anim.animate();
4673                         } else {
4674                             Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4675                         }
4676                     }
4677                 }
4678             });
4679             /**
4680             * @config autoHeight
4681             * @description Remove the scrollbars from the edit area and resize it to fit the content. It will not go any lower than the current config height.
4682             * @default false
4683             * @type Boolean || Number
4684             */
4685             this.setAttributeConfig('autoHeight', {
4686                 value: attr.autoHeight || false,
4687                 method: function(a) {
4688                     if (a) {
4689                         if (this.get('iframe')) {
4690                             this.get('iframe').get('element').setAttribute('scrolling', 'no');
4691                         }
4692                         this.on('afterNodeChange', this._handleAutoHeight, this, true);
4693                         this.on('editorKeyDown', this._handleAutoHeight, this, true);
4694                         this.on('editorKeyPress', this._handleAutoHeight, this, true);
4695                     } else {
4696                         if (this.get('iframe')) {
4697                             this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4698                         }
4699                         this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4700                         this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4701                         this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4702                     }
4703                 }
4704             });
4705             /**
4706             * @attribute width
4707             * @description The width of the editor container.
4708             * @default Best guessed size of the textarea, for best results use CSS to style the width of the textarea or pass it in as an argument
4709             * @type String
4710             */            
4711             this.setAttributeConfig('width', {
4712                 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
4713                 method: function(width) {
4714                     if (this._rendered) {
4715                         //We have been rendered, change the width
4716                         if (this.get('animate')) {
4717                             var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
4718                                 width: {
4719                                     to: parseInt(width, 10)
4720                                 }
4721                             }, 0.5);
4722                             anim.animate();
4723                         } else {
4724                             this.get('element_cont').setStyle('width', width);
4725                         }
4726                     }
4727                 }
4728             });
4729                         
4730             /**
4731             * @attribute blankimage
4732             * @description The URL for the image placeholder to put in when inserting an image.
4733             * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
4734             * @type String
4735             */            
4736             this.setAttributeConfig('blankimage', {
4737                 value: attr.blankimage || this._getBlankImage()
4738             });
4739             /**
4740             * @attribute css
4741             * @description The Base CSS used to format the content of the editor
4742             * @default <code><pre>html {
4743                 height: 95%;
4744             }
4745             body {
4746                 height: 100%;
4747                 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
4748             }
4749             a {
4750                 color: blue;
4751                 text-decoration: underline;
4752                 cursor: pointer;
4753             }
4754             .warning-localfile {
4755                 border-bottom: 1px dashed red !important;
4756             }
4757             .yui-busy {
4758                 cursor: wait !important;
4759             }
4760             img.selected { //Safari image selection
4761                 border: 2px dotted #808080;
4762             }
4763             img {
4764                 cursor: pointer !important;
4765                 border: none;
4766             }
4767             </pre></code>
4768             * @type String
4769             */            
4770             this.setAttributeConfig('css', {
4771                 value: attr.css || this._defaultCSS,
4772                 writeOnce: true
4773             });
4774             /**
4775             * @attribute html
4776             * @description The default HTML to be written to the iframe document before the contents are loaded (Note that the DOCTYPE attr will be added at render item)
4777             * @default This HTML requires a few things if you are to override:
4778                 <p><code>{TITLE}, {CSS}, {HIDDEN_CSS}, {EXTRA_CSS}</code> and <code>{CONTENT}</code> need to be there, they are passed to YAHOO.lang.substitute to be replace with other strings.<p>
4779                 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
4780                 <code>
4781                 <pre>
4782                 &lt;html&gt;
4783                     &lt;head&gt;
4784                         &lt;title&gt;{TITLE}&lt;/title&gt;
4785                         &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
4786                         &lt;style&gt;
4787                         {CSS}
4788                         &lt;/style&gt;
4789                         &lt;style&gt;
4790                         {HIDDEN_CSS}
4791                         &lt;/style&gt;
4792                         &lt;style&gt;
4793                         {EXTRA_CSS}
4794                         &lt;/style&gt;
4795                     &lt;/head&gt;
4796                 &lt;body onload="document.body._rteLoaded = true;"&gt;
4797                 {CONTENT}
4798                 &lt;/body&gt;
4799                 &lt;/html&gt;
4800                 </pre>
4801                 </code>
4802             * @type String
4803             */            
4804             this.setAttributeConfig('html', {
4805                 value: attr.html || '<html><head><title>{TITLE}</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><base href="' + this._baseHREF + '"><style>{CSS}</style><style>{HIDDEN_CSS}</style><style>{EXTRA_CSS}</style></head><body onload="document.body._rteLoaded = true;">{CONTENT}</body></html>',
4806                 writeOnce: true
4807             });
4809             /**
4810             * @attribute extracss
4811             * @description Extra user defined css to load after the default SimpleEditor CSS
4812             * @default ''
4813             * @type String
4814             */            
4815             this.setAttributeConfig('extracss', {
4816                 value: attr.extracss || '',
4817                 writeOnce: true
4818             });
4820             /**
4821             * @attribute handleSubmit
4822             * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
4823             If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
4824             Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
4825             * @default false
4826             * @type Boolean
4827             */            
4828             this.setAttributeConfig('handleSubmit', {
4829                 value: attr.handleSubmit || false,
4830                 method: function(exec) {
4831                     if (this.get('element').form) {
4832                         if (!this._formButtons) {
4833                             this._formButtons = [];
4834                         }
4835                         if (exec) {
4836                             Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
4837                             var i = this.get('element').form.getElementsByTagName('input');
4838                             for (var s = 0; s < i.length; s++) {
4839                                 var type = i[s].getAttribute('type');
4840                                 if (type && (type.toLowerCase() == 'submit')) {
4841                                     Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
4842                                     this._formButtons[this._formButtons.length] = i[s];
4843                                 }
4844                             }
4845                         } else {
4846                             Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
4847                             if (this._formButtons) {
4848                                 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
4849                             }
4850                         }
4851                     }
4852                 }
4853             });
4854             /**
4855             * @attribute disabled
4856             * @description This will toggle the editor's disabled state. When the editor is disabled, designMode is turned off and a mask is placed over the iframe so no interaction can take place.
4857             All Toolbar buttons are also disabled so they cannot be used.
4858             * @default false
4859             * @type Boolean
4860             */
4862             this.setAttributeConfig('disabled', {
4863                 value: false,
4864                 method: function(disabled) {
4865                     if (this._rendered) {
4866                         this._disableEditor(disabled);
4867                     }
4868                 }
4869             });
4870             /**
4871             * @config saveEl
4872             * @description When save HTML is called, this element will be updated as well as the source of data.
4873             * @default element
4874             * @type HTMLElement
4875             */
4876             this.setAttributeConfig('saveEl', {
4877                 value: this.get('element')
4878             });
4879             /**
4880             * @config toolbar_cont
4881             * @description Internal config for the toolbars container
4882             * @default false
4883             * @type Boolean
4884             */
4885             this.setAttributeConfig('toolbar_cont', {
4886                 value: null,
4887                 writeOnce: true
4888             });
4889             /**
4890             * @attribute toolbar
4891             * @description The default toolbar config.
4892             * @type Object
4893             */            
4894             this.setAttributeConfig('toolbar', {
4895                 value: attr.toolbar || this._defaultToolbar,
4896                 writeOnce: true,
4897                 method: function(toolbar) {
4898                     if (!toolbar.buttonType) {
4899                         toolbar.buttonType = this._defaultToolbar.buttonType;
4900                     }
4901                     this._defaultToolbar = toolbar;
4902                 }
4903             });
4904             /**
4905             * @attribute animate
4906             * @description Should the editor animate window movements
4907             * @default false unless Animation is found, then true
4908             * @type Boolean
4909             */            
4910             this.setAttributeConfig('animate', {
4911                 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
4912                 validator: function(value) {
4913                     var ret = true;
4914                     if (!YAHOO.util.Anim) {
4915                         ret = false;
4916                     }
4917                     return ret;
4918                 }
4919             });
4920             /**
4921             * @config panel
4922             * @description A reference to the panel we are using for windows.
4923             * @default false
4924             * @type Boolean
4925             */            
4926             this.setAttributeConfig('panel', {
4927                 value: null,
4928                 writeOnce: true,
4929                 validator: function(value) {
4930                     var ret = true;
4931                     if (!YAHOO.widget.Overlay) {
4932                         ret = false;
4933                     }
4934                     return ret;
4935                 }               
4936             });
4937             /**
4938             * @attribute focusAtStart
4939             * @description Should we focus the window when the content is ready?
4940             * @default false
4941             * @type Boolean
4942             */            
4943             this.setAttributeConfig('focusAtStart', {
4944                 value: attr.focusAtStart || false,
4945                 writeOnce: true,
4946                 method: function(fs) {
4947                     if (fs) {
4948                         this.on('editorContentLoaded', function() {
4949                             var self = this;
4950                             setTimeout(function() {
4951                                 self._focusWindow.call(self, true);
4952                                 self.editorDirty = false;
4953                             }, 400);
4954                         }, this, true);
4955                     }
4956                 }
4957             });
4958             /**
4959             * @attribute dompath
4960             * @description Toggle the display of the current Dom path below the editor
4961             * @default false
4962             * @type Boolean
4963             */            
4964             this.setAttributeConfig('dompath', {
4965                 value: attr.dompath || false,
4966                 method: function(dompath) {
4967                     if (dompath && !this.dompath) {
4968                         this.dompath = document.createElement('DIV');
4969                         this.dompath.id = this.get('id') + '_dompath';
4970                         Dom.addClass(this.dompath, 'dompath');
4971                         this.get('element_cont').get('firstChild').appendChild(this.dompath);
4972                         if (this.get('iframe')) {
4973                             this._writeDomPath();
4974                         }
4975                     } else if (!dompath && this.dompath) {
4976                         this.dompath.parentNode.removeChild(this.dompath);
4977                         this.dompath = null;
4978                     }
4979                 }
4980             });
4981             /**
4982             * @attribute markup
4983             * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
4984             * @default "semantic"
4985             * @type String
4986             */            
4987             this.setAttributeConfig('markup', {
4988                 value: attr.markup || 'semantic',
4989                 validator: function(markup) {
4990                     switch (markup.toLowerCase()) {
4991                         case 'semantic':
4992                         case 'css':
4993                         case 'default':
4994                         case 'xhtml':
4995                         return true;
4996                     }
4997                     return false;
4998                 }
4999             });
5000             /**
5001             * @attribute removeLineBreaks
5002             * @description Should we remove linebreaks and extra spaces on cleanup
5003             * @default false
5004             * @type Boolean
5005             */            
5006             this.setAttributeConfig('removeLineBreaks', {
5007                 value: attr.removeLineBreaks || false,
5008                 validator: YAHOO.lang.isBoolean
5009             });
5010             
5011             /**
5012             * @config drag
5013             * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5014             * @type {Boolean/String}
5015             */
5016             this.setAttributeConfig('drag', {
5017                 writeOnce: true,
5018                 value: attr.drag || false
5019             });
5021             /**
5022             * @config resize
5023             * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5024             * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5025             * @type Boolean
5026             */
5027             this.setAttributeConfig('resize', {
5028                 writeOnce: true,
5029                 value: attr.resize || false
5030             });
5031         },
5032         /**
5033         * @private
5034         * @method _getBlankImage
5035         * @description Retrieves the full url of the image to use as the blank image.
5036         * @return {String} The URL to the blank image
5037         */
5038         _getBlankImage: function() {
5039             if (!this.DOMReady) {
5040                 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5041                 return '';
5042             }
5043             var img = '';
5044             if (!this._blankImageLoaded) {
5045                 if (YAHOO.widget.EditorInfo.blankImage) {
5046                     this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5047                     this._blankImageLoaded = true;
5048                 } else {
5049                     var div = document.createElement('div');
5050                     div.style.position = 'absolute';
5051                     div.style.top = '-9999px';
5052                     div.style.left = '-9999px';
5053                     div.className = this.CLASS_PREFIX + '-blankimage';
5054                     document.body.appendChild(div);
5055                     img = YAHOO.util.Dom.getStyle(div, 'background-image');
5056                     img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5057                     //Adobe AIR Code
5058                     img = img.replace('app:/', '');             
5059                     this.set('blankimage', img);
5060                     this._blankImageLoaded = true;
5061                     div.parentNode.removeChild(div);
5062                     YAHOO.widget.EditorInfo.blankImage = img;
5063                 }
5064             } else {
5065                 img = this.get('blankimage');
5066             }
5067             return img;
5068         },
5069         /**
5070         * @private
5071         * @method _handleAutoHeight
5072         * @description Handles resizing the editor's height based on the content
5073         */
5074         _handleAutoHeight: function() {
5075             var doc = this._getDoc(),
5076                 body = doc.body,
5077                 docEl = doc.documentElement;
5079             var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5080             var newHeight = body.scrollHeight;
5081             if (this.browser.webkit) {
5082                 newHeight = docEl.scrollHeight;
5083             }
5084             if (newHeight < parseInt(this.get('height'), 10)) {
5085                 newHeight = parseInt(this.get('height'), 10);
5086             }
5087             if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {   
5088                 Dom.setStyle(this.get('editor_wrapper'), 'height', newHeight + 'px');
5089                 if (this.browser.ie) {
5090                     //Internet Explorer needs this
5091                     this.get('iframe').setStyle('height', '99%');
5092                     this.get('iframe').setStyle('zoom', '1');
5093                     var self = this;
5094                     window.setTimeout(function() {
5095                         self.get('iframe').setStyle('height', '100%');
5096                     }, 1);
5097                 }
5098             }
5099         },
5100         /**
5101         * @private
5102         * @property _formButtons
5103         * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5104         * @type Array
5105         */
5106         _formButtons: null,
5107         /**
5108         * @private
5109         * @property _formButtonClicked
5110         * @description The form button that was clicked to submit the form.
5111         * @type HTMLElement
5112         */
5113         _formButtonClicked: null,
5114         /**
5115         * @private
5116         * @method _handleFormButtonClick
5117         * @description The click listener assigned to each submit button in the Editor's parent form.
5118         * @param {Event} ev The click event
5119         */
5120         _handleFormButtonClick: function(ev) {
5121             var tar = Event.getTarget(ev);
5122             this._formButtonClicked = tar;
5123         },
5124         /**
5125         * @private
5126         * @method _handleFormSubmit
5127         * @description Handles the form submission.
5128         * @param {Object} ev The Form Submit Event
5129         */
5130         _handleFormSubmit: function(ev) {
5131             this.saveHTML();
5133             var form = this.get('element').form,
5134                 tar = this._formButtonClicked || false;
5136             Event.removeListener(form, 'submit', this._handleFormSubmit);
5137             if (YAHOO.env.ua.ie) {
5138                 //form.fireEvent("onsubmit");
5139                 if (tar && !tar.disabled) {
5140                     tar.click();
5141                 }
5142             } else {  // Gecko, Opera, and Safari
5143                 if (tar && !tar.disabled) {
5144                     tar.click();
5145                 }
5146                 var oEvent = document.createEvent("HTMLEvents");
5147                 oEvent.initEvent("submit", true, true);
5148                 form.dispatchEvent(oEvent);
5149                 if (YAHOO.env.ua.webkit) {
5150                     if (YAHOO.lang.isFunction(form.submit)) {
5151                         form.submit();
5152                     }
5153                 }
5154             }
5155             //2.6.0
5156             //Removed this, not need since removing Safari 2.x
5157             //Event.stopEvent(ev);
5158         },
5159         /**
5160         * @private
5161         * @method _handleFontSize
5162         * @description Handles the font size button in the toolbar.
5163         * @param {Object} o Object returned from Toolbar's buttonClick Event
5164         */
5165         _handleFontSize: function(o) {
5166             var button = this.toolbar.getButtonById(o.button.id);
5167             var value = button.get('label') + 'px';
5168             this.execCommand('fontsize', value);
5169             this.STOP_EXEC_COMMAND = true;
5170         },
5171         /**
5172         * @private
5173         * @description Handles the colorpicker buttons in the toolbar.
5174         * @param {Object} o Object returned from Toolbar's buttonClick Event
5175         */
5176         _handleColorPicker: function(o) {
5177             var cmd = o.button;
5178             var value = '#' + o.color;
5179             if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5180                 this.execCommand(cmd, value);
5181             }
5182         },
5183         /**
5184         * @private
5185         * @method _handleAlign
5186         * @description Handles the alignment buttons in the toolbar.
5187         * @param {Object} o Object returned from Toolbar's buttonClick Event
5188         */
5189         _handleAlign: function(o) {
5190             var cmd = null;
5191             for (var i = 0; i < o.button.menu.length; i++) {
5192                 if (o.button.menu[i].value == o.button.value) {
5193                     cmd = o.button.menu[i].value;
5194                 }
5195             }
5196             var value = this._getSelection();
5198             this.execCommand(cmd, value);
5199             this.STOP_EXEC_COMMAND = true;
5200         },
5201         /**
5202         * @private
5203         * @method _handleAfterNodeChange
5204         * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5205         */
5206         _handleAfterNodeChange: function() {
5207             var path = this._getDomPath(),
5208                 elm = null,
5209                 family = null,
5210                 fontsize = null,
5211                 validFont = false,
5212                 fn_button = this.toolbar.getButtonByValue('fontname'),
5213                 fs_button = this.toolbar.getButtonByValue('fontsize'),
5214                 hd_button = this.toolbar.getButtonByValue('heading');
5216             for (var i = 0; i < path.length; i++) {
5217                 elm = path[i];
5219                 var tag = elm.tagName.toLowerCase();
5222                 if (elm.getAttribute('tag')) {
5223                     tag = elm.getAttribute('tag');
5224                 }
5226                 family = elm.getAttribute('face');
5227                 if (Dom.getStyle(elm, 'font-family')) {
5228                     family = Dom.getStyle(elm, 'font-family');
5229                     //Adobe AIR Code
5230                     family = family.replace(/'/g, '');                    
5231                 }
5233                 if (tag.substring(0, 1) == 'h') {
5234                     if (hd_button) {
5235                         for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5236                             if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5237                                 hd_button.set('label', hd_button._configs.menu.value[h].text);
5238                             }
5239                         }
5240                         this._updateMenuChecked('heading', tag);
5241                     }
5242                 }
5243             }
5245             if (fn_button) {
5246                 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5247                     if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5248                         validFont = true;
5249                         family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5250                     }
5251                 }
5252                 if (!validFont) {
5253                     family = fn_button._configs.label._initialConfig.value;
5254                 }
5255                 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5256                 if (fn_button.get('label') != familyLabel) {
5257                     fn_button.set('label', familyLabel);
5258                     this._updateMenuChecked('fontname', family);
5259                 }
5260             }
5262             if (fs_button) {
5263                 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5264                 if ((fontsize === null) || isNaN(fontsize)) {
5265                     fontsize = fs_button._configs.label._initialConfig.value;
5266                 }
5267                 fs_button.set('label', ''+fontsize);
5268             }
5269             
5270             if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5271                 this.toolbar.enableButton(fn_button);
5272                 this.toolbar.enableButton(fs_button);
5273                 this.toolbar.enableButton('forecolor');
5274                 this.toolbar.enableButton('backcolor');
5275             }
5276             if (this._isElement(elm, 'img')) {
5277                 if (YAHOO.widget.Overlay) {
5278                     this.toolbar.enableButton('createlink');
5279                 }
5280             }
5281             if (this._hasParent(elm, 'blockquote')) {
5282                 this.toolbar.selectButton('indent');
5283                 this.toolbar.disableButton('indent');
5284                 this.toolbar.enableButton('outdent');
5285             }
5286             if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5287                 this.toolbar.disableButton('indent');
5288             }
5289             this._lastButton = null;
5290             
5291         },
5292         /**
5293         * @private
5294         * @method _handleInsertImageClick
5295         * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5296         */
5297         _handleInsertImageClick: function() {
5298             if (this.get('limitCommands')) {
5299                 if (!this.toolbar.getButtonByValue('insertimage')) {
5300                     return false;
5301                 }
5302             }
5303         
5304             this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5305             this.on('afterExecCommand', function() {
5306                 var el = this.currentElement[0],
5307                     src = 'http://';
5308                 if (!el) {
5309                     el = this._getSelectedElement();
5310                 }
5311                 if (el) {
5312                     if (el.getAttribute('src')) {
5313                         src = el.getAttribute('src', 2);
5314                         if (src.indexOf(this.get('blankimage')) != -1) {
5315                             src = this.STR_IMAGE_HERE;
5316                         }
5317                     }
5318                 }
5319                 var str = prompt(this.STR_LINK_URL + ': ', src);
5320                 if ((str !== '') && (str !== null)) {
5321                     el.setAttribute('src', str);
5322                 } else if (str === null) {
5323                     el.parentNode.removeChild(el);
5324                     this.currentElement = [];
5325                     this.nodeChange();
5326                 }
5327                 this.closeWindow();
5328                 this.toolbar.set('disabled', false);
5329             }, this, true);
5330         },
5331         /**
5332         * @private
5333         * @method _handleInsertImageWindowClose
5334         * @description Handles the closing of the Image Properties Window.
5335         */
5336         _handleInsertImageWindowClose: function() {
5337             this.nodeChange();
5338         },
5339         /**
5340         * @private
5341         * @method _isLocalFile
5342         * @param {String} url THe url/string to check
5343         * @description Checks to see if a string (href or img src) is possibly a local file reference..
5344         */
5345         _isLocalFile: function(url) {
5346             if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5347                 return true;
5348             }
5349             return false;
5350         },
5351         /**
5352         * @private
5353         * @method _handleCreateLinkClick
5354         * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5355         */
5356         _handleCreateLinkClick: function() {
5357             if (this.get('limitCommands')) {
5358                 if (!this.toolbar.getButtonByValue('createlink')) {
5359                     return false;
5360                 }
5361             }
5362         
5363             this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5365             this.on('afterExecCommand', function() {
5366                 var el = this.currentElement[0],
5367                     url = '';
5369                 if (el) {
5370                     if (el.getAttribute('href', 2) !== null) {
5371                         url = el.getAttribute('href', 2);
5372                     }
5373                 }
5374                 var str = prompt(this.STR_LINK_URL + ': ', url);
5375                 if ((str !== '') && (str !== null)) {
5376                     var urlValue = str;
5377                     if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5378                         if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5379                             //Found an @ sign, prefix with mailto:
5380                             urlValue = 'mailto:' + urlValue;
5381                         } else {
5382                             /* :// not found adding */
5383                             if (urlValue.substring(0, 1) != '#') {
5384                                 //urlValue = 'http:/'+'/' + urlValue;
5385                             }
5386                         }
5387                     }
5388                     el.setAttribute('href', urlValue);
5389                 } else if (str !== null) {
5390                     var _span = this._getDoc().createElement('span');
5391                     _span.innerHTML = el.innerHTML;
5392                     Dom.addClass(_span, 'yui-non');
5393                     el.parentNode.replaceChild(_span, el);
5394                 }
5395                 this.closeWindow();
5396                 this.toolbar.set('disabled', false);
5397             }, this);
5399         },
5400         /**
5401         * @private
5402         * @method _handleCreateLinkWindowClose
5403         * @description Handles the closing of the Link Properties Window.
5404         */
5405         _handleCreateLinkWindowClose: function() {
5406             this.nodeChange();
5407             this.currentElement = [];
5408         },
5409         /**
5410         * @method render
5411         * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5412         */
5413         render: function() {
5414             if (this._rendered) {
5415                 return false;
5416             }
5417             if (!this.DOMReady) {
5418                 this._queue[this._queue.length] = ['render', arguments];
5419                 return false;
5420             }
5421             if (this.get('element')) {
5422                 if (this.get('element').tagName) {
5423                     this._textarea = true;
5424                     if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5425                         this._textarea = false;
5426                     }
5427                 } else {
5428                     return false;
5429                 }
5430             } else {
5431                 return false;
5432             }
5433             this._rendered = true;
5434             var self = this;
5435             window.setTimeout(function() {
5436                 self._render.call(self);
5437             }, 4);
5438         },
5439         /**
5440         * @private
5441         * @method _render
5442         * @description Causes the toolbar and the editor to render and replace the textarea.
5443         */
5444         _render: function() {
5445             var self = this;
5446             this.set('textarea', this.get('element'));
5448             this.get('element_cont').setStyle('display', 'none');
5449             this.get('element_cont').addClass(this.CLASS_CONTAINER);
5451             this.set('iframe', this._createIframe());
5452             window.setTimeout(function() {
5453                 self._setInitialContent.call(self);
5454             }, 10);
5456             this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5458             if (this.get('disabled')) {
5459                 this._disableEditor(true);
5460             }
5462             var tbarConf = this.get('toolbar');
5463             //Create Toolbar instance
5464             if (tbarConf instanceof Toolbar) {
5465                 this.toolbar = tbarConf;
5466                 //Set the toolbar to disabled until content is loaded
5467                 this.toolbar.set('disabled', true);
5468             } else {
5469                 //Set the toolbar to disabled until content is loaded
5470                 tbarConf.disabled = true;
5471                 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5472             }
5474             this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5476             
5477             this.toolbar.on('toolbarCollapsed', function() {
5478                 if (this.currentWindow) {
5479                     this.moveWindow();
5480                 }
5481             }, this, true);
5482             this.toolbar.on('toolbarExpanded', function() {
5483                 if (this.currentWindow) {
5484                     this.moveWindow();
5485                 }
5486             }, this, true);
5487             this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5488             
5489             this.toolbar.on('colorPickerClicked', function(o) {
5490                 this._handleColorPicker(o);
5491                 return false; //Stop the buttonClick event
5492             }, this, true);
5494             this.toolbar.on('alignClick', this._handleAlign, this, true);
5495             this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5496             this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5497             this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5498             this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5499             this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5500             
5502             //Replace Textarea with editable area
5503             this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5505             
5506             this.setStyle('visibility', 'hidden');
5507             this.setStyle('position', 'absolute');
5508             this.setStyle('top', '-9999px');
5509             this.setStyle('left', '-9999px');
5510             this.get('element_cont').appendChild(this.get('element'));
5511             this.get('element_cont').setStyle('display', 'block');
5514             Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5515             this.get('iframe').addClass(this.CLASS_EDITABLE);
5517             //Set height and width of editor container
5518             this.get('element_cont').setStyle('width', this.get('width'));
5519             Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5521             this.get('iframe').setStyle('width', '100%'); //WIDTH
5522             this.get('iframe').setStyle('height', '100%');
5524             this._setupDD();
5526             window.setTimeout(function() {
5527                 self._setupAfterElement.call(self);
5528             }, 0);
5529             this.fireEvent('afterRender', { type: 'afterRender', target: this });
5530         },
5531         /**
5532         * @method execCommand
5533         * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5534         * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5535         * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5536         */
5537         execCommand: function(action, value) {
5538             var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5539             if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5540                 this.STOP_EXEC_COMMAND = false;
5541                 return false;
5542             }
5543             this._lastCommand = action;
5544             this._setMarkupType(action);
5545             if (this.browser.ie) {
5546                 this._getWindow().focus();
5547             }
5548             var exec = true;
5549             
5550             if (this.get('limitCommands')) {
5551                 if (!this.toolbar.getButtonByValue(action)) {
5552                     exec = false;
5553                 }
5554             }
5556             this.editorDirty = true;
5557             
5558             if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5559                 var retValue = this['cmd_' + action.toLowerCase()](value);
5560                 exec = retValue[0];
5561                 if (retValue[1]) {
5562                     action = retValue[1];
5563                 }
5564                 if (retValue[2]) {
5565                     value = retValue[2];
5566                 }
5567             }
5568             if (exec) {
5569                 try {
5570                     this._getDoc().execCommand(action, false, value);
5571                 } catch(e) {
5572                 }
5573             } else {
5574             }
5575             this.on('afterExecCommand', function() {
5576                 this.unsubscribeAll('afterExecCommand');
5577                 this.nodeChange();
5578             }, this, true);
5579             this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5580             
5581         },
5582     /* {{{  Command Overrides */
5584         /**
5585         * @method cmd_underline
5586         * @param value Value passed from the execCommand method
5587         * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5588         */
5589         cmd_underline: function(value) {
5590             if (!this.browser.webkit) {
5591                 var el = this._getSelectedElement();
5592                 if (el && this._isElement(el, 'span')) {
5593                     if (el.style.textDecoration == 'underline') {
5594                         el.style.textDecoration = 'none';
5595                     } else {
5596                         el.style.textDecoration = 'underline';
5597                     }
5598                     return [false];
5599                 }
5600             }
5601             return [true];
5602         },
5603         /**
5604         * @method cmd_backcolor
5605         * @param value Value passed from the execCommand method
5606         * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5607         */
5608         cmd_backcolor: function(value) {
5609             var exec = true,
5610                 el = this._getSelectedElement(),
5611                 action = 'backcolor';
5613             if (this.browser.gecko || this.browser.opera) {
5614                 this._setEditorStyle(true);
5615                 action = 'hilitecolor';
5616             }
5618             if (!this._isElement(el, 'body') && !this._hasSelection()) {
5619                 Dom.setStyle(el, 'background-color', value);
5620                 this._selectNode(el);
5621                 exec = false;
5622             } else if (!this._isElement(el, 'body') && this._hasSelection()) {
5623                 Dom.setStyle(el, 'background-color', value);
5624                 this._selectNode(el);
5625                 exec = false;
5626             } else {
5627                 if (this.get('insert')) {
5628                     el = this._createInsertElement({ backgroundColor: value });
5629                 } else {
5630                     this._createCurrentElement('span', { backgroundColor: value });
5631                     this._selectNode(this.currentElement[0]);
5632                 }
5633                 exec = false;
5634             }
5636             return [exec, action];
5637         },
5638         /**
5639         * @method cmd_forecolor
5640         * @param value Value passed from the execCommand method
5641         * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
5642         */
5643         cmd_forecolor: function(value) {
5644             var exec = true,
5645                 el = this._getSelectedElement();
5646                 
5648                 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5649                     Dom.setStyle(el, 'color', value);
5650                     this._selectNode(el);
5651                     exec = false;
5652                 } else if (!this._isElement(el, 'body') && this._hasSelection()) {
5653                     Dom.setStyle(el, 'color', value);
5654                     this._selectNode(el);
5655                     exec = false;
5656                 } else {
5657                     if (this.get('insert')) {
5658                         el = this._createInsertElement({ color: value });
5659                     } else {
5660                         this._createCurrentElement('span', { color: value });
5661                         this._selectNode(this.currentElement[0]);
5662                     }
5663                     exec = false;
5664                 }
5665                 return [exec];
5666         },
5667         /**
5668         * @method cmd_unlink
5669         * @param value Value passed from the execCommand method
5670         * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
5671         */
5672         cmd_unlink: function(value) {
5673             this._swapEl(this.currentElement[0], 'span', function(el) {
5674                 el.className = 'yui-non';
5675             });
5676             return [false];
5677         },
5678         /**
5679         * @method cmd_createlink
5680         * @param value Value passed from the execCommand method
5681         * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
5682         */
5683         cmd_createlink: function(value) {
5684             var el = this._getSelectedElement(), _a = null;
5685             if (this._hasParent(el, 'a')) {
5686                 this.currentElement[0] = this._hasParent(el, 'a');
5687             } else if (!this._isElement(el, 'a')) {
5688                 this._createCurrentElement('a');
5689                 _a = this._swapEl(this.currentElement[0], 'a');
5690                 this.currentElement[0] = _a;
5691             } else {
5692                 this.currentElement[0] = el;
5693             }
5694             return [false];
5695         },
5696         /**
5697         * @method cmd_insertimage
5698         * @param value Value passed from the execCommand method
5699         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
5700         */
5701         cmd_insertimage: function(value) {
5702             var exec = true, _img = null, action = 'insertimage',
5703                 el = this._getSelectedElement();
5705             if (value === '') {
5706                 value = this.get('blankimage');
5707             }
5709             /**
5710             * @knownissue
5711             * @browser Safari 2.x
5712             * @description The issue here is that we have no way of knowing where the cursor position is
5713             * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5714             */
5715             
5716             if (this._isElement(el, 'img')) {
5717                 this.currentElement[0] = el;
5718                 exec = false;
5719             } else {
5720                 if (this._getDoc().queryCommandEnabled(action)) {
5721                     this._getDoc().execCommand('insertimage', false, value);
5722                     var imgs = this._getDoc().getElementsByTagName('img');
5723                     for (var i = 0; i < imgs.length; i++) {
5724                         if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
5725                             YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
5726                             this.currentElement[0] = imgs[i];
5727                         }
5728                     }
5729                     exec = false;
5730                 } else {
5731                     if (el == this._getDoc().body) {
5732                         _img = this._getDoc().createElement('img');
5733                         _img.setAttribute('src', value);
5734                         YAHOO.util.Dom.addClass(_img, 'yui-img');
5735                         this._getDoc().body.appendChild(_img);
5736                     } else {
5737                         this._createCurrentElement('img');
5738                         _img = this._getDoc().createElement('img');
5739                         _img.setAttribute('src', value);
5740                         YAHOO.util.Dom.addClass(_img, 'yui-img');
5741                         this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
5742                     }
5743                     this.currentElement[0] = _img;
5744                     exec = false;
5745                 }
5746             }
5747             return [exec];
5748         },
5749         /**
5750         * @method cmd_inserthtml
5751         * @param value Value passed from the execCommand method
5752         * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
5753         */
5754         cmd_inserthtml: function(value) {
5755             var exec = true, action = 'inserthtml', _span = null, _range = null;
5756             /**
5757             * @knownissue
5758             * @browser Safari 2.x
5759             * @description The issue here is that we have no way of knowing where the cursor position is
5760             * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5761             */
5762             if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
5763                 this._createCurrentElement('img');
5764                 _span = this._getDoc().createElement('span');
5765                 _span.innerHTML = value;
5766                 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
5767                 exec = false;
5768             } else if (this.browser.ie) {
5769                 _range = this._getRange();
5770                 if (_range.item) {
5771                     _range.item(0).outerHTML = value;
5772                 } else {
5773                     _range.pasteHTML(value);
5774                 }
5775                 exec = false;                    
5776             }
5777             return [exec];
5778         },
5779         /**
5780         * @method cmd_list
5781         * @param tag The tag of the list you want to create (eg, ul or ol)
5782         * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
5783         */
5784         cmd_list: function(tag) {
5785             var exec = true, list = null, li = 0, el = null, str = '',
5786                 selEl = this._getSelectedElement(), action = 'insertorderedlist';
5787                 if (tag == 'ul') {
5788                     action = 'insertunorderedlist';
5789                 }
5790             /**
5791             * @knownissue Safari 2.+ doesn't support ordered and unordered lists
5792             * @browser Safari 2.x
5793             * The issue with this workaround is that when applied to a set of text
5794             * that has BR's in it, Safari may or may not pick up the individual items as
5795             * list items. This is fixed in WebKit (Safari 3)
5796             * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
5797             */
5798             //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
5799             if (this.browser.webkit) {
5800                 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
5801                     el = selEl.parentNode;
5802                     list = this._getDoc().createElement('span');
5803                     YAHOO.util.Dom.addClass(list, 'yui-non');
5804                     str = '';
5805                     var lis = el.getElementsByTagName('li');
5806                     for (li = 0; li < lis.length; li++) {
5807                         str += '<div>' + lis[li].innerHTML + '</div>';
5808                     }
5809                     list.innerHTML = str;
5810                     this.currentElement[0] = el;
5811                     this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
5812                 } else {
5813                     this._createCurrentElement(tag.toLowerCase());
5814                     list = this._getDoc().createElement(tag);
5815                     for (li = 0; li < this.currentElement.length; li++) {
5816                         var newli = this._getDoc().createElement('li');
5817                         newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non">&nbsp;</span>&nbsp;';
5818                         list.appendChild(newli);
5819                         if (li > 0) {
5820                             this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
5821                         }
5822                     }
5823                     this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
5824                     this.currentElement[0] = list;
5825                     var _h = this.currentElement[0].firstChild;
5826                     _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
5827                     this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
5828                 }
5829                 exec = false;
5830             } else {
5831                 el = this._getSelectedElement();
5832                 if (this._isElement(el, 'li') && this._isElement(el.parentNode, tag) || (this.browser.ie && this._isElement(this._getRange().parentElement, 'li')) || (this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { //we are in a list..
5833                     if (this.browser.ie) {
5834                         if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
5835                             el = el.getElementsByTagName('li')[0];
5836                         }
5837                         str = '';
5838                         var lis2 = el.parentNode.getElementsByTagName('li');
5839                         for (var j = 0; j < lis2.length; j++) {
5840                             str += lis2[j].innerHTML + '<br>';
5841                         }
5842                         var newEl = this._getDoc().createElement('span');
5843                         newEl.innerHTML = str;
5844                         el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
5845                     } else {
5846                         this.nodeChange();
5847                         this._getDoc().execCommand(action, '', el.parentNode);
5848                         this.nodeChange();
5849                     }
5850                     exec = false;
5851                 }
5852                 if (this.browser.opera) {
5853                     var self = this;
5854                     window.setTimeout(function() {
5855                         var liso = self._getDoc().getElementsByTagName('li');
5856                         for (var i = 0; i < liso.length; i++) {
5857                             if (liso[i].innerHTML.toLowerCase() == '<br>') {
5858                                 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
5859                             }
5860                         }
5861                     },30);
5862                 }
5863                 if (this.browser.ie && exec) {
5864                     var html = '';
5865                     if (this._getRange().html) {
5866                         html = '<li>' + this._getRange().html+ '</li>';
5867                     } else {
5868                         var t = this._getRange().text.split('\n');
5869                         if (t.length > 1) {
5870                             html = '';
5871                             for (var ie = 0; ie < t.length; ie++) {
5872                                 html += '<li>' + t[ie] + '</li>';
5873                             }
5874                         } else {
5875                             var txt = this._getRange().text;
5876                             if (txt === '') {
5877                                 html = '<li id="new_list_item">' + txt + '</li>';
5878                             } else {
5879                                 html = '<li>' + txt + '</li>';
5880                             }
5881                         }
5882                     }
5883                     this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
5884                     var new_item = this._getDoc().getElementById('new_list_item');
5885                     if (new_item) {
5886                         var range = this._getDoc().body.createTextRange();
5887                         range.moveToElementText(new_item);
5888                         range.collapse(false);
5889                         range.select();                       
5890                         new_item.id = '';
5891                     }
5892                     exec = false;
5893                 }
5894             }
5895             return exec;
5896         },
5897         /**
5898         * @method cmd_insertorderedlist
5899         * @param value Value passed from the execCommand method
5900         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
5901         */
5902         cmd_insertorderedlist: function(value) {
5903             return [this.cmd_list('ol')];
5904         },
5905         /**
5906         * @method cmd_insertunorderedlist 
5907         * @param value Value passed from the execCommand method
5908         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
5909         */
5910         cmd_insertunorderedlist: function(value) {
5911             return [this.cmd_list('ul')];
5912         },
5913         /**
5914         * @method cmd_fontname
5915         * @param value Value passed from the execCommand method
5916         * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
5917         */
5918         cmd_fontname: function(value) {
5919             var exec = true,
5920                 selEl = this._getSelectedElement();
5922             this.currentFont = value;
5923             if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
5924                 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
5925                 exec = false;
5926             } else if (this.get('insert') && !this._hasSelection()) {
5927                 var el = this._createInsertElement({ fontFamily: value });
5928                 exec = false;
5929             }
5930             return [exec];
5931         },
5932         /**
5933         * @method cmd_fontsize
5934         * @param value Value passed from the execCommand method
5935         * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
5936         */
5937         cmd_fontsize: function(value) {
5938             var el = null;
5939             if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
5940                 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
5941             } else if (!this._isElement(this._getSelectedElement(), 'body')) {
5942                 el = this._getSelectedElement();
5943                 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
5944                 if (this.get('insert') && this.browser.ie) {
5945                     var r = this._getRange();
5946                     r.collapse(false);
5947                     r.select();
5948                 } else {
5949                     this._selectNode(el);
5950                 }
5951             } else {
5952                 if (this.get('insert') && !this._hasSelection()) {
5953                     el = this._createInsertElement({ fontSize: value });
5954                     this.currentElement[0] = el;
5955                     this._selectNode(this.currentElement[0]);
5956                 } else {
5957                     this._createCurrentElement('span', {'fontSize': value });
5958                     this._selectNode(this.currentElement[0]);
5959                 }
5960             }
5961             return [false];
5962         },
5963     /* }}} */
5964         /**
5965         * @private
5966         * @method _swapEl
5967         * @param {HTMLElement} el The element to swap with
5968         * @param {String} tagName The tagname of the element that you wish to create
5969         * @param {Function} callback (optional) A function to run on the element after it is created, but before it is replaced. An element reference is passed to this function.
5970         * @description This function will create a new element in the DOM and populate it with the contents of another element. Then it will assume it's place.
5971         */
5972         _swapEl: function(el, tagName, callback) {
5973             var _el = this._getDoc().createElement(tagName);
5974             if (el) {
5975                 _el.innerHTML = el.innerHTML;
5976             }
5977             if (typeof callback == 'function') {
5978                 callback.call(this, _el);
5979             }
5980             if (el) {
5981                 el.parentNode.replaceChild(_el, el);
5982             }
5983             return _el;
5984         },
5985         /**
5986         * @private
5987         * @method _createInsertElement
5988         * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
5989         * @param {Object} css (optional) Object literal containing styles to apply to the new element.
5990         * @return
5991         */
5992         _createInsertElement: function(css) {
5993             this._createCurrentElement('span', css);
5994             var el = this.currentElement[0];
5995             if (this.browser.webkit) {
5996                 //Little Safari Hackery here..
5997                 el.innerHTML = '<span class="yui-non">&nbsp;</span>';
5998                 el = el.firstChild;
5999                 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);                    
6000             } else if (this.browser.ie || this.browser.opera) {
6001                 el.innerHTML = '&nbsp;';
6002             }
6003             this._focusWindow();
6004             this._selectNode(el, true);
6005             return el;
6006         },
6007         /**
6008         * @private
6009         * @method _createCurrentElement
6010         * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6011         * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6012         * @description This is a work around for the various browser issues with execCommand. This method will run <code>execCommand('fontname', false, 'yui-tmp')</code> on the given selection.
6013         * It will then search the document for an element with the font-family set to <strong>yui-tmp</strong> and replace that with another span that has other information in it, then assign the new span to the 
6014         * <code>this.currentElement</code> array, so we now have element references to the elements that were just modified. At this point we can use standard DOM manipulation to change them as we see fit.
6015         */
6016         _createCurrentElement: function(tagName, tagStyle) {
6017             tagName = ((tagName) ? tagName : 'a');
6018             var tar = null,
6019                 el = [],
6020                 _doc = this._getDoc();
6021             
6022             if (this.currentFont) {
6023                 if (!tagStyle) {
6024                     tagStyle = {};
6025                 }
6026                 tagStyle.fontFamily = this.currentFont;
6027                 this.currentFont = null;
6028             }
6029             this.currentElement = [];
6031             var _elCreate = function(tagName, tagStyle) {
6032                 var el = null;
6033                 tagName = ((tagName) ? tagName : 'span');
6034                 tagName = tagName.toLowerCase();
6035                 switch (tagName) {
6036                     case 'h1':
6037                     case 'h2':
6038                     case 'h3':
6039                     case 'h4':
6040                     case 'h5':
6041                     case 'h6':
6042                         el = _doc.createElement(tagName);
6043                         break;
6044                     default:
6045                         el = _doc.createElement(tagName);
6046                         if (tagName === 'span') {
6047                             YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6048                             YAHOO.util.Dom.addClass(el, 'yui-tag');
6049                             el.setAttribute('tag', tagName);
6050                         }
6052                         for (var k in tagStyle) {
6053                             if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6054                                 el.style[k] = tagStyle[k];
6055                             }
6056                         }
6057                         break;
6058                 }
6059                 return el;
6060             };
6062             if (!this._hasSelection()) {
6063                 if (this._getDoc().queryCommandEnabled('insertimage')) {
6064                     this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6065                     var imgs = this._getDoc().getElementsByTagName('img');
6066                     for (var j = 0; j < imgs.length; j++) {
6067                         if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6068                             el = _elCreate(tagName, tagStyle);
6069                             imgs[j].parentNode.replaceChild(el, imgs[j]);
6070                             this.currentElement[this.currentElement.length] = el;
6071                         }
6072                     }
6073                 } else {
6074                     if (this.currentEvent) {
6075                         tar = YAHOO.util.Event.getTarget(this.currentEvent);
6076                     } else {
6077                         //For Safari..
6078                         tar = this._getDoc().body;                        
6079                     }
6080                 }
6081                 if (tar) {
6082                     /**
6083                     * @knownissue
6084                     * @browser Safari 2.x
6085                     * @description The issue here is that we have no way of knowing where the cursor position is
6086                     * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6087                     */
6088                     el = _elCreate(tagName, tagStyle);
6089                     if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6090                         if (this._isElement(tar, 'html')) {
6091                             tar = this._getDoc().body;
6092                         }
6093                         tar.appendChild(el);
6094                     } else if (tar.nextSibling) {
6095                         tar.parentNode.insertBefore(el, tar.nextSibling);
6096                     } else {
6097                         tar.parentNode.appendChild(el);
6098                     }
6099                     //this.currentElement = el;
6100                     this.currentElement[this.currentElement.length] = el;
6101                     this.currentEvent = null;
6102                     if (this.browser.webkit) {
6103                         //Force Safari to focus the new element
6104                         this._getSelection().setBaseAndExtent(el, 0, el, 0);
6105                         if (this.browser.webkit3) {
6106                             this._getSelection().collapseToStart();
6107                         } else {
6108                             this._getSelection().collapse(true);
6109                         }
6110                     }
6111                 }
6112             } else {
6113                 //Force CSS Styling for this action...
6114                 this._setEditorStyle(true);
6115                 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6116                 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6118                 if (!this._isElement(this._getSelectedElement(), 'body')) {
6119                     __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6120                     __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6121                 }
6122                 for (var _els = 0; _els < __els.length; _els++) {
6123                     var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6124                     for (var e = 0; e < _tmp1.length; e++) {
6125                         _tmp[_tmp.length] = _tmp1[e];
6126                     }
6127                 }
6128                 
6129                 for (var i = 0; i < _tmp.length; i++) {
6130                     if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6131                         //TODO Why is this here?!?
6132                         //el = _elCreate(_tmp[i].tagName, tagStyle);
6133                         el = _elCreate(tagName, tagStyle);
6134                         el.innerHTML = _tmp[i].innerHTML;
6135                         if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6136                             var fc = _tmp[i].getElementsByTagName('li')[0];
6137                             _tmp[i].style.fontFamily = 'inherit';
6138                             fc.style.fontFamily = 'inherit';
6139                             el.innerHTML = fc.innerHTML;
6140                             fc.innerHTML = '';
6141                             fc.appendChild(el);
6142                             this.currentElement[this.currentElement.length] = el;
6143                         } else if (this._isElement(_tmp[i], 'li')) {
6144                             _tmp[i].innerHTML = '';
6145                             _tmp[i].appendChild(el);
6146                             _tmp[i].style.fontFamily = 'inherit';
6147                             this.currentElement[this.currentElement.length] = el;
6148                         } else {
6149                             if (_tmp[i].parentNode) {
6150                                 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6151                                 this.currentElement[this.currentElement.length] = el;
6152                                 this.currentEvent = null;
6153                                 if (this.browser.webkit) {
6154                                     //Force Safari to focus the new element
6155                                     this._getSelection().setBaseAndExtent(el, 0, el, 0);
6156                                     if (this.browser.webkit3) {
6157                                         this._getSelection().collapseToStart();
6158                                     } else {
6159                                         this._getSelection().collapse(true);
6160                                     }
6161                                 }
6162                                 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6163                                     this._getSelection().empty();
6164                                 }
6165                                 if (this.browser.gecko) {
6166                                     this._getSelection().collapseToStart();
6167                                 }
6168                             }
6169                         }
6170                     }
6171                 }
6172                 var len = this.currentElement.length;
6173                 for (var o = 0; o < len; o++) {
6174                     if ((o + 1) != len) { //Skip the last one in the list
6175                         if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6176                             if (this._isElement(this.currentElement[o], 'br')) {
6177                                 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6178                             }
6179                         }
6180                     }
6181                 }
6182             }
6183         },
6184         /**
6185         * @method saveHTML
6186         * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6187         * @return String
6188         */
6189         saveHTML: function() {
6190             var html = this.cleanHTML();
6191             if (this._textarea) {
6192                 this.get('element').value = html;
6193             } else {
6194                 this.get('element').innerHTML = html;
6195             }
6196             if (this.get('saveEl') !== this.get('element')) {
6197                 var out = this.get('saveEl');
6198                 if (Lang.isString(out)) {
6199                     out = Dom.get(out);
6200                 }
6201                 if (out) {
6202                     if (out.tagName.toLowerCase() === 'textarea') {
6203                         out.value = html;
6204                     } else {
6205                         out.innerHTML = html;
6206                     }
6207                 }
6208             }
6209             return html;
6210         },
6211         /**
6212         * @method setEditorHTML
6213         * @param {String} incomingHTML The html content to load into the editor
6214         * @description Loads HTML into the editors body
6215         */
6216         setEditorHTML: function(incomingHTML) {
6217             var html = this._cleanIncomingHTML(incomingHTML);
6218             this._getDoc().body.innerHTML = html;
6219             this.nodeChange();
6220         },
6221         /**
6222         * @method getEditorHTML
6223         * @description Gets the unprocessed/unfiltered HTML from the editor
6224         */
6225         getEditorHTML: function() {
6226             var b = this._getDoc().body;
6227             if (b === null) {
6228                 return null;
6229             }
6230             return this._getDoc().body.innerHTML;
6231         },
6232         /**
6233         * @method show
6234         * @description This method needs to be called if the Editor was hidden (like in a TabView or Panel). It is used to reset the editor after being in a container that was set to display none.
6235         */
6236         show: function() {
6237             if (this.browser.gecko) {
6238                 this._setDesignMode('on');
6239                 this._focusWindow();
6240             }
6241             if (this.browser.webkit) {
6242                 var self = this;
6243                 window.setTimeout(function() {
6244                     self._setInitialContent.call(self);
6245                 }, 10);
6246             }
6247             //Adding this will close all other Editor window's when showing this one.
6248             if (this.currentWindow) {
6249                 this.closeWindow();
6250             }
6251             //Put the iframe back in place
6252             this.get('iframe').setStyle('position', 'static');
6253             this.get('iframe').setStyle('left', '');
6254         },
6255         /**
6256         * @method hide
6257         * @description This method needs to be called if the Editor is to be hidden (like in a TabView or Panel). It should be called to clear timeouts and close open editor windows.
6258         */
6259         hide: function() {
6260             //Adding this will close all other Editor window's.
6261             if (this.currentWindow) {
6262                 this.closeWindow();
6263             }
6264             if (this._fixNodesTimer) {
6265                 clearTimeout(this._fixNodesTimer);
6266                 this._fixNodesTimer = null;
6267             }
6268             if (this._nodeChangeTimer) {
6269                 clearTimeout(this._nodeChangeTimer);
6270                 this._nodeChangeTimer = null;
6271             }
6272             this._lastNodeChange = 0;
6273             //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6274             this.get('iframe').setStyle('position', 'absolute');
6275             this.get('iframe').setStyle('left', '-9999px');
6276         },
6277         /**
6278         * @method _cleanIncomingHTML
6279         * @param {String} html The unfiltered HTML
6280         * @description Process the HTML with a few regexes to clean it up and stabilize the input
6281         * @return {String} The filtered HTML
6282         */
6283         _cleanIncomingHTML: function(html) {
6284             html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6285             html = html.replace(/<\/strong>/gi, '</b>');   
6287             //replace embed before em check
6288             html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6289             html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6291             html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6292             html = html.replace(/<\/em>/gi, '</i>');
6293             
6294             //Put embed tags back in..
6295             html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6296             html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6297             if (this.get('plainText')) {
6298                 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6299                 html = html.replace(/  /gi, '&nbsp;&nbsp;'); //Replace all double spaces
6300                 html = html.replace(/\t/gi, '&nbsp;&nbsp;&nbsp;&nbsp;'); //Replace all tabs
6301             }
6302             //Removing Script Tags from the Editor
6303             html = html.replace(/<script([^>]*)>/gi, '<bad>');
6304             html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6305             html = html.replace(/&lt;script([^>]*)&gt;/gi, '<bad>');
6306             html = html.replace(/&lt;\/script([^>]*)&gt;/gi, '</bad>');
6307             //Replace the line feeds
6308             html = html.replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6309             //Remove Bad HTML elements (used to be script nodes)
6310             html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6311             //Replace the lines feeds
6312             html = html.replace(/<YUI_LF>/g, '\n');
6313             return html;
6314         },
6315         /**
6316         * @method cleanHTML
6317         * @param {String} html The unfiltered HTML
6318         * @description Process the HTML with a few regexes to clean it up and stabilize the output
6319         * @return {String} The filtered HTML
6320         */
6321         cleanHTML: function(html) {
6322             //Start Filtering Output
6323             //Begin RegExs..
6324             if (!html) { 
6325                 html = this.getEditorHTML();
6326             }
6327             var markup = this.get('markup');
6328             //Make some backups...
6329             html = this.pre_filter_linebreaks(html, markup);
6331                     html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6332                     html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6334                     html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6335                     html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6337                     html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6338                     html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6339                     html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6340                     html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6342                     html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6343                     html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6345             //Convert b and i tags to strong and em tags
6346             if ((markup == 'semantic') || (markup == 'xhtml')) {
6347                 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6348                 html = html.replace(/<\/i>/gi, '</em>');
6349                 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6350                 html = html.replace(/<\/b>/gi, '</strong>');
6351             }
6352             
6353             //Case Changing
6354                     html = html.replace(/<font/gi, '<font');
6355                     html = html.replace(/<\/font>/gi, '</font>');
6356                     html = html.replace(/<span/gi, '<span');
6357                     html = html.replace(/<\/span>/gi, '</span>');
6358             if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6359                 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6360                 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6361                 if (this.browser.webkit) {
6362                     html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6363                     html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6364                 }
6365                 html = html.replace(/\/u>/gi, '/span>');
6366                 if (markup == 'css') {
6367                     html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6368                     html = html.replace(/<\/em>/gi, '</i>');
6369                     html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6370                     html = html.replace(/<\/strong>/gi, '</b>');
6371                     html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6372                     html = html.replace(/\/b>/gi, '/span>');
6373                     html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6374                     html = html.replace(/\/i>/gi, '/span>');
6375                 }
6376                 html = html.replace(/  /gi, ' '); //Replace all double spaces and replace with a single
6377             } else {
6378                         html = html.replace(/<u/gi, '<u');
6379                         html = html.replace(/\/u>/gi, '/u>');
6380             }
6381                     html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6382                     html = html.replace(/\/ol>/gi, '/ol>');
6383                     html = html.replace(/<li/gi, '<li');
6384                     html = html.replace(/\/li>/gi, '/li>');
6385             html = this.filter_safari(html);
6387             html = this.filter_internals(html);
6389             html = this.filter_all_rgb(html);
6391             //Replace our backups with the real thing
6392             html = this.post_filter_linebreaks(html, markup);
6394             if (markup == 'xhtml') {
6395                         html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6396                         html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6397             } else {
6398                         html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6399                         html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6400             }
6401                     html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6402                     html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6404             html = this.filter_invalid_lists(html);
6406                     html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6407                     html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6409                     html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6410                     html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6411             
6412             //This should fix &amp;s in URL's
6413             html = html.replace(' &amp; ', 'YUI_AMP');
6414             html = html.replace('&amp;', '&');
6415             html = html.replace('YUI_AMP', '&amp;');
6417             //Trim the output, removing whitespace from the beginning and end
6418             html = YAHOO.lang.trim(html);
6420             if (this.get('removeLineBreaks')) {
6421                 html = html.replace(/\n/g, '').replace(/\r/g, '');
6422                 html = html.replace(/  /gi, ' '); //Replace all double spaces and replace with a single
6423             }
6424             
6425             //First empty span
6426             if (html.substring(0, 6).toLowerCase() == '<span>')  {
6427                 html = html.substring(6);
6428                 //Last empty span
6429                 if (html.substring(html.length - 7, html.length).toLowerCase() == '</span>')  {
6430                     html = html.substring(0, html.length - 7);
6431                 }
6432             }
6434             for (var v in this.invalidHTML) {
6435                 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6436                     if (Lang.isObject(v) && v.keepContents) {
6437                         html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6438                     } else {
6439                         html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6440                     }
6441                 }
6442             }
6444             this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6446             return html;
6447         },
6448         /**
6449         * @method filter_invalid_lists
6450         * @param String html The HTML string to filter
6451         * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6452         */
6453         filter_invalid_lists: function(html) {
6454             html = html.replace(/<\/li>\n/gi, '</li>');
6456             html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6457             html = html.replace(/<\/ol>/gi, '</ol></li>');
6458             html = html.replace(/<\/ol><\/li>\n/gi, "</ol>\n");
6460             html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6461             html = html.replace(/<\/ul>/gi, '</ul></li>');
6462             html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>\n");
6464             html = html.replace(/<\/li>/gi, "</li>\n");
6465             html = html.replace(/<\/ol>/gi, "</ol>\n");
6466             html = html.replace(/<ol>/gi, "<ol>\n");
6467             html = html.replace(/<ul>/gi, "<ul>\n");
6468             return html;
6469         },
6470         /**
6471         * @method filter_safari
6472         * @param String html The HTML string to filter
6473         * @description Filters strings specific to Safari
6474         * @return String
6475         */
6476         filter_safari: function(html) {
6477             if (this.browser.webkit) {
6478                 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6479                 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, '&nbsp;&nbsp;&nbsp;&nbsp;');
6480                 html = html.replace(/Apple-style-span/gi, '');
6481                 html = html.replace(/style="line-height: normal;"/gi, '');
6482                 //Remove bogus LI's
6483                 html = html.replace(/<li><\/li>/gi, '');
6484                 html = html.replace(/<li> <\/li>/gi, '');
6485                 html = html.replace(/<li>  <\/li>/gi, '');
6486                 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6487                 if (this.get('ptags')) {
6488                             html = html.replace(/<div([^>]*)>/g, '<p$1>');
6489                                     html = html.replace(/<\/div>/gi, '</p>');
6490                 } else {
6491                     html = html.replace(/<div>/gi, '');
6492                                     html = html.replace(/<\/div>/gi, '<br>');
6493                 }
6494             }
6495             return html;
6496         },
6497         /**
6498         * @method filter_internals
6499         * @param String html The HTML string to filter
6500         * @description Filters internal RTE strings and bogus attrs we don't want
6501         * @return String
6502         */
6503         filter_internals: function(html) {
6504                     html = html.replace(/\r/g, '');
6505             //Fix stuff we don't want
6506                 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6507             //Fix last BR in LI
6508                     html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6510                     html = html.replace(/yui-tag-span/gi, '');
6511                     html = html.replace(/yui-tag/gi, '');
6512                     html = html.replace(/yui-non/gi, '');
6513                     html = html.replace(/yui-img/gi, '');
6514                     html = html.replace(/ tag="span"/gi, '');
6515                     html = html.replace(/ class=""/gi, '');
6516                     html = html.replace(/ style=""/gi, '');
6517                     html = html.replace(/ class=" "/gi, '');
6518                     html = html.replace(/ class="  "/gi, '');
6519                     html = html.replace(/ target=""/gi, '');
6520                     html = html.replace(/ title=""/gi, '');
6522             if (this.browser.ie) {
6523                         html = html.replace(/ class= /gi, '');
6524                         html = html.replace(/ class= >/gi, '');
6525                         html = html.replace(/_height="([^>])"/gi, '');
6526                         html = html.replace(/_width="([^>])"/gi, '');
6527             }
6528             
6529             return html;
6530         },
6531         /**
6532         * @method filter_all_rgb
6533         * @param String str The HTML string to filter
6534         * @description Converts all RGB color strings found in passed string to a hex color, example: style="color: rgb(0, 255, 0)" converts to style="color: #00ff00"
6535         * @return String
6536         */
6537         filter_all_rgb: function(str) {
6538             var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
6539             var arr = str.match(exp);
6540             if (Lang.isArray(arr)) {
6541                 for (var i = 0; i < arr.length; i++) {
6542                     var color = this.filter_rgb(arr[i]);
6543                     str = str.replace(arr[i].toString(), color);
6544                 }
6545             }
6546             
6547             return str;
6548         },
6549         /**
6550         * @method filter_rgb
6551         * @param String css The CSS string containing rgb(#,#,#);
6552         * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
6553         * @return String
6554         */
6555         filter_rgb: function(css) {
6556             if (css.toLowerCase().indexOf('rgb') != -1) {
6557                 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
6558                 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
6559             
6560                 if (rgb.length == 5) {
6561                     var r = parseInt(rgb[1], 10).toString(16);
6562                     var g = parseInt(rgb[2], 10).toString(16);
6563                     var b = parseInt(rgb[3], 10).toString(16);
6565                     r = r.length == 1 ? '0' + r : r;
6566                     g = g.length == 1 ? '0' + g : g;
6567                     b = b.length == 1 ? '0' + b : b;
6569                     css = "#" + r + g + b;
6570                 }
6571             }
6572             return css;
6573         },
6574         /**
6575         * @method pre_filter_linebreaks
6576         * @param String html The HTML to filter
6577         * @param String markup The markup type to filter to
6578         * @description HTML Pre Filter
6579         * @return String
6580         */
6581         pre_filter_linebreaks: function(html, markup) {
6582             if (this.browser.webkit) {
6583                         html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
6584                         html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
6585             }
6586                     html = html.replace(/<br>/gi, '<YUI_BR>');
6587                     html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
6588                     html = html.replace(/<br\/>/gi, '<YUI_BR>');
6589                     html = html.replace(/<br \/>/gi, '<YUI_BR>');
6590                     html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
6591                     html = html.replace(/<p>(&nbsp;|&#160;)<\/p>/g, '<YUI_BR>');            
6592                     html = html.replace(/<p><br>&nbsp;<\/p>/gi, '<YUI_BR>');
6593                     html = html.replace(/<p>&nbsp;<\/p>/gi, '<YUI_BR>');
6594             //Fix last BR
6595                 html = html.replace(/<YUI_BR>$/, '');
6596             //Fix last BR in P
6597                 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
6598             if (this.browser.ie) {
6599                     html = html.replace(/&nbsp;&nbsp;&nbsp;&nbsp;/g, '\t');
6600             }
6601             return html;
6602         },
6603         /**
6604         * @method post_filter_linebreaks
6605         * @param String html The HTML to filter
6606         * @param String markup The markup type to filter to
6607         * @description HTML Pre Filter
6608         * @return String
6609         */
6610         post_filter_linebreaks: function(html, markup) {
6611             if (markup == 'xhtml') {
6612                         html = html.replace(/<YUI_BR>/g, '<br />');
6613             } else {
6614                         html = html.replace(/<YUI_BR>/g, '<br>');
6615             }
6616             return html;
6617         },
6618         /**
6619         * @method clearEditorDoc
6620         * @description Clear the doc of the Editor
6621         */
6622         clearEditorDoc: function() {
6623             this._getDoc().body.innerHTML = '&nbsp;';
6624         },
6625         /**
6626         * @method openWindow
6627         * @description Override Method for Advanced Editor
6628         */
6629         openWindow: function(win) {
6630         },
6631         /**
6632         * @method moveWindow
6633         * @description Override Method for Advanced Editor
6634         */
6635         moveWindow: function() {
6636         },
6637         /**
6638         * @private
6639         * @method _closeWindow
6640         * @description Override Method for Advanced Editor
6641         */
6642         _closeWindow: function() {
6643         },
6644         /**
6645         * @method closeWindow
6646         * @description Override Method for Advanced Editor
6647         */
6648         closeWindow: function() {
6649             //this.unsubscribeAll('afterExecCommand');
6650             this.toolbar.resetAllButtons();
6651             this._focusWindow();        
6652         },
6653         /**
6654         * @method destroy
6655         * @description Destroys the editor, all of it's elements and objects.
6656         * @return {Boolean}
6657         */
6658         destroy: function() {
6659             if (this.resize) {
6660                 this.resize.destroy();
6661             }
6662             if (this.dd) {
6663                 this.dd.unreg();
6664             }
6665             if (this.get('panel')) {
6666                 this.get('panel').destroy();
6667             }
6668             this.saveHTML();
6669             this.toolbar.destroy();
6670             this.setStyle('visibility', 'visible');
6671             this.setStyle('position', 'static');
6672             this.setStyle('top', '');
6673             this.setStyle('left', '');
6674             var textArea = this.get('element');
6675             this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
6676             this.get('element_cont').get('element').innerHTML = '';
6677             this.set('handleSubmit', false); //Remove the submit handler
6678             return true;
6679         },        
6680         /**
6681         * @method toString
6682         * @description Returns a string representing the editor.
6683         * @return {String}
6684         */
6685         toString: function() {
6686             var str = 'SimpleEditor';
6687             if (this.get && this.get('element_cont')) {
6688                 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
6689             }
6690             return str;
6691         }
6692     });
6695 * @event toolbarLoaded
6696 * @description Event is fired during the render process directly after the Toolbar is loaded. Allowing you to attach events to the toolbar. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6697 * @type YAHOO.util.CustomEvent
6700 * @event cleanHTML
6701 * @description Event is fired after the cleanHTML method is called.
6702 * @type YAHOO.util.CustomEvent
6705 * @event afterRender
6706 * @description Event is fired after the render process finishes. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6707 * @type YAHOO.util.CustomEvent
6710 * @event editorContentLoaded
6711 * @description Event is fired after the editor iframe's document fully loads and fires it's onload event. From here you can start injecting your own things into the document. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6712 * @type YAHOO.util.CustomEvent
6715 * @event beforeNodeChange
6716 * @description Event fires at the beginning of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6717 * @type YAHOO.util.CustomEvent
6720 * @event afterNodeChange
6721 * @description Event fires at the end of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6722 * @type YAHOO.util.CustomEvent
6725 * @event beforeExecCommand
6726 * @description Event fires at the beginning of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6727 * @type YAHOO.util.CustomEvent
6730 * @event afterExecCommand
6731 * @description Event fires at the end of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6732 * @type YAHOO.util.CustomEvent
6735 * @event editorMouseUp
6736 * @param {Event} ev The DOM Event that occured
6737 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6738 * @type YAHOO.util.CustomEvent
6741 * @event editorMouseDown
6742 * @param {Event} ev The DOM Event that occured
6743 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6744 * @type YAHOO.util.CustomEvent
6747 * @event editorDoubleClick
6748 * @param {Event} ev The DOM Event that occured
6749 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6750 * @type YAHOO.util.CustomEvent
6753 * @event editorClick
6754 * @param {Event} ev The DOM Event that occured
6755 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6756 * @type YAHOO.util.CustomEvent
6759 * @event editorKeyUp
6760 * @param {Event} ev The DOM Event that occured
6761 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6762 * @type YAHOO.util.CustomEvent
6765 * @event editorKeyPress
6766 * @param {Event} ev The DOM Event that occured
6767 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6768 * @type YAHOO.util.CustomEvent
6771 * @event editorKeyDown
6772 * @param {Event} ev The DOM Event that occured
6773 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6774 * @type YAHOO.util.CustomEvent
6777 * @event beforeEditorMouseUp
6778 * @param {Event} ev The DOM Event that occured
6779 * @description Fires before editor event, returning false will stop the internal processing.
6780 * @type YAHOO.util.CustomEvent
6783 * @event beforeEditorMouseDown
6784 * @param {Event} ev The DOM Event that occured
6785 * @description Fires before editor event, returning false will stop the internal processing.
6786 * @type YAHOO.util.CustomEvent
6789 * @event beforeEditorDoubleClick
6790 * @param {Event} ev The DOM Event that occured
6791 * @description Fires before editor event, returning false will stop the internal processing.
6792 * @type YAHOO.util.CustomEvent
6795 * @event beforeEditorClick
6796 * @param {Event} ev The DOM Event that occured
6797 * @description Fires before editor event, returning false will stop the internal processing.
6798 * @type YAHOO.util.CustomEvent
6801 * @event beforeEditorKeyUp
6802 * @param {Event} ev The DOM Event that occured
6803 * @description Fires before editor event, returning false will stop the internal processing.
6804 * @type YAHOO.util.CustomEvent
6807 * @event beforeEditorKeyPress
6808 * @param {Event} ev The DOM Event that occured
6809 * @description Fires before editor event, returning false will stop the internal processing.
6810 * @type YAHOO.util.CustomEvent
6813 * @event beforeEditorKeyDown
6814 * @param {Event} ev The DOM Event that occured
6815 * @description Fires before editor event, returning false will stop the internal processing.
6816 * @type YAHOO.util.CustomEvent
6820     /**
6821      * @description Singleton object used to track the open window objects and panels across the various open editors
6822      * @class EditorInfo
6823      * @static
6824     */
6825     YAHOO.widget.EditorInfo = {
6826         /**
6827         * @private
6828         * @property _instances
6829         * @description A reference to all editors on the page.
6830         * @type Object
6831         */
6832         _instances: {},
6833         /**
6834         * @private
6835         * @property blankImage
6836         * @description A reference to the blankImage url
6837         * @type String 
6838         */
6839         blankImage: '',
6840         /**
6841         * @private
6842         * @property window
6843         * @description A reference to the currently open window object in any editor on the page.
6844         * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
6845         */
6846         window: {},
6847         /**
6848         * @private
6849         * @property panel
6850         * @description A reference to the currently open panel in any editor on the page.
6851         * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
6852         */
6853         panel: null,
6854         /**
6855         * @method getEditorById
6856         * @description Returns a reference to the Editor object associated with the given textarea
6857         * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
6858         * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
6859         */
6860         getEditorById: function(id) {
6861             if (!YAHOO.lang.isString(id)) {
6862                 //Not a string, assume a node Reference
6863                 id = id.id;
6864             }
6865             if (this._instances[id]) {
6866                 return this._instances[id];
6867             }
6868             return false;
6869         },
6870         /**
6871         * @method toString
6872         * @description Returns a string representing the EditorInfo.
6873         * @return {String}
6874         */
6875         toString: function() {
6876             var len = 0;
6877             for (var i in this._instances) {
6878                 if (Lang.hasOwnProperty(this._instances, i)) {
6879                     len++;
6880                 }
6881             }
6882             return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
6883         }
6884     };
6888     
6889 })();
6890 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.6.0", build: "1321"});