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