Bug 5917 : Converted templates
[koha.git] / koha-tt / intranet-tmpl / prog / en / lib / yui / editor / editor-debug.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
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         YAHOO.log('ToolbarButton Initalizing', 'info', 'ToolbarButton');
70         YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
71         
72         if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
73             attrs = el;
74         }
75         var local_attrs = (attrs || {});
76
77         var oConfig = {
78             element: null,
79             attributes: local_attrs
80         };
81
82         if (!oConfig.attributes.type) {
83             oConfig.attributes.type = 'push';
84         }
85         
86         oConfig.element = document.createElement('span');
87         oConfig.element.setAttribute('unselectable', 'on');
88         oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
89         oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
90         oConfig.element.firstChild.firstChild.tabIndex = '-1';
91         oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId());
92         oConfig.element.id = oConfig.attributes.id;
93
94         YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
95     };
96
97     YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
98         /**
99         * @property buttonType
100         * @private
101         * @description Tells if the Button is a Rich Button or a Simple Button
102         */
103         buttonType: 'normal',
104         /**
105         * @method _handleMouseOver
106         * @private
107         * @description Adds classes to the button elements on mouseover (hover)
108         */
109         _handleMouseOver: function() {
110             if (!this.get('disabled')) {
111                 this.addClass('yui-button-hover');
112                 this.addClass('yui-' + this.get('type') + '-button-hover');
113             }
114         },
115         /**
116         * @method _handleMouseOut
117         * @private
118         * @description Removes classes from the button elements on mouseout (hover)
119         */
120         _handleMouseOut: function() {
121             this.removeClass('yui-button-hover');
122             this.removeClass('yui-' + this.get('type') + '-button-hover');
123         },
124         /**
125         * @method checkValue
126         * @param {String} value The value of the option that we want to mark as selected
127         * @description Select an option by value
128         */
129         checkValue: function(value) {
130             if (this.get('type') == 'menu') {
131                 var opts = this._button.options;
132                 for (var i = 0; i < opts.length; i++) {
133                     if (opts[i].value == value) {
134                         opts.selectedIndex = i;
135                     }
136                 }
137             }
138         },
139         /** 
140         * @method init
141         * @description The ToolbarButton class's initialization method
142         */        
143         init: function(p_oElement, p_oAttributes) {
144             YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
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         YAHOO.log('Toolbar Initalizing', 'info', 'Toolbar');
370         YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
371         
372         if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
373             attrs = el;
374         }
375         var local_attrs = {};
376         if (attrs) {
377             Lang.augmentObject(local_attrs, attrs); //Break the config reference
378         }
379         
380
381         var oConfig = {
382             element: null,
383             attributes: local_attrs
384         };
385         
386         
387         if (Lang.isString(el) && Dom.get(el)) {
388             oConfig.element = Dom.get(el);
389         } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {  
390             oConfig.element = Dom.get(el);
391         }
392         
393
394         if (!oConfig.element) {
395             YAHOO.log('No element defined, creating toolbar container', 'warn', 'Toolbar');
396             oConfig.element = document.createElement('DIV');
397             oConfig.element.id = Dom.generateId();
398             
399             if (local_attrs.container && Dom.get(local_attrs.container)) {
400                 YAHOO.log('Container found in config appending to it (' + Dom.get(local_attrs.container).id + ')', 'info', 'Toolbar');
401                 Dom.get(local_attrs.container).appendChild(oConfig.element);
402             }
403         }
404         
405
406         if (!oConfig.element.id) {
407             oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
408             YAHOO.log('No element ID defined for toolbar container, creating..', 'warn', 'Toolbar');
409         }
410         YAHOO.log('Initing toolbar with id: ' + oConfig.element.id, 'info', 'Toolbar');
411         
412         var fs = document.createElement('fieldset');
413         var lg = document.createElement('legend');
414         lg.innerHTML = 'Toolbar';
415         fs.appendChild(lg);
416         
417         var cont = document.createElement('DIV');
418         oConfig.attributes.cont = cont;
419         Dom.addClass(cont, 'yui-toolbar-subcont');
420         fs.appendChild(cont);
421         oConfig.element.appendChild(fs);
422
423         oConfig.element.tabIndex = -1;
424
425         
426         oConfig.attributes.element = oConfig.element;
427         oConfig.attributes.id = oConfig.element.id;
428
429         this._configuredButtons = [];
430
431         YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
432          
433     };
434
435     YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
436         /**
437         * @protected
438         * @property _configuredButtons
439         * @type Array
440         */
441         _configuredButtons: null,
442         /**
443         * @method _addMenuClasses
444         * @private
445         * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
446         * @param {String} ev The event that fired.
447         * @param {Array} na Array of event information.
448         * @param {Object} o Button config object. 
449         */
450         _addMenuClasses: function(ev, na, o) {
451             Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
452             if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
453                 Dom.addClass(this.element, 'yui-toolbar-select-menu');
454             }
455             var items = this.getItems();
456             for (var i = 0; i < items.length; i++) {
457                 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()));
458                 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
459             }
460         },
461         /** 
462         * @property buttonType
463         * @description The default button to use
464         * @type Object
465         */
466         buttonType: YAHOO.widget.ToolbarButton,
467         /** 
468         * @property dd
469         * @description The DragDrop instance associated with the Toolbar
470         * @type Object
471         */
472         dd: null,
473         /** 
474         * @property _colorData
475         * @description Object reference containing colors hex and text values.
476         * @type Object
477         */
478         _colorData: {
479 /* {{{ _colorData */
480     '#111111': 'Obsidian',
481     '#2D2D2D': 'Dark Gray',
482     '#434343': 'Shale',
483     '#5B5B5B': 'Flint',
484     '#737373': 'Gray',
485     '#8B8B8B': 'Concrete',
486     '#A2A2A2': 'Gray',
487     '#B9B9B9': 'Titanium',
488     '#000000': 'Black',
489     '#D0D0D0': 'Light Gray',
490     '#E6E6E6': 'Silver',
491     '#FFFFFF': 'White',
492     '#BFBF00': 'Pumpkin',
493     '#FFFF00': 'Yellow',
494     '#FFFF40': 'Banana',
495     '#FFFF80': 'Pale Yellow',
496     '#FFFFBF': 'Butter',
497     '#525330': 'Raw Siena',
498     '#898A49': 'Mildew',
499     '#AEA945': 'Olive',
500     '#7F7F00': 'Paprika',
501     '#C3BE71': 'Earth',
502     '#E0DCAA': 'Khaki',
503     '#FCFAE1': 'Cream',
504     '#60BF00': 'Cactus',
505     '#80FF00': 'Chartreuse',
506     '#A0FF40': 'Green',
507     '#C0FF80': 'Pale Lime',
508     '#DFFFBF': 'Light Mint',
509     '#3B5738': 'Green',
510     '#668F5A': 'Lime Gray',
511     '#7F9757': 'Yellow',
512     '#407F00': 'Clover',
513     '#8A9B55': 'Pistachio',
514     '#B7C296': 'Light Jade',
515     '#E6EBD5': 'Breakwater',
516     '#00BF00': 'Spring Frost',
517     '#00FF80': 'Pastel Green',
518     '#40FFA0': 'Light Emerald',
519     '#80FFC0': 'Sea Foam',
520     '#BFFFDF': 'Sea Mist',
521     '#033D21': 'Dark Forrest',
522     '#438059': 'Moss',
523     '#7FA37C': 'Medium Green',
524     '#007F40': 'Pine',
525     '#8DAE94': 'Yellow Gray Green',
526     '#ACC6B5': 'Aqua Lung',
527     '#DDEBE2': 'Sea Vapor',
528     '#00BFBF': 'Fog',
529     '#00FFFF': 'Cyan',
530     '#40FFFF': 'Turquoise Blue',
531     '#80FFFF': 'Light Aqua',
532     '#BFFFFF': 'Pale Cyan',
533     '#033D3D': 'Dark Teal',
534     '#347D7E': 'Gray Turquoise',
535     '#609A9F': 'Green Blue',
536     '#007F7F': 'Seaweed',
537     '#96BDC4': 'Green Gray',
538     '#B5D1D7': 'Soapstone',
539     '#E2F1F4': 'Light Turquoise',
540     '#0060BF': 'Summer Sky',
541     '#0080FF': 'Sky Blue',
542     '#40A0FF': 'Electric Blue',
543     '#80C0FF': 'Light Azure',
544     '#BFDFFF': 'Ice Blue',
545     '#1B2C48': 'Navy',
546     '#385376': 'Biscay',
547     '#57708F': 'Dusty Blue',
548     '#00407F': 'Sea Blue',
549     '#7792AC': 'Sky Blue Gray',
550     '#A8BED1': 'Morning Sky',
551     '#DEEBF6': 'Vapor',
552     '#0000BF': 'Deep Blue',
553     '#0000FF': 'Blue',
554     '#4040FF': 'Cerulean Blue',
555     '#8080FF': 'Evening Blue',
556     '#BFBFFF': 'Light Blue',
557     '#212143': 'Deep Indigo',
558     '#373E68': 'Sea Blue',
559     '#444F75': 'Night Blue',
560     '#00007F': 'Indigo Blue',
561     '#585E82': 'Dockside',
562     '#8687A4': 'Blue Gray',
563     '#D2D1E1': 'Light Blue Gray',
564     '#6000BF': 'Neon Violet',
565     '#8000FF': 'Blue Violet',
566     '#A040FF': 'Violet Purple',
567     '#C080FF': 'Violet Dusk',
568     '#DFBFFF': 'Pale Lavender',
569     '#302449': 'Cool Shale',
570     '#54466F': 'Dark Indigo',
571     '#655A7F': 'Dark Violet',
572     '#40007F': 'Violet',
573     '#726284': 'Smoky Violet',
574     '#9E8FA9': 'Slate Gray',
575     '#DCD1DF': 'Violet White',
576     '#BF00BF': 'Royal Violet',
577     '#FF00FF': 'Fuchsia',
578     '#FF40FF': 'Magenta',
579     '#FF80FF': 'Orchid',
580     '#FFBFFF': 'Pale Magenta',
581     '#4A234A': 'Dark Purple',
582     '#794A72': 'Medium Purple',
583     '#936386': 'Cool Granite',
584     '#7F007F': 'Purple',
585     '#9D7292': 'Purple Moon',
586     '#C0A0B6': 'Pale Purple',
587     '#ECDAE5': 'Pink Cloud',
588     '#BF005F': 'Hot Pink',
589     '#FF007F': 'Deep Pink',
590     '#FF409F': 'Grape',
591     '#FF80BF': 'Electric Pink',
592     '#FFBFDF': 'Pink',
593     '#451528': 'Purple Red',
594     '#823857': 'Purple Dino',
595     '#A94A76': 'Purple Gray',
596     '#7F003F': 'Rose',
597     '#BC6F95': 'Antique Mauve',
598     '#D8A5BB': 'Cool Marble',
599     '#F7DDE9': 'Pink Granite',
600     '#C00000': 'Apple',
601     '#FF0000': 'Fire Truck',
602     '#FF4040': 'Pale Red',
603     '#FF8080': 'Salmon',
604     '#FFC0C0': 'Warm Pink',
605     '#441415': 'Sepia',
606     '#82393C': 'Rust',
607     '#AA4D4E': 'Brick',
608     '#800000': 'Brick Red',
609     '#BC6E6E': 'Mauve',
610     '#D8A3A4': 'Shrimp Pink',
611     '#F8DDDD': 'Shell Pink',
612     '#BF5F00': 'Dark Orange',
613     '#FF7F00': 'Orange',
614     '#FF9F40': 'Grapefruit',
615     '#FFBF80': 'Canteloupe',
616     '#FFDFBF': 'Wax',
617     '#482C1B': 'Dark Brick',
618     '#855A40': 'Dirt',
619     '#B27C51': 'Tan',
620     '#7F3F00': 'Nutmeg',
621     '#C49B71': 'Mustard',
622     '#E1C4A8': 'Pale Tan',
623     '#FDEEE0': 'Marble'
624 /* }}} */
625         },
626         /** 
627         * @property _colorPicker
628         * @description The HTML Element containing the colorPicker
629         * @type HTMLElement
630         */
631         _colorPicker: null,
632         /** 
633         * @property STR_COLLAPSE
634         * @description String for Toolbar Collapse Button
635         * @type String
636         */
637         STR_COLLAPSE: 'Collapse Toolbar',
638         /** 
639         * @property STR_EXPAND
640         * @description String for Toolbar Collapse Button - Expand
641         * @type String
642         */
643         STR_EXPAND: 'Expand Toolbar',
644         /** 
645         * @property STR_SPIN_LABEL
646         * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
647         * @type String
648         */
649         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.',
650         /** 
651         * @property STR_SPIN_UP
652         * @description String for spinbutton up
653         * @type String
654         */
655         STR_SPIN_UP: 'Click to increase the value of this input',
656         /** 
657         * @property STR_SPIN_DOWN
658         * @description String for spinbutton down
659         * @type String
660         */
661         STR_SPIN_DOWN: 'Click to decrease the value of this input',
662         /** 
663         * @property _titlebar
664         * @description Object reference to the titlebar
665         * @type HTMLElement
666         */
667         _titlebar: null,
668         /** 
669         * @property browser
670         * @description Standard browser detection
671         * @type Object
672         */
673         browser: YAHOO.env.ua,
674         /**
675         * @protected
676         * @property _buttonList
677         * @description Internal property list of current buttons in the toolbar
678         * @type Array
679         */
680         _buttonList: null,
681         /**
682         * @protected
683         * @property _buttonGroupList
684         * @description Internal property list of current button groups in the toolbar
685         * @type Array
686         */
687         _buttonGroupList: null,
688         /**
689         * @protected
690         * @property _sep
691         * @description Internal reference to the separator HTML Element for cloning
692         * @type HTMLElement
693         */
694         _sep: null,
695         /**
696         * @protected
697         * @property _sepCount
698         * @description Internal refernce for counting separators, so we can give them a useful class name for styling
699         * @type Number
700         */
701         _sepCount: null,
702         /**
703         * @protected
704         * @property draghandle
705         * @type HTMLElement
706         */
707         _dragHandle: null,
708         /**
709         * @protected
710         * @property _toolbarConfigs
711         * @type Object
712         */
713         _toolbarConfigs: {
714             renderer: true
715         },
716         /**
717         * @protected
718         * @property CLASS_CONTAINER
719         * @description Default CSS class to apply to the toolbar container element
720         * @type String
721         */
722         CLASS_CONTAINER: 'yui-toolbar-container',
723         /**
724         * @protected
725         * @property CLASS_DRAGHANDLE
726         * @description Default CSS class to apply to the toolbar's drag handle element
727         * @type String
728         */
729         CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
730         /**
731         * @protected
732         * @property CLASS_SEPARATOR
733         * @description Default CSS class to apply to all separators in the toolbar
734         * @type String
735         */
736         CLASS_SEPARATOR: 'yui-toolbar-separator',
737         /**
738         * @protected
739         * @property CLASS_DISABLED
740         * @description Default CSS class to apply when the toolbar is disabled
741         * @type String
742         */
743         CLASS_DISABLED: 'yui-toolbar-disabled',
744         /**
745         * @protected
746         * @property CLASS_PREFIX
747         * @description Default prefix for dynamically created class names
748         * @type String
749         */
750         CLASS_PREFIX: 'yui-toolbar',
751         /** 
752         * @method init
753         * @description The Toolbar class's initialization method
754         */
755         init: function(p_oElement, p_oAttributes) {
756             YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
757         },
758         /**
759         * @method initAttributes
760         * @description Initializes all of the configuration attributes used to create 
761         * the toolbar.
762         * @param {Object} attr Object literal specifying a set of 
763         * configuration attributes used to create the toolbar.
764         */
765         initAttributes: function(attr) {
766             YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
767             this.addClass(this.CLASS_CONTAINER);
768
769             /**
770             * @attribute buttonType
771             * @description The buttonType to use (advanced or basic)
772             * @type String
773             */
774             this.setAttributeConfig('buttonType', {
775                 value: attr.buttonType || 'basic',
776                 writeOnce: true,
777                 validator: function(type) {
778                     switch (type) {
779                         case 'advanced':
780                         case 'basic':
781                             return true;
782                     }
783                     return false;
784                 },
785                 method: function(type) {
786                     if (type == 'advanced') {
787                         if (YAHOO.widget.Button) {
788                             this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
789                         } else {
790                             YAHOO.log('Can not find YAHOO.widget.Button', 'error', 'Toolbar');
791                             this.buttonType = YAHOO.widget.ToolbarButton;
792                         }
793                     } else {
794                         this.buttonType = YAHOO.widget.ToolbarButton;
795                     }
796                 }
797             });
798
799
800             /**
801             * @attribute buttons
802             * @description Object specifying the buttons to include in the toolbar
803             * Example:
804             * <code><pre>
805             * {
806             *   { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
807             *   { type: 'separator' },
808             *   { id: 'b4', type: 'menu', label: 'Align', value: 'align',
809             *       menu: [
810             *           { text: "Left", value: 'alignleft' },
811             *           { text: "Center", value: 'aligncenter' },
812             *           { text: "Right", value: 'alignright' }
813             *       ]
814             *   }
815             * }
816             * </pre></code>
817             * @type Array
818             */
819             
820             this.setAttributeConfig('buttons', {
821                 value: [],
822                 writeOnce: true,
823                 method: function(data) {
824                     var i, button, buttons, len, b;
825                     for (i in data) {
826                         if (Lang.hasOwnProperty(data, i)) {
827                             if (data[i].type == 'separator') {
828                                 this.addSeparator();
829                             } else if (data[i].group !== undefined) {
830                                 buttons = this.addButtonGroup(data[i]);
831                                 if (buttons) {
832                                     len = buttons.length;
833                                     for(b = 0; b < len; b++) {
834                                         if (buttons[b]) {
835                                             this._configuredButtons[this._configuredButtons.length] = buttons[b].id;
836                                         }
837                                     }
838                                 }
839                                 
840                             } else {
841                                 button = this.addButton(data[i]);
842                                 if (button) {
843                                     this._configuredButtons[this._configuredButtons.length] = button.id;
844                                 }
845                             }
846                         }
847                     }
848                 }
849             });
850
851             /**
852             * @attribute disabled
853             * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
854             * @default false
855             * @type Boolean
856             */
857             this.setAttributeConfig('disabled', {
858                 value: false,
859                 method: function(disabled) {
860                     if (this.get('disabled') === disabled) {
861                         return false;
862                     }
863                     if (disabled) {
864                         this.addClass(this.CLASS_DISABLED);
865                         this.set('draggable', false);
866                         this.disableAllButtons();
867                     } else {
868                         this.removeClass(this.CLASS_DISABLED);
869                         if (this._configs.draggable._initialConfig.value) {
870                             //Draggable by default, set it back
871                             this.set('draggable', true);
872                         }
873                         this.resetAllButtons();
874                     }
875                 }
876             });
877
878             /**
879             * @config cont
880             * @description The container for the toolbar.
881             * @type HTMLElement
882             */
883             this.setAttributeConfig('cont', {
884                 value: attr.cont,
885                 readOnly: true
886             });
887
888
889             /**
890             * @attribute grouplabels
891             * @description Boolean indicating if the toolbar should show the group label's text string.
892             * @default true
893             * @type Boolean
894             */
895             this.setAttributeConfig('grouplabels', {
896                 value: ((attr.grouplabels === false) ? false : true),
897                 method: function(grouplabels) {
898                     if (grouplabels) {
899                         Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
900                     } else {
901                         Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
902                     }
903                 }
904             });
905             /**
906             * @attribute titlebar
907             * @description Boolean indicating if the toolbar should have a titlebar. If
908             * passed a string, it will use that as the titlebar text
909             * @default false
910             * @type Boolean or String
911             */
912             this.setAttributeConfig('titlebar', {
913                 value: false,
914                 method: function(titlebar) {
915                     if (titlebar) {
916                         if (this._titlebar && this._titlebar.parentNode) {
917                             this._titlebar.parentNode.removeChild(this._titlebar);
918                         }
919                         this._titlebar = document.createElement('DIV');
920                         this._titlebar.tabIndex = '-1';
921                         Event.on(this._titlebar, 'focus', function() {
922                             this._handleFocus();
923                         }, this, true);
924                         Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
925                         if (Lang.isString(titlebar)) {
926                             var h2 = document.createElement('h2');
927                             h2.tabIndex = '-1';
928                             h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
929                             this._titlebar.appendChild(h2);
930                             Event.on(h2.firstChild, 'click', function(ev) {
931                                 Event.stopEvent(ev);
932                             });
933                             Event.on([h2, h2.firstChild], 'focus', function() {
934                                 this._handleFocus();
935                             }, this, true);
936                         }
937                         if (this.get('firstChild')) {
938                             this.insertBefore(this._titlebar, this.get('firstChild'));
939                         } else {
940                             this.appendChild(this._titlebar);
941                         }
942                         if (this.get('collapse')) {
943                             this.set('collapse', true);
944                         }
945                     } else if (this._titlebar) {
946                         if (this._titlebar && this._titlebar.parentNode) {
947                             this._titlebar.parentNode.removeChild(this._titlebar);
948                         }
949                     }
950                 }
951             });
952
953
954             /**
955             * @attribute collapse
956             * @description Boolean indicating if the the titlebar should have a collapse button.
957             * The collapse button will not remove the toolbar, it will minimize it to the titlebar
958             * @default false
959             * @type Boolean
960             */
961             this.setAttributeConfig('collapse', {
962                 value: false,
963                 method: function(collapse) {
964                     if (this._titlebar) {
965                         var collapseEl = null;
966                         var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
967                         if (collapse) {
968                             if (el.length > 0) {
969                                 //There is already a collapse button
970                                 return true;
971                             }
972                             collapseEl = document.createElement('SPAN');
973                             collapseEl.innerHTML = 'X';
974                             collapseEl.title = this.STR_COLLAPSE;
975
976                             Dom.addClass(collapseEl, 'collapse');
977                             this._titlebar.appendChild(collapseEl);
978                             Event.addListener(collapseEl, 'click', function() {
979                                 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
980                                     this.collapse(false); //Expand Toolbar
981                                 } else {
982                                     this.collapse(); //Collapse Toolbar
983                                 }
984                             }, this, true);
985                         } else {
986                             collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
987                             if (collapseEl[0]) {
988                                 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
989                                     //We are closed, reopen the titlebar..
990                                     this.collapse(false); //Expand Toolbar
991                                 }
992                                 collapseEl[0].parentNode.removeChild(collapseEl[0]);
993                             }
994                         }
995                     }
996                 }
997             });
998
999             /**
1000             * @attribute draggable
1001             * @description Boolean indicating if the toolbar should be draggable.  
1002             * @default false
1003             * @type Boolean
1004             */
1005
1006             this.setAttributeConfig('draggable', {
1007                 value: (attr.draggable || false),
1008                 method: function(draggable) {
1009                     if (draggable && !this.get('titlebar')) {
1010                         YAHOO.log('Dragging enabled', 'info', 'Toolbar');
1011                         if (!this._dragHandle) {
1012                             this._dragHandle = document.createElement('SPAN');
1013                             this._dragHandle.innerHTML = '|';
1014                             this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
1015                             this._dragHandle.id = this.get('id') + '_draghandle';
1016                             Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
1017                             if (this.get('cont').hasChildNodes()) {
1018                                 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
1019                             } else {
1020                                 this.get('cont').appendChild(this._dragHandle);
1021                             }
1022                             this.dd = new YAHOO.util.DD(this.get('id'));
1023                             this.dd.setHandleElId(this._dragHandle.id);
1024                             
1025                         }
1026                     } else {
1027                         YAHOO.log('Dragging disabled', 'info', 'Toolbar');
1028                         if (this._dragHandle) {
1029                             this._dragHandle.parentNode.removeChild(this._dragHandle);
1030                             this._dragHandle = null;
1031                             this.dd = null;
1032                         }
1033                     }
1034                     if (this._titlebar) {
1035                         if (draggable) {
1036                             this.dd = new YAHOO.util.DD(this.get('id'));
1037                             this.dd.setHandleElId(this._titlebar);
1038                             Dom.addClass(this._titlebar, 'draggable');
1039                         } else {
1040                             Dom.removeClass(this._titlebar, 'draggable');
1041                             if (this.dd) {
1042                                 this.dd.unreg();
1043                                 this.dd = null;
1044                             }
1045                         }
1046                     }
1047                 },
1048                 validator: function(value) {
1049                     var ret = true;
1050                     if (!YAHOO.util.DD) {
1051                         ret = false;
1052                     }
1053                     return ret;
1054                 }
1055             });
1056
1057         },
1058         /**
1059         * @method addButtonGroup
1060         * @description Add a new button group to the toolbar. (uses addButton)
1061         * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1062         */
1063         addButtonGroup: function(oGroup) {
1064             if (!this.get('element')) {
1065                 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1066                 return false;
1067             }
1068             
1069             if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1070                 this.addClass(this.CLASS_PREFIX + '-grouped');
1071             }
1072             var div = document.createElement('DIV');
1073             Dom.addClass(div, this.CLASS_PREFIX + '-group');
1074             Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1075             if (oGroup.label) {
1076                 var label = document.createElement('h3');
1077                 label.innerHTML = oGroup.label;
1078                 div.appendChild(label);
1079             }
1080             if (!this.get('grouplabels')) {
1081                 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1082             }
1083
1084             this.get('cont').appendChild(div);
1085
1086             //For accessibility, let's put all of the group buttons in an Unordered List
1087             var ul = document.createElement('ul');
1088             div.appendChild(ul);
1089
1090             if (!this._buttonGroupList) {
1091                 this._buttonGroupList = {};
1092             }
1093             
1094             this._buttonGroupList[oGroup.group] = ul;
1095
1096             //An array of the button ids added to this group
1097             //This is used for destruction later...
1098             var addedButtons = [],
1099                 button;
1100             
1101
1102             for (var i = 0; i < oGroup.buttons.length; i++) {
1103                 var li = document.createElement('li');
1104                 li.className = this.CLASS_PREFIX + '-groupitem';
1105                 ul.appendChild(li);
1106                 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1107                     this.addSeparator(li);
1108                 } else {
1109                     oGroup.buttons[i].container = li;
1110                     button = this.addButton(oGroup.buttons[i]);
1111                     if (button) {
1112                         addedButtons[addedButtons.length]  = button.id;
1113                     }
1114                 }
1115             }
1116             return addedButtons;
1117         },
1118         /**
1119         * @method addButtonToGroup
1120         * @description Add a new button to a toolbar group. Buttons supported:
1121         *   push, split, menu, select, color, spin
1122         * @param {Object} oButton Object literal reference to the Button's Config
1123         * @param {String} group The Group identifier passed into the initial config
1124         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1125         */
1126         addButtonToGroup: function(oButton, group, after) {
1127             var groupCont = this._buttonGroupList[group],
1128                 li = document.createElement('li');
1129
1130             li.className = this.CLASS_PREFIX + '-groupitem';
1131             oButton.container = li;
1132             this.addButton(oButton, after);
1133             groupCont.appendChild(li);
1134         },
1135         /**
1136         * @method addButton
1137         * @description Add a new button to the toolbar. Buttons supported:
1138         *   push, split, menu, select, color, spin
1139         * @param {Object} oButton Object literal reference to the Button's Config
1140         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1141         */
1142         addButton: function(oButton, after) {
1143             if (!this.get('element')) {
1144                 this._queue[this._queue.length] = ['addButton', arguments];
1145                 return false;
1146             }
1147             if (!this._buttonList) {
1148                 this._buttonList = [];
1149             }
1150             YAHOO.log('Adding button of type: ' + oButton.type, 'info', 'Toolbar');
1151             if (!oButton.container) {
1152                 oButton.container = this.get('cont');
1153             }
1154
1155             if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1156                 if (Lang.isArray(oButton.menu)) {
1157                     for (var i in oButton.menu) {
1158                         if (Lang.hasOwnProperty(oButton.menu, i)) {
1159                             var funcObject = {
1160                                 fn: function(ev, x, oMenu) {
1161                                     if (!oButton.menucmd) {
1162                                         oButton.menucmd = oButton.value;
1163                                     }
1164                                     oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1165                                 },
1166                                 scope: this
1167                             };
1168                             oButton.menu[i].onclick = funcObject;
1169                         }
1170                     }
1171                 }
1172             }
1173             var _oButton = {}, skip = false;
1174             for (var o in oButton) {
1175                 if (Lang.hasOwnProperty(oButton, o)) {
1176                     if (!this._toolbarConfigs[o]) {
1177                         _oButton[o] = oButton[o];
1178                     }
1179                 }
1180             }
1181             if (oButton.type == 'select') {
1182                 _oButton.type = 'menu';
1183             }
1184             if (oButton.type == 'spin') {
1185                 _oButton.type = 'push';
1186             }
1187             if (_oButton.type == 'color') {
1188                 if (YAHOO.widget.Overlay) {
1189                     _oButton = this._makeColorButton(_oButton);
1190                 } else {
1191                     skip = true;
1192                 }
1193             }
1194             if (_oButton.menu) {
1195                 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1196                     oButton.menu.showEvent.subscribe(function() {
1197                         this._button = _oButton;
1198                     });
1199                 } else {
1200                     for (var m = 0; m < _oButton.menu.length; m++) {
1201                         if (!_oButton.menu[m].value) {
1202                             _oButton.menu[m].value = _oButton.menu[m].text;
1203                         }
1204                     }
1205                     if (this.browser.webkit) {
1206                         _oButton.focusmenu = false;
1207                     }
1208                 }
1209             }
1210             if (skip) {
1211                 oButton = false;
1212             } else {
1213                 //Add to .get('buttons') manually
1214                 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1215                 
1216                 var tmp = new this.buttonType(_oButton);
1217                 tmp.get('element').tabIndex = '-1';
1218                 tmp.get('element').setAttribute('role', 'button');
1219                 tmp._selected = true;
1220                 
1221                 if (this.get('disabled')) {
1222                     //Toolbar is disabled, disable the new button too!
1223                     tmp.set('disabled', true);
1224                 }
1225                 if (!oButton.id) {
1226                     oButton.id = tmp.get('id');
1227                 }
1228                 YAHOO.log('Button created (' + oButton.type + ')', 'info', 'Toolbar');
1229                 
1230                 if (after) {
1231                     var el = tmp.get('element');
1232                     var nextSib = null;
1233                     if (after.get) {
1234                         nextSib = after.get('element').nextSibling;
1235                     } else if (after.nextSibling) {
1236                         nextSib = after.nextSibling;
1237                     }
1238                     if (nextSib) {
1239                         nextSib.parentNode.insertBefore(el, nextSib);
1240                     }
1241                 }
1242                 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1243
1244                 var icon = document.createElement('span');
1245                 icon.className = this.CLASS_PREFIX + '-icon';
1246                 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1247                 if (tmp._button.tagName.toLowerCase() == 'button') {
1248                     tmp.get('element').setAttribute('unselectable', 'on');
1249                     //Replace the Button HTML Element with an a href if it exists
1250                     var a = document.createElement('a');
1251                     a.innerHTML = tmp._button.innerHTML;
1252                     a.href = '#';
1253                     a.tabIndex = '-1';
1254                     Event.on(a, 'click', function(ev) {
1255                         Event.stopEvent(ev);
1256                     });
1257                     tmp._button.parentNode.replaceChild(a, tmp._button);
1258                     tmp._button = a;
1259                 }
1260
1261                 if (oButton.type == 'select') {
1262                     if (tmp._button.tagName.toLowerCase() == 'select') {
1263                         icon.parentNode.removeChild(icon);
1264                         var iel = tmp._button,
1265                             parEl = tmp.get('element');
1266                         parEl.parentNode.replaceChild(iel, parEl);
1267                         //The 'element' value is currently the orphaned element
1268                         //In order for "destroy" to execute we need to get('element') to reference the correct node.
1269                         //I'm not sure if there is a direct approach to setting this value.
1270                         tmp._configs.element.value = iel;
1271                     } else {
1272                         //Don't put a class on it if it's a real select element
1273                         tmp.addClass(this.CLASS_PREFIX + '-select');
1274                     }
1275                 }
1276                 if (oButton.type == 'spin') {
1277                     if (!Lang.isArray(oButton.range)) {
1278                         oButton.range = [ 10, 100 ];
1279                     }
1280                     this._makeSpinButton(tmp, oButton);
1281                 }
1282                 tmp.get('element').setAttribute('title', tmp.get('label'));
1283                 if (oButton.type != 'spin') {
1284                     if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1285                         var showPicker = function(ev) {
1286                             var exec = true;
1287                             if (ev.keyCode && (ev.keyCode == 9)) {
1288                                 exec = false;
1289                             }
1290                             if (exec) {
1291                                 if (this._colorPicker) {
1292                                     this._colorPicker._button = oButton.value;
1293                                 }
1294                                 var menuEL = tmp.getMenu().element;
1295                                 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1296                                     tmp.getMenu().show();
1297                                 } else {
1298                                     tmp.getMenu().hide();
1299                                 }
1300                             }
1301                             YAHOO.util.Event.stopEvent(ev);
1302                         };
1303                         tmp.on('mousedown', showPicker, oButton, this);
1304                         tmp.on('keydown', showPicker, oButton, this);
1305                         
1306                     } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1307                         tmp.on('keypress', this._buttonClick, oButton, this);
1308                         tmp.on('mousedown', function(ev) {
1309                             YAHOO.util.Event.stopEvent(ev);
1310                             this._buttonClick(ev, oButton);
1311                         }, oButton, this);
1312                         tmp.on('click', function(ev) {
1313                             YAHOO.util.Event.stopEvent(ev);
1314                         });
1315                     } else {
1316                         //Stop the mousedown event so we can trap the selection in the editor!
1317                         tmp.on('mousedown', function(ev) {
1318                             YAHOO.util.Event.stopEvent(ev);
1319                         });
1320                         tmp.on('click', function(ev) {
1321                             YAHOO.util.Event.stopEvent(ev);
1322                         });
1323                         tmp.on('change', function(ev) {
1324                             if (!ev.target) {
1325                                 if (!oButton.menucmd) {
1326                                     oButton.menucmd = oButton.value;
1327                                 }
1328                                 oButton.value = ev.value;
1329                                 this._buttonClick(ev, oButton);
1330                             }
1331                         }, this, true);
1332
1333                         var self = this;
1334                         //Hijack the mousedown event in the menu and make it fire a button click..
1335                         tmp.on('appendTo', function() {
1336                             var tmp = this;
1337                             if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1338                                 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1339                                     YAHOO.log('mouseDownEvent', 'warn', 'Toolbar');
1340                                     var oMenu = args[1];
1341                                     YAHOO.util.Event.stopEvent(args[0]);
1342                                     tmp._onMenuClick(args[0], tmp);
1343                                     if (!oButton.menucmd) {
1344                                         oButton.menucmd = oButton.value;
1345                                     }
1346                                     oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1347                                     self._buttonClick.call(self, args[1], oButton);
1348                                     tmp._hideMenu();
1349                                     return false;
1350                                 });
1351                                 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1352                                     YAHOO.log('clickEvent', 'warn', 'Toolbar');
1353                                     YAHOO.util.Event.stopEvent(args[0]);
1354                                 });
1355                                 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1356                                     YAHOO.log('mouseUpEvent', 'warn', 'Toolbar');
1357                                     YAHOO.util.Event.stopEvent(args[0]);
1358                                 });
1359                             }
1360                         });
1361                         
1362                     }
1363                 } else {
1364                     //Stop the mousedown event so we can trap the selection in the editor!
1365                     tmp.on('mousedown', function(ev) {
1366                         YAHOO.util.Event.stopEvent(ev);
1367                     });
1368                     tmp.on('click', function(ev) {
1369                         YAHOO.util.Event.stopEvent(ev);
1370                     });
1371                 }
1372                 if (this.browser.ie) {
1373                     /*
1374                     //Add a couple of new events for IE
1375                     tmp.DOM_EVENTS.focusin = true;
1376                     tmp.DOM_EVENTS.focusout = true;
1377                     
1378                     //Stop them so we don't loose focus in the Editor
1379                     tmp.on('focusin', function(ev) {
1380                         YAHOO.util.Event.stopEvent(ev);
1381                     }, oButton, this);
1382                     
1383                     tmp.on('focusout', function(ev) {
1384                         YAHOO.util.Event.stopEvent(ev);
1385                     }, oButton, this);
1386                     tmp.on('click', function(ev) {
1387                         YAHOO.util.Event.stopEvent(ev);
1388                     }, oButton, this);
1389                     */
1390                 }
1391                 if (this.browser.webkit) {
1392                     //This will keep the document from gaining focus and the editor from loosing it..
1393                     //Forcefully remove the focus calls in button!
1394                     tmp.hasFocus = function() {
1395                         return true;
1396                     };
1397                 }
1398                 this._buttonList[this._buttonList.length] = tmp;
1399                 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1400                     if (Lang.isArray(oButton.menu)) {
1401                         YAHOO.log('Button type is (' + oButton.type + '), doing extra renderer work.', 'info', 'Toolbar');
1402                         var menu = tmp.getMenu();
1403                         if (menu && menu.renderEvent) {
1404                             menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1405                             if (oButton.renderer) {
1406                                 menu.renderEvent.subscribe(oButton.renderer, tmp);
1407                             }
1408                         }
1409                     }
1410                 }
1411             }
1412             return oButton;
1413         },
1414         /**
1415         * @method addSeparator
1416         * @description Add a new button separator to the toolbar.
1417         * @param {HTMLElement} cont Optional HTML element to insert this button into.
1418         * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1419         */
1420         addSeparator: function(cont, after) {
1421             if (!this.get('element')) {
1422                 this._queue[this._queue.length] = ['addSeparator', arguments];
1423                 return false;
1424             }
1425             var sepCont = ((cont) ? cont : this.get('cont'));
1426             if (!this.get('element')) {
1427                 this._queue[this._queue.length] = ['addSeparator', arguments];
1428                 return false;
1429             }
1430             if (this._sepCount === null) {
1431                 this._sepCount = 0;
1432             }
1433             if (!this._sep) {
1434                 YAHOO.log('Separator does not yet exist, creating', 'info', 'Toolbar');
1435                 this._sep = document.createElement('SPAN');
1436                 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1437                 this._sep.innerHTML = '|';
1438             }
1439             YAHOO.log('Separator does exist, cloning', 'info', 'Toolbar');
1440             var _sep = this._sep.cloneNode(true);
1441             this._sepCount++;
1442             Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1443             if (after) {
1444                 var nextSib = null;
1445                 if (after.get) {
1446                     nextSib = after.get('element').nextSibling;
1447                 } else if (after.nextSibling) {
1448                     nextSib = after.nextSibling;
1449                 } else {
1450                     nextSib = after;
1451                 }
1452                 if (nextSib) {
1453                     if (nextSib == after) {
1454                         nextSib.parentNode.appendChild(_sep);
1455                     } else {
1456                         nextSib.parentNode.insertBefore(_sep, nextSib);
1457                     }
1458                 }
1459             } else {
1460                 sepCont.appendChild(_sep);
1461             }
1462             return _sep;
1463         },
1464         /**
1465         * @method _createColorPicker
1466         * @private
1467         * @description Creates the core DOM reference to the color picker menu item.
1468         * @param {String} id the id of the toolbar to prefix this DOM container with.
1469         */
1470         _createColorPicker: function(id) {
1471             if (Dom.get(id + '_colors')) {
1472                Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1473             }
1474             var picker = document.createElement('div');
1475             picker.className = 'yui-toolbar-colors';
1476             picker.id = id + '_colors';
1477             picker.style.display = 'none';
1478             Event.on(window, 'load', function() {
1479                 document.body.appendChild(picker);
1480             }, this, true);
1481
1482             this._colorPicker = picker;
1483
1484             var html = '';
1485             for (var i in this._colorData) {
1486                 if (Lang.hasOwnProperty(this._colorData, i)) {
1487                     html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1488                 }
1489             }
1490             html += '<span><em>X</em><strong></strong></span>';
1491             window.setTimeout(function() {
1492                 picker.innerHTML = html;
1493             }, 0);
1494
1495             Event.on(picker, 'mouseover', function(ev) {
1496                 var picker = this._colorPicker;
1497                 var em = picker.getElementsByTagName('em')[0];
1498                 var strong = picker.getElementsByTagName('strong')[0];
1499                 var tar = Event.getTarget(ev);
1500                 if (tar.tagName.toLowerCase() == 'a') {
1501                     em.style.backgroundColor = tar.style.backgroundColor;
1502                     strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1503                 }
1504             }, this, true);
1505             Event.on(picker, 'focus', function(ev) {
1506                 Event.stopEvent(ev);
1507             });
1508             Event.on(picker, 'click', function(ev) {
1509                 Event.stopEvent(ev);
1510             });
1511             Event.on(picker, 'mousedown', function(ev) {
1512                 Event.stopEvent(ev);
1513                 var tar = Event.getTarget(ev);
1514                 if (tar.tagName.toLowerCase() == 'a') {
1515                     var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1516                     if (retVal !== false) {
1517                         var info = {
1518                             color: tar.innerHTML,
1519                             colorName: this._colorData['#' + tar.innerHTML],
1520                             value: this._colorPicker._button 
1521                         };
1522                     
1523                         this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1524                     }
1525                     this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1526                 }
1527             }, this, true);
1528         },
1529         /**
1530         * @method _resetColorPicker
1531         * @private
1532         * @description Clears the currently selected color or mouseover color in the color picker.
1533         */
1534         _resetColorPicker: function() {
1535             var em = this._colorPicker.getElementsByTagName('em')[0];
1536             var strong = this._colorPicker.getElementsByTagName('strong')[0];
1537             em.style.backgroundColor = 'transparent';
1538             strong.innerHTML = '';
1539         },
1540         /**
1541         * @method _makeColorButton
1542         * @private
1543         * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1544         * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1545         */
1546         _makeColorButton: function(_oButton) {
1547             if (!this._colorPicker) {   
1548                 this._createColorPicker(this.get('id'));
1549             }
1550             _oButton.type = 'color';
1551             _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1552             _oButton.menu.setBody('');
1553             _oButton.menu.render(this.get('cont'));
1554             Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1555             Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1556             _oButton.menu.beforeShowEvent.subscribe(function() {
1557                 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1558                 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1559                 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1560                 this._resetColorPicker();
1561                 var _p = this._colorPicker;
1562                 if (_p.parentNode) {
1563                     _p.parentNode.removeChild(_p);
1564                 }
1565                 _oButton.menu.setBody('');
1566                 _oButton.menu.appendToBody(_p);
1567                 this._colorPicker.style.display = 'block';
1568             }, this, true);
1569             return _oButton;
1570         },
1571         /**
1572         * @private
1573         * @method _makeSpinButton
1574         * @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.
1575         * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1576         * @param {Object} oButton Object literal containing the buttons initial config
1577         */
1578         _makeSpinButton: function(_button, oButton) {
1579             _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1580             var self = this,
1581                 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1582                 range = oButton.range,
1583                 _b1 = document.createElement('a'),
1584                 _b2 = document.createElement('a');
1585                 _b1.href = '#';
1586                 _b2.href = '#';
1587                 _b1.tabIndex = '-1';
1588                 _b2.tabIndex = '-1';
1589             
1590             //Setup the up and down arrows
1591             _b1.className = 'up';
1592             _b1.title = this.STR_SPIN_UP;
1593             _b1.innerHTML = this.STR_SPIN_UP;
1594             _b2.className = 'down';
1595             _b2.title = this.STR_SPIN_DOWN;
1596             _b2.innerHTML = this.STR_SPIN_DOWN;
1597
1598             //Append them to the container
1599             _par.appendChild(_b1);
1600             _par.appendChild(_b2);
1601             
1602             var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1603             _button.set('title', label);
1604
1605             var cleanVal = function(value) {
1606                 value = ((value < range[0]) ? range[0] : value);
1607                 value = ((value > range[1]) ? range[1] : value);
1608                 return value;
1609             };
1610
1611             var br = this.browser;
1612             var tbar = false;
1613             var strLabel = this.STR_SPIN_LABEL;
1614             if (this._titlebar && this._titlebar.firstChild) {
1615                 tbar = this._titlebar.firstChild;
1616             }
1617             
1618             var _intUp = 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                     _button.set('label', ''+value);
1625                     var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1626                     _button.set('title', label);
1627                     if (!br.webkit && tbar) {
1628                         //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
1629                         //_button.focus();
1630                     }
1631                     self._buttonClick(ev, oButton);
1632                 }
1633             };
1634
1635             var _intDown = function(ev) {
1636                 YAHOO.util.Event.stopEvent(ev);
1637                 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1638                     var value = parseInt(_button.get('label'), 10);
1639                     value--;
1640                     value = cleanVal(value);
1641
1642                     _button.set('label', ''+value);
1643                     var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1644                     _button.set('title', label);
1645                     if (!br.webkit && tbar) {
1646                         //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
1647                         //_button.focus();
1648                     }
1649                     self._buttonClick(ev, oButton);
1650                 }
1651             };
1652
1653             var _intKeyUp = function(ev) {
1654                 if (ev.keyCode == 38) {
1655                     _intUp(ev);
1656                 } else if (ev.keyCode == 40) {
1657                     _intDown(ev);
1658                 } else if (ev.keyCode == 107 && ev.shiftKey) {  //Plus Key
1659                     _intUp(ev);
1660                 } else if (ev.keyCode == 109 && ev.shiftKey) {  //Minus Key
1661                     _intDown(ev);
1662                 }
1663             };
1664
1665             //Handle arrow keys..
1666             _button.on('keydown', _intKeyUp, this, true);
1667
1668             //Listen for the click on the up button and act on it
1669             //Listen for the click on the down button and act on it
1670             Event.on(_b1, 'mousedown',function(ev) {
1671                 Event.stopEvent(ev);
1672             }, this, true);
1673             Event.on(_b2, 'mousedown', function(ev) {
1674                 Event.stopEvent(ev);
1675             }, this, true);
1676             Event.on(_b1, 'click', _intUp, this, true);
1677             Event.on(_b2, 'click', _intDown, this, true);
1678         },
1679         /**
1680         * @protected
1681         * @method _buttonClick
1682         * @description Click handler for all buttons in the toolbar.
1683         * @param {String} ev The event that was passed in.
1684         * @param {Object} info Object literal of information about the button that was clicked.
1685         */
1686         _buttonClick: function(ev, info) {
1687             var doEvent = true;
1688             
1689             if (ev && ev.type == 'keypress') {
1690                 if (ev.keyCode == 9) {
1691                     doEvent = false;
1692                 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1693                 } else {
1694                     doEvent = false;
1695                 }
1696             }
1697
1698             if (doEvent) {
1699                 var fireNextEvent = true,
1700                     retValue = false;
1701                     
1702                 info.isSelected = this.isSelected(info.id);
1703
1704                 if (info.value) {
1705                     YAHOO.log('fireEvent::' + info.value + 'Click', 'info', 'Toolbar');
1706                     retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1707                     if (retValue === false) {
1708                         fireNextEvent = false;
1709                     }
1710                 }
1711                 
1712                 if (info.menucmd && fireNextEvent) {
1713                     YAHOO.log('fireEvent::' + info.menucmd + 'Click', 'info', 'Toolbar');
1714                     retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1715                     if (retValue === false) {
1716                         fireNextEvent = false;
1717                     }
1718                 }
1719                 if (fireNextEvent) {
1720                     YAHOO.log('fireEvent::buttonClick', 'info', 'Toolbar');
1721                     this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1722                 }
1723
1724                 if (info.type == 'select') {
1725                     var button = this.getButtonById(info.id);
1726                     if (button.buttonType == 'rich') {
1727                         var txt = info.value;
1728                         for (var i = 0; i < info.menu.length; i++) {
1729                             if (info.menu[i].value == info.value) {
1730                                 txt = info.menu[i].text;
1731                                 break;
1732                             }
1733                         }
1734                         button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1735                         var _items = button.getMenu().getItems();
1736                         for (var m = 0; m < _items.length; m++) {
1737                             if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1738                                 _items[m].cfg.setProperty('checked', true);
1739                             } else {
1740                                 _items[m].cfg.setProperty('checked', false);
1741                             }
1742                         }
1743                     }
1744                 }
1745                 if (ev) {
1746                     Event.stopEvent(ev);
1747                 }
1748             }
1749         },
1750         /**
1751         * @private
1752         * @property _keyNav
1753         * @description Flag to determine if the arrow nav listeners have been attached
1754         * @type Boolean
1755         */
1756         _keyNav: null,
1757         /**
1758         * @private
1759         * @property _navCounter
1760         * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1761         * @type Number
1762         */
1763         _navCounter: null,
1764         /**
1765         * @private
1766         * @method _navigateButtons
1767         * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1768         * @param {Event} ev The Key Event
1769         */
1770         _navigateButtons: function(ev) {
1771             switch (ev.keyCode) {
1772                 case 37:
1773                 case 39:
1774                     if (ev.keyCode == 37) {
1775                         this._navCounter--;
1776                     } else {
1777                         this._navCounter++;
1778                     }
1779                     if (this._navCounter > (this._buttonList.length - 1)) {
1780                         this._navCounter = 0;
1781                     }
1782                     if (this._navCounter < 0) {
1783                         this._navCounter = (this._buttonList.length - 1);
1784                     }
1785                     if (this._buttonList[this._navCounter]) {
1786                         var el = this._buttonList[this._navCounter].get('element');
1787                         if (this.browser.ie) {
1788                             el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1789                         }
1790                         if (this._buttonList[this._navCounter].get('disabled')) {
1791                             this._navigateButtons(ev);
1792                         } else {
1793                             el.focus();
1794                         }
1795                     }
1796                     break;
1797             }
1798         },
1799         /**
1800         * @private
1801         * @method _handleFocus
1802         * @description Sets up the listeners for the arrow key navigation
1803         */
1804         _handleFocus: function() {
1805             if (!this._keyNav) {
1806                 var ev = 'keypress';
1807                 if (this.browser.ie) {
1808                     ev = 'keydown';
1809                 }
1810                 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1811                 this._keyNav = true;
1812                 this._navCounter = -1;
1813             }
1814         },
1815         /**
1816         * @method getButtonById
1817         * @description Gets a button instance from the toolbar by is Dom id.
1818         * @param {String} id The Dom id to query for.
1819         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1820         */
1821         getButtonById: function(id) {
1822             var len = this._buttonList.length;
1823             for (var i = 0; i < len; i++) {
1824                 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1825                     return this._buttonList[i];
1826                 }
1827             }
1828             return false;
1829         },
1830         /**
1831         * @method getButtonByValue
1832         * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1833         * @param {String} value The button value to query for.
1834         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1835         */
1836         getButtonByValue: function(value) {
1837             var _buttons = this.get('buttons');
1838             if (!_buttons) {
1839                 return false;
1840             }
1841             var len = _buttons.length;
1842             for (var i = 0; i < len; i++) {
1843                 if (_buttons[i].group !== undefined) {
1844                     for (var m = 0; m < _buttons[i].buttons.length; m++) {
1845                         if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1846                             return this.getButtonById(_buttons[i].buttons[m].id);
1847                         }
1848                         if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1849                             for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1850                                 if (_buttons[i].buttons[m].menu[s].value == value) {
1851                                     return this.getButtonById(_buttons[i].buttons[m].id);
1852                                 }
1853                             }
1854                         }
1855                     }
1856                 } else {
1857                     if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1858                         return this.getButtonById(_buttons[i].id);
1859                     }
1860                     if (_buttons[i].menu) { //Menu Button, loop through the values
1861                         for (var j = 0; j < _buttons[i].menu.length; j++) {
1862                             if (_buttons[i].menu[j].value == value) {
1863                                 return this.getButtonById(_buttons[i].id);
1864                             }
1865                         }
1866                     }
1867                 }
1868             }
1869             return false;
1870         },
1871         /**
1872         * @method getButtonByIndex
1873         * @description Gets a button instance from the toolbar by is index in _buttonList.
1874         * @param {Number} index The index of the button in _buttonList.
1875         * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1876         */
1877         getButtonByIndex: function(index) {
1878             if (this._buttonList[index]) {
1879                 return this._buttonList[index];
1880             } else {
1881                 return false;
1882             }
1883         },
1884         /**
1885         * @method getButtons
1886         * @description Returns an array of buttons in the current toolbar
1887         * @return {Array}
1888         */
1889         getButtons: function() {
1890             return this._buttonList;
1891         },
1892         /**
1893         * @method disableButton
1894         * @description Disables a button in the toolbar.
1895         * @param {String/Number} id Disable a button by it's id, index or value.
1896         * @return {Boolean}
1897         */
1898         disableButton: function(id) {
1899             var button = getButton.call(this, id);
1900             if (button) {
1901                 button.set('disabled', true);
1902             } else {
1903                 return false;
1904             }
1905         },
1906         /**
1907         * @method enableButton
1908         * @description Enables a button in the toolbar.
1909         * @param {String/Number} id Enable a button by it's id, index or value.
1910         * @return {Boolean}
1911         */
1912         enableButton: function(id) {
1913             if (this.get('disabled')) {
1914                 return false;
1915             }
1916             var button = getButton.call(this, id);
1917             if (button) {
1918                 if (button.get('disabled')) {
1919                     button.set('disabled', false);
1920                 }
1921             } else {
1922                 return false;
1923             }
1924         },
1925         /**
1926         * @method isSelected
1927         * @description Tells if a button is selected or not.
1928         * @param {String/Number} id A button by it's id, index or value.
1929         * @return {Boolean}
1930         */
1931         isSelected: function(id) {
1932             var button = getButton.call(this, id);
1933             if (button) {
1934                 return button._selected;
1935             }
1936             return false;
1937         },
1938         /**
1939         * @method selectButton
1940         * @description Selects a button in the toolbar.
1941         * @param {String/Number} id Select a button by it's id, index or value.
1942         * @param {String} value If this is a Menu Button, check this item in the menu
1943         * @return {Boolean}
1944         */
1945         selectButton: function(id, value) {
1946             var button = getButton.call(this, id);
1947             if (button) {
1948                 button.addClass('yui-button-selected');
1949                 button.addClass('yui-button-' + button.get('value') + '-selected');
1950                 button._selected = true;
1951                 if (value) {
1952                     if (button.buttonType == 'rich') {
1953                         var _items = button.getMenu().getItems();
1954                         for (var m = 0; m < _items.length; m++) {
1955                             if (_items[m].value == value) {
1956                                 _items[m].cfg.setProperty('checked', true);
1957                                 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1958                             } else {
1959                                 _items[m].cfg.setProperty('checked', false);
1960                             }
1961                         }
1962                     }
1963                 }
1964             } else {
1965                 return false;
1966             }
1967         },
1968         /**
1969         * @method deselectButton
1970         * @description Deselects a button in the toolbar.
1971         * @param {String/Number} id Deselect a button by it's id, index or value.
1972         * @return {Boolean}
1973         */
1974         deselectButton: function(id) {
1975             var button = getButton.call(this, id);
1976             if (button) {
1977                 button.removeClass('yui-button-selected');
1978                 button.removeClass('yui-button-' + button.get('value') + '-selected');
1979                 button.removeClass('yui-button-hover');
1980                 button._selected = false;
1981             } else {
1982                 return false;
1983             }
1984         },
1985         /**
1986         * @method deselectAllButtons
1987         * @description Deselects all buttons in the toolbar.
1988         * @return {Boolean}
1989         */
1990         deselectAllButtons: function() {
1991             var len = this._buttonList.length;
1992             for (var i = 0; i < len; i++) {
1993                 this.deselectButton(this._buttonList[i]);
1994             }
1995         },
1996         /**
1997         * @method disableAllButtons
1998         * @description Disables all buttons in the toolbar.
1999         * @return {Boolean}
2000         */
2001         disableAllButtons: function() {
2002             if (this.get('disabled')) {
2003                 return false;
2004             }
2005             var len = this._buttonList.length;
2006             for (var i = 0; i < len; i++) {
2007                 this.disableButton(this._buttonList[i]);
2008             }
2009         },
2010         /**
2011         * @method enableAllButtons
2012         * @description Enables all buttons in the toolbar.
2013         * @return {Boolean}
2014         */
2015         enableAllButtons: function() {
2016             if (this.get('disabled')) {
2017                 return false;
2018             }
2019             var len = this._buttonList.length;
2020             for (var i = 0; i < len; i++) {
2021                 this.enableButton(this._buttonList[i]);
2022             }
2023         },
2024         /**
2025         * @method resetAllButtons
2026         * @description Resets all buttons to their initial state.
2027         * @param {Object} _ex Except these buttons
2028         * @return {Boolean}
2029         */
2030         resetAllButtons: function(_ex) {
2031             if (!Lang.isObject(_ex)) {
2032                 _ex = {};
2033             }
2034             if (this.get('disabled') || !this._buttonList) {
2035                 return false;
2036             }
2037             var len = this._buttonList.length;
2038             for (var i = 0; i < len; i++) {
2039                 var _button = this._buttonList[i];
2040                 if (_button) {
2041                     var disabled = _button._configs.disabled._initialConfig.value;
2042                     if (_ex[_button.get('id')]) {
2043                         this.enableButton(_button);
2044                         this.selectButton(_button);
2045                     } else {
2046                         if (disabled) {
2047                             this.disableButton(_button);
2048                         } else {
2049                             this.enableButton(_button);
2050                         }
2051                         this.deselectButton(_button);
2052                     }
2053                 }
2054             }
2055         },
2056         /**
2057         * @method destroyButton
2058         * @description Destroy a button in the toolbar.
2059         * @param {String/Number} id Destroy a button by it's id or index.
2060         * @return {Boolean}
2061         */
2062         destroyButton: function(id) {
2063             var button = getButton.call(this, id);
2064             if (button) {
2065                 var thisID = button.get('id'),
2066                     new_list = [], i = 0,
2067                     len = this._buttonList.length;
2068
2069                 button.destroy();
2070                 
2071                 for (i = 0; i < len; i++) {
2072                     if (this._buttonList[i].get('id') != thisID) {
2073                         new_list[new_list.length]= this._buttonList[i];
2074                     }
2075                 }
2076
2077                 this._buttonList = new_list;
2078             } else {
2079                 return false;
2080             }
2081         },
2082         /**
2083         * @method destroy
2084         * @description Destroys the toolbar, all of it's elements and objects.
2085         * @return {Boolean}
2086         */
2087         destroy: function() {
2088             var len = this._configuredButtons.length, j, i;
2089             for(b = 0; b < len; b++) {
2090                 this.destroyButton(this._configuredButtons[b]);
2091             }
2092
2093             this._configuredButtons = null;
2094         
2095             this.get('element').innerHTML = '';
2096             this.get('element').className = '';
2097             //Brutal Object Destroy
2098             for (i in this) {
2099                 if (Lang.hasOwnProperty(this, i)) {
2100                     this[i] = null;
2101                 }
2102             }
2103             return true;
2104         },
2105         /**
2106         * @method collapse
2107         * @description Programatically collapse the toolbar.
2108         * @param {Boolean} collapse True to collapse, false to expand.
2109         */
2110         collapse: function(collapse) {
2111             var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2112             if (collapse === false) {
2113                 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2114                 if (el[0]) {
2115                     Dom.removeClass(el[0], 'collapsed');
2116                     el[0].title = this.STR_COLLAPSE;
2117                 }
2118                 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2119             } else {
2120                 if (el[0]) {
2121                     Dom.addClass(el[0], 'collapsed');
2122                     el[0].title = this.STR_EXPAND;
2123                 }
2124                 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2125                 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2126             }
2127         },
2128         /**
2129         * @method toString
2130         * @description Returns a string representing the toolbar.
2131         * @return {String}
2132         */
2133         toString: function() {
2134             return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2135         }
2136     });
2137 /**
2138 * @event buttonClick
2139 * @param {Object} o The object passed to this handler is the button config used to create the button.
2140 * @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.
2141 * @type YAHOO.util.CustomEvent
2142 */
2143 /**
2144 * @event valueClick
2145 * @param {Object} o The object passed to this handler is the button config used to create the button.
2146 * @description This is a special dynamic event that is created and dispatched based on the value property
2147 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2148 * Example:
2149 * <code><pre>
2150 * buttons : [
2151 *   { type: 'button', value: 'test', value: 'testButton' }
2152 * ]</pre>
2153 * </code>
2154 * With the valueClick event you could subscribe to this buttons click event with this:
2155 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2156 * @type YAHOO.util.CustomEvent
2157 */
2158 /**
2159 * @event toolbarExpanded
2160 * @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.
2161 * @type YAHOO.util.CustomEvent
2162 */
2163 /**
2164 * @event toolbarCollapsed
2165 * @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.
2166 * @type YAHOO.util.CustomEvent
2167 */
2168 })();
2169 /**
2170  * @module editor
2171  * @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>
2172  * @namespace YAHOO.widget
2173  * @requires yahoo, dom, element, event, toolbar
2174  * @optional animation, container_core, resize, dragdrop
2175  */
2176
2177 (function() {
2178 var Dom = YAHOO.util.Dom,
2179     Event = YAHOO.util.Event,
2180     Lang = YAHOO.lang,
2181     Toolbar = YAHOO.widget.Toolbar;
2182
2183     /**
2184      * 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.
2185      * @constructor
2186      * @class SimpleEditor
2187      * @extends YAHOO.util.Element
2188      * @param {String/HTMLElement} el The textarea element to turn into an editor.
2189      * @param {Object} attrs Object liternal containing configuration parameters.
2190     */
2191     
2192     YAHOO.widget.SimpleEditor = function(el, attrs) {
2193         YAHOO.log('SimpleEditor Initalizing', 'info', 'SimpleEditor');
2194         
2195         var o = {};
2196         if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2197             Lang.augmentObject(o, el); //Break the config reference
2198             el = document.createElement('textarea');
2199             this.DOMReady = true;
2200             if (o.container) {
2201                 var c = Dom.get(o.container);
2202                 c.appendChild(el);
2203             } else {
2204                 document.body.appendChild(el);
2205             }
2206         } else {
2207             if (attrs) {
2208                 Lang.augmentObject(o, attrs); //Break the config reference
2209             }
2210         }
2211
2212         var oConfig = {
2213             element: null,
2214             attributes: o
2215         }, id = null;
2216
2217         if (Lang.isString(el)) {
2218             id = el;
2219         } else {
2220             if (oConfig.attributes.id) {
2221                 id = oConfig.attributes.id;
2222             } else {
2223                 this.DOMReady = true;
2224                 id = Dom.generateId(el);
2225             }
2226         }
2227         oConfig.element = el;
2228
2229         var element_cont = document.createElement('DIV');
2230         oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2231             id: id + '_container'
2232         });
2233         var div = document.createElement('div');
2234         Dom.addClass(div, 'first-child');
2235         oConfig.attributes.element_cont.appendChild(div);
2236         
2237         if (!oConfig.attributes.toolbar_cont) {
2238             oConfig.attributes.toolbar_cont = document.createElement('DIV');
2239             oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2240             div.appendChild(oConfig.attributes.toolbar_cont);
2241         }
2242         var editorWrapper = document.createElement('DIV');
2243         div.appendChild(editorWrapper);
2244         oConfig.attributes.editor_wrapper = editorWrapper;
2245
2246         YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2247     };
2248
2249
2250     YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2251         /**
2252         * @private
2253         * @property _resizeConfig
2254         * @description The default config for the Resize Utility
2255         */
2256         _resizeConfig: {
2257             handles: ['br'],
2258             autoRatio: true,
2259             status: true,
2260             proxy: true,
2261             useShim: true,
2262             setSize: false
2263         },
2264         /**
2265         * @private
2266         * @method _setupResize
2267         * @description Creates the Resize instance and binds its events.
2268         */
2269         _setupResize: function() {
2270             if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2271             if (this.get('resize')) {
2272                 var config = {};
2273                 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2274                 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2275                 this.resize.on('resize', function(args) {
2276                     var anim = this.get('animate');
2277                     this.set('animate', false);
2278                     this.set('width', args.width + 'px');
2279                     var h = args.height,
2280                         th = (this.toolbar.get('element').clientHeight + 2),
2281                         dh = 0;
2282                     if (this.dompath) {
2283                         dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2284                     }
2285                     var newH = (h - th - dh);
2286                     this.set('height', newH + 'px');
2287                     this.get('element_cont').setStyle('height', '');
2288                     this.set('animate', anim);
2289                 }, this, true);
2290             }
2291         },
2292         /**
2293         * @property resize
2294         * @description A reference to the Resize object
2295         * @type YAHOO.util.Resize
2296         */
2297         resize: null,
2298         /**
2299         * @private
2300         * @method _setupDD
2301         * @description Sets up the DD instance used from the 'drag' config option.
2302         */
2303         _setupDD: function() {
2304             if (!YAHOO.util.DD) { return false; }
2305             if (this.get('drag')) {
2306                 YAHOO.log('Attaching DD instance to Editor', 'info', 'SimpleEditor');
2307                 var d = this.get('drag'),
2308                     dd = YAHOO.util.DD;
2309                 if (d === 'proxy') {
2310                     dd = YAHOO.util.DDProxy;
2311                 }
2312
2313                 this.dd = new dd(this.get('element_cont').get('element'));
2314                 this.toolbar.addClass('draggable'); 
2315                 this.dd.setHandleElId(this.toolbar._titlebar); 
2316             }
2317         },
2318         /**
2319         * @property dd
2320         * @description A reference to the DragDrop object.
2321         * @type YAHOO.util.DD/YAHOO.util.DDProxy
2322         */
2323         dd: null,
2324         /**
2325         * @private
2326         * @property _lastCommand
2327         * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2328         * @type String
2329         */
2330         _lastCommand: null,
2331         _undoNodeChange: function() {},
2332         _storeUndo: function() {},
2333         /**
2334         * @private
2335         * @method _checkKey
2336         * @description Checks a keyMap entry against a key event
2337         * @param {Object} k The _keyMap object
2338         * @param {Event} e The Mouse Event
2339         * @return {Boolean}
2340         */
2341         _checkKey: function(k, e) {
2342             var ret = false;
2343             if ((e.keyCode === k.key)) {
2344                 if (k.mods && (k.mods.length > 0)) {
2345                     var val = 0;
2346                     for (var i = 0; i < k.mods.length; i++) {
2347                         if (this.browser.mac) {
2348                             if (k.mods[i] == 'ctrl') {
2349                                 k.mods[i] = 'meta';
2350                             }
2351                         }
2352                         if (e[k.mods[i] + 'Key'] === true) {
2353                             val++;
2354                         }
2355                     }
2356                     if (val === k.mods.length) {
2357                         ret = true;
2358                     }
2359                 } else {
2360                     ret = true;
2361                 }
2362             }
2363             //YAHOO.log('Shortcut Key Check: (' + k.key + ') return: ' + ret, 'info', 'SimpleEditor');
2364             return ret;
2365         },
2366         /**
2367         * @private
2368         * @property _keyMap
2369         * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>. 
2370         * 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.
2371         * @type {Object/Mixed}
2372         */
2373         _keyMap: {
2374             SELECT_ALL: {
2375                 key: 65, //A key
2376                 mods: ['ctrl']
2377             },
2378             CLOSE_WINDOW: {
2379                 key: 87, //W key
2380                 mods: ['shift', 'ctrl']
2381             },
2382             FOCUS_TOOLBAR: {
2383                 key: 27,
2384                 mods: ['shift']
2385             },
2386             FOCUS_AFTER: {
2387                 key: 27
2388             },
2389             FONT_SIZE_UP: {
2390                 key: 38,
2391                 mods: ['shift', 'ctrl']
2392             },
2393             FONT_SIZE_DOWN: {
2394                 key: 40,
2395                 mods: ['shift', 'ctrl']
2396             },
2397             CREATE_LINK: {
2398                 key: 76,
2399                 mods: ['shift', 'ctrl']
2400             },
2401             BOLD: {
2402                 key: 66,
2403                 mods: ['shift', 'ctrl']
2404             },
2405             ITALIC: {
2406                 key: 73,
2407                 mods: ['shift', 'ctrl']
2408             },
2409             UNDERLINE: {
2410                 key: 85,
2411                 mods: ['shift', 'ctrl']
2412             },
2413             UNDO: {
2414                 key: 90,
2415                 mods: ['ctrl']
2416             },
2417             REDO: {
2418                 key: 90,
2419                 mods: ['shift', 'ctrl']
2420             },
2421             JUSTIFY_LEFT: {
2422                 key: 219,
2423                 mods: ['shift', 'ctrl']
2424             },
2425             JUSTIFY_CENTER: {
2426                 key: 220,
2427                 mods: ['shift', 'ctrl']
2428             },
2429             JUSTIFY_RIGHT: {
2430                 key: 221,
2431                 mods: ['shift', 'ctrl']
2432             }
2433         },
2434         /**
2435         * @private
2436         * @method _cleanClassName
2437         * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2438         * @param {String} str The classname to clean up
2439         * @return {String}
2440         */
2441         _cleanClassName: function(str) {
2442             return str.replace(/ /g, '-').toLowerCase();
2443         },
2444         /**
2445         * @property _textarea
2446         * @description Flag to determine if we are using a textarea or an HTML Node.
2447         * @type Boolean
2448         */
2449         _textarea: null,
2450         /**
2451         * @property _docType
2452         * @description The DOCTYPE to use in the editable container.
2453         * @type String
2454         */
2455         _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2456         /**
2457         * @property editorDirty
2458         * @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.
2459         * @type Boolean
2460         */
2461         editorDirty: null,
2462         /**
2463         * @property _defaultCSS
2464         * @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' }
2465         * @type String
2466         */
2467         _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; }',
2468         /**
2469         * @property _defaultToolbar
2470         * @private
2471         * @description Default toolbar config.
2472         * @type Object
2473         */
2474         _defaultToolbar: null,
2475         /**
2476         * @property _lastButton
2477         * @private
2478         * @description The last button pressed, so we don't disable it.
2479         * @type Object
2480         */
2481         _lastButton: null,
2482         /**
2483         * @property _baseHREF
2484         * @private
2485         * @description The base location of the editable page (this page) so that relative paths for image work.
2486         * @type String
2487         */
2488         _baseHREF: function() {
2489             var href = document.location.href;
2490             if (href.indexOf('?') !== -1) { //Remove the query string
2491                 href = href.substring(0, href.indexOf('?'));
2492             }
2493             href = href.substring(0, href.lastIndexOf('/')) + '/';
2494             return href;
2495         }(),
2496         /**
2497         * @property _lastImage
2498         * @private
2499         * @description Safari reference for the last image selected (for styling as selected).
2500         * @type HTMLElement
2501         */
2502         _lastImage: null,
2503         /**
2504         * @property _blankImageLoaded
2505         * @private
2506         * @description Don't load the blank image more than once..
2507         * @type Boolean
2508         */
2509         _blankImageLoaded: null,
2510         /**
2511         * @property _fixNodesTimer
2512         * @private
2513         * @description Holder for the fixNodes timer
2514         * @type Date
2515         */
2516         _fixNodesTimer: null,
2517         /**
2518         * @property _nodeChangeTimer
2519         * @private
2520         * @description Holds a reference to the nodeChange setTimeout call
2521         * @type Number
2522         */
2523         _nodeChangeTimer: null,
2524         /**
2525         * @property _nodeChangeDelayTimer
2526         * @private
2527         * @description Holds a reference to the nodeChangeDelay setTimeout call
2528         * @type Number
2529         */
2530         _nodeChangeDelayTimer: null,
2531         /**
2532         * @property _lastNodeChangeEvent
2533         * @private
2534         * @description Flag to determine the last event that fired a node change
2535         * @type Event
2536         */
2537         _lastNodeChangeEvent: null,
2538         /**
2539         * @property _lastNodeChange
2540         * @private
2541         * @description Flag to determine when the last node change was fired
2542         * @type Date
2543         */
2544         _lastNodeChange: 0,
2545         /**
2546         * @property _rendered
2547         * @private
2548         * @description Flag to determine if editor has been rendered or not
2549         * @type Boolean
2550         */
2551         _rendered: null,
2552         /**
2553         * @property DOMReady
2554         * @private
2555         * @description Flag to determine if DOM is ready or not
2556         * @type Boolean
2557         */
2558         DOMReady: null,
2559         /**
2560         * @property _selection
2561         * @private
2562         * @description Holder for caching iframe selections
2563         * @type Object
2564         */
2565         _selection: null,
2566         /**
2567         * @property _mask
2568         * @private
2569         * @description DOM Element holder for the editor Mask when disabled
2570         * @type Object
2571         */
2572         _mask: null,
2573         /**
2574         * @property _showingHiddenElements
2575         * @private
2576         * @description Status of the hidden elements button
2577         * @type Boolean
2578         */
2579         _showingHiddenElements: null,
2580         /**
2581         * @property currentWindow
2582         * @description A reference to the currently open EditorWindow
2583         * @type Object
2584         */
2585         currentWindow: null,
2586         /**
2587         * @property currentEvent
2588         * @description A reference to the current editor event
2589         * @type Event
2590         */
2591         currentEvent: null,
2592         /**
2593         * @property operaEvent
2594         * @private
2595         * @description setTimeout holder for Opera and Image DoubleClick event..
2596         * @type Object
2597         */
2598         operaEvent: null,
2599         /**
2600         * @property currentFont
2601         * @description A reference to the last font selected from the Toolbar
2602         * @type HTMLElement
2603         */
2604         currentFont: null,
2605         /**
2606         * @property currentElement
2607         * @description A reference to the current working element in the editor
2608         * @type Array
2609         */
2610         currentElement: null,
2611         /**
2612         * @property dompath
2613         * @description A reference to the dompath container for writing the current working dom path to.
2614         * @type HTMLElement
2615         */
2616         dompath: null,
2617         /**
2618         * @property beforeElement
2619         * @description A reference to the H2 placed before the editor for Accessibilty.
2620         * @type HTMLElement
2621         */
2622         beforeElement: null,
2623         /**
2624         * @property afterElement
2625         * @description A reference to the H2 placed after the editor for Accessibilty.
2626         * @type HTMLElement
2627         */
2628         afterElement: null,
2629         /**
2630         * @property invalidHTML
2631         * @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.
2632         * @type Object
2633         */
2634         invalidHTML: {
2635             form: true,
2636             input: true,
2637             button: true,
2638             select: true,
2639             link: true,
2640             html: true,
2641             body: true,
2642             iframe: true,
2643             script: true,
2644             style: true,
2645             textarea: true
2646         },
2647         /**
2648         * @property toolbar
2649         * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2650         * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2651         */
2652         toolbar: null,
2653         /**
2654         * @private
2655         * @property _contentTimer
2656         * @description setTimeout holder for documentReady check
2657         */
2658         _contentTimer: null,
2659         /**
2660         * @private
2661         * @property _contentTimerMax
2662         * @description The number of times the loaded content should be checked before giving up. Default: 500
2663         */
2664         _contentTimerMax: 500,
2665         /**
2666         * @private
2667         * @property _contentTimerCounter
2668         * @description Counter to check the number of times the body is polled for before giving up
2669         * @type Number
2670         */
2671         _contentTimerCounter: 0,
2672         /**
2673         * @private
2674         * @property _disabled
2675         * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2676         * @type Array
2677         */
2678         _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2679         /**
2680         * @private
2681         * @property _alwaysDisabled
2682         * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2683         * @type Object
2684         */
2685         _alwaysDisabled: { undo: true, redo: true },
2686         /**
2687         * @private
2688         * @property _alwaysEnabled
2689         * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2690         * @type Object
2691         */
2692         _alwaysEnabled: { },
2693         /**
2694         * @private
2695         * @property _semantic
2696         * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2697         * @type Object
2698         */
2699         _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2700         /**
2701         * @private
2702         * @property _tag2cmd
2703         * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2704         * @type Object
2705         */
2706         _tag2cmd: {
2707             'b': 'bold',
2708             'strong': 'bold',
2709             'i': 'italic',
2710             'em': 'italic',
2711             'u': 'underline',
2712             'sup': 'superscript',
2713             'sub': 'subscript',
2714             'img': 'insertimage',
2715             'a' : 'createlink',
2716             'ul' : 'insertunorderedlist',
2717             'ol' : 'insertorderedlist'
2718         },
2719
2720         /**
2721         * @private _createIframe
2722         * @description Creates the DOM and YUI Element for the iFrame editor area.
2723         * @param {String} id The string ID to prefix the iframe with
2724         * @return {Object} iFrame object
2725         */
2726         _createIframe: function() {
2727             var ifrmDom = document.createElement('iframe');
2728             ifrmDom.id = this.get('id') + '_editor';
2729             var config = {
2730                 border: '0',
2731                 frameBorder: '0',
2732                 marginWidth: '0',
2733                 marginHeight: '0',
2734                 leftMargin: '0',
2735                 topMargin: '0',
2736                 allowTransparency: 'true',
2737                 width: '100%'
2738             };
2739             if (this.get('autoHeight')) {
2740                 config.scrolling = 'no';
2741             }
2742             for (var i in config) {
2743                 if (Lang.hasOwnProperty(config, i)) {
2744                     ifrmDom.setAttribute(i, config[i]);
2745                 }
2746             }
2747             var isrc = 'javascript:;';
2748             if (this.browser.ie) {
2749                 //isrc = 'about:blank';
2750                 //TODO - Check this, I have changed it before..
2751                 isrc = 'javascript:false;';
2752             }
2753             ifrmDom.setAttribute('src', isrc);
2754             var ifrm = new YAHOO.util.Element(ifrmDom);
2755             ifrm.setStyle('visibility', 'hidden');
2756             return ifrm;
2757         },
2758         /**
2759         * @private _isElement
2760         * @description Checks to see if an Element reference is a valid one and has a certain tag type
2761         * @param {HTMLElement} el The element to check
2762         * @param {String} tag The tag that the element needs to be
2763         * @return {Boolean}
2764         */
2765         _isElement: function(el, tag) {
2766             if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2767                 return true;
2768             }
2769             if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2770                 return true;
2771             }
2772             return false;
2773         },
2774         /**
2775         * @private _hasParent
2776         * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2777         * @param {HTMLElement} el The element to check
2778         * @param {String} tag The tag that the element needs to be
2779         * @return HTMLElement
2780         */
2781         _hasParent: function(el, tag) {
2782             if (!el || !el.parentNode) {
2783                 return false;
2784             }
2785             
2786             while (el.parentNode) {
2787                 if (this._isElement(el, tag)) {
2788                     return el;
2789                 }
2790                 if (el.parentNode) {
2791                     el = el.parentNode;
2792                 } else {
2793                     return false;
2794                 }
2795             }
2796             return false;
2797         },
2798         /**
2799         * @private
2800         * @method _getDoc
2801         * @description Get the Document of the IFRAME
2802         * @return {Object}
2803         */
2804         _getDoc: function() {
2805             var value = false;
2806             try {
2807                 if (this.get('iframe').get('element').contentWindow.document) {
2808                     value = this.get('iframe').get('element').contentWindow.document;
2809                     return value;
2810                 }
2811             } catch (e) {
2812                 return false;
2813             }
2814         },
2815         /**
2816         * @private
2817         * @method _getWindow
2818         * @description Get the Window of the IFRAME
2819         * @return {Object}
2820         */
2821         _getWindow: function() {
2822             return this.get('iframe').get('element').contentWindow;
2823         },
2824         /**
2825         * @method focus
2826         * @description Attempt to set the focus of the iframes window.
2827         */
2828         focus: function() {
2829             this._getWindow().focus();
2830         },
2831         /**
2832         * @private
2833         * @depreciated - This should not be used, moved to this.focus();
2834         * @method _focusWindow
2835         * @description Attempt to set the focus of the iframes window.
2836         */
2837         _focusWindow: function() {
2838             YAHOO.log('_focusWindow: depreciated in favor of this.focus()', 'warn', 'Editor');
2839             this.focus();
2840         },
2841         /**
2842         * @private
2843         * @method _hasSelection
2844         * @description Determines if there is a selection in the editor document.
2845         * @return {Boolean}
2846         */
2847         _hasSelection: function() {
2848             var sel = this._getSelection();
2849             var range = this._getRange();
2850             var hasSel = false;
2851
2852             if (!sel || !range) {
2853                 return hasSel;
2854             }
2855
2856             //Internet Explorer
2857             if (this.browser.ie || this.browser.opera) {
2858                 if (range.text) {
2859                     hasSel = true;
2860                 }
2861                 if (range.html) {
2862                     hasSel = true;
2863                 }
2864             } else {
2865                 if (this.browser.webkit) {
2866                     if (sel+'' !== '') {
2867                         hasSel = true;
2868                     }
2869                 } else {
2870                     if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2871                         hasSel = true;
2872                     }
2873                 }
2874             }
2875             return hasSel;
2876         },
2877         /**
2878         * @private
2879         * @method _getSelection
2880         * @description Handles the different selection objects across the A-Grade list.
2881         * @return {Object} Selection Object
2882         */
2883         _getSelection: function() {
2884             var _sel = null;
2885             if (this._getDoc() && this._getWindow()) {
2886                 if (this._getDoc().selection) {
2887                     _sel = this._getDoc().selection;
2888                 } else {
2889                     _sel = this._getWindow().getSelection();
2890                 }
2891                 //Handle Safari's lack of Selection Object
2892                 if (this.browser.webkit) {
2893                     if (_sel.baseNode) {
2894                             this._selection = {};
2895                             this._selection.baseNode = _sel.baseNode;
2896                             this._selection.baseOffset = _sel.baseOffset;
2897                             this._selection.extentNode = _sel.extentNode;
2898                             this._selection.extentOffset = _sel.extentOffset;
2899                     } else if (this._selection !== null) {
2900                         _sel = this._getWindow().getSelection();
2901                         _sel.setBaseAndExtent(
2902                             this._selection.baseNode,
2903                             this._selection.baseOffset,
2904                             this._selection.extentNode,
2905                             this._selection.extentOffset);
2906                         this._selection = null;
2907                     }
2908                 }
2909             }
2910             return _sel;
2911         },
2912         /**
2913         * @private
2914         * @method _selectNode
2915         * @description Places the highlight around a given node
2916         * @param {HTMLElement} node The node to select
2917         */
2918         _selectNode: function(node, collapse) {
2919             if (!node) {
2920                 return false;
2921             }
2922             var sel = this._getSelection(),
2923                 range = null;
2924
2925             if (this.browser.ie) {
2926                 try { //IE freaks out here sometimes..
2927                     range = this._getDoc().body.createTextRange();
2928                     range.moveToElementText(node);
2929                     range.select();
2930                 } catch (e) {
2931                     YAHOO.log('IE failed to select element: ' + node.tagName, 'warn', 'SimpleEditor');
2932                 }
2933             } else if (this.browser.webkit) {
2934                 if (collapse) {
2935                                     sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2936                 } else {
2937                                     sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2938                 }
2939             } else if (this.browser.opera) {
2940                 sel = this._getWindow().getSelection();
2941                 range = this._getDoc().createRange();
2942                 range.selectNode(node);
2943                 sel.removeAllRanges();
2944                 sel.addRange(range);
2945             } else {
2946                 range = this._getDoc().createRange();
2947                 range.selectNodeContents(node);
2948                 sel.removeAllRanges();
2949                 sel.addRange(range);
2950             }
2951             //TODO - Check Performance
2952             this.nodeChange();
2953         },
2954         /**
2955         * @private
2956         * @method _getRange
2957         * @description Handles the different range objects across the A-Grade list.
2958         * @return {Object} Range Object
2959         */
2960         _getRange: function() {
2961             var sel = this._getSelection();
2962
2963             if (sel === null) {
2964                 return null;
2965             }
2966
2967             if (this.browser.webkit && !sel.getRangeAt) {
2968                 var _range = this._getDoc().createRange();
2969                 try {
2970                     _range.setStart(sel.anchorNode, sel.anchorOffset);
2971                     _range.setEnd(sel.focusNode, sel.focusOffset);
2972                 } catch (e) {
2973                     _range = this._getWindow().getSelection()+'';
2974                 }
2975                 return _range;
2976             }
2977
2978             if (this.browser.ie || this.browser.opera) {
2979                 try {
2980                     return sel.createRange();
2981                 } catch (e2) {
2982                     return null;
2983                 }
2984             }
2985
2986             if (sel.rangeCount > 0) {
2987                 return sel.getRangeAt(0);
2988             }
2989             return null;
2990         },
2991         /**
2992         * @private
2993         * @method _setDesignMode
2994         * @description Sets the designMode property of the iFrame document's body.
2995         * @param {String} state This should be either on or off
2996         */
2997         _setDesignMode: function(state) {
2998             if (this.get('setDesignMode')) {
2999                 try {
3000                     this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on');
3001                 } catch(e) { }
3002             }
3003         },
3004         /**
3005         * @private
3006         * @method _toggleDesignMode
3007         * @description Toggles the designMode property of the iFrame document on and off.
3008         * @return {String} The state that it was set to.
3009         */
3010         _toggleDesignMode: function() {
3011             YAHOO.log('It is not recommended to use this method and it will be depreciated.', 'warn', 'SimpleEditor');
3012             var _dMode = this._getDoc().designMode,
3013                 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on');
3014             this._setDesignMode(_state);
3015             return _state;
3016         },
3017         /**
3018         * @private
3019         * @property _focused
3020         * @description Holder for trapping focus/blur state and prevent double events
3021         * @type Boolean
3022         */
3023         _focused: null,
3024         /**
3025         * @private
3026         * @method _handleFocus
3027         * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
3028         * @param {Event} e The DOM Event
3029         */
3030         _handleFocus: function(e) {
3031             if (!this._focused) {
3032                 //YAHOO.log('Editor Window Focused', 'info', 'SimpleEditor');
3033                 this._focused = true;
3034                 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
3035             }
3036         },
3037         /**
3038         * @private
3039         * @method _handleBlur
3040         * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
3041         * @param {Event} e The DOM Event
3042         */
3043         _handleBlur: function(e) {
3044             if (this._focused) {
3045                 //YAHOO.log('Editor Window Blurred', 'info', 'SimpleEditor');
3046                 this._focused = false;
3047                 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
3048             }
3049         },
3050         /**
3051         * @private
3052         * @method _initEditorEvents
3053         * @description This method sets up the listeners on the Editors document.
3054         */
3055         _initEditorEvents: function() {
3056             //Setup Listeners on iFrame
3057             var doc = this._getDoc(),
3058                 win = this._getWindow();
3059
3060             Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3061             Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3062             Event.on(doc, 'click', this._handleClick, this, true);
3063             Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3064             Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3065             Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3066             Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3067             /* TODO -- Everyone but Opera works here..
3068             Event.on(doc, 'paste', function() {
3069                 YAHOO.log('PASTE', 'info', 'SimpleEditor');
3070             }, this, true);
3071             */
3072  
3073             //Focus and blur..
3074             Event.on(win, 'focus', this._handleFocus, this, true);
3075             Event.on(win, 'blur', this._handleBlur, this, true);
3076         },
3077         /**
3078         * @private
3079         * @method _removeEditorEvents
3080         * @description This method removes the listeners on the Editors document (for disabling).
3081         */
3082         _removeEditorEvents: function() {
3083             //Remove Listeners on iFrame
3084             var doc = this._getDoc(),
3085                 win = this._getWindow();
3086
3087             Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3088             Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3089             Event.removeListener(doc, 'click', this._handleClick, this, true);
3090             Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3091             Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3092             Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3093             Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3094
3095             //Focus and blur..
3096             Event.removeListener(win, 'focus', this._handleFocus, this, true);
3097             Event.removeListener(win, 'blur', this._handleBlur, this, true);
3098         },
3099         _fixWebkitDivs: function() {
3100             if (this.browser.webkit) {
3101                 var divs = this._getDoc().body.getElementsByTagName('div');
3102                 Dom.addClass(divs, 'yui-wk-div');
3103             }
3104         },
3105         /**
3106         * @private
3107         * @method _initEditor
3108         * @param {Boolean} raw Don't add events.
3109         * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3110         */
3111         _initEditor: function(raw) {
3112             if (this._editorInit) {
3113                 return;
3114             }
3115             this._editorInit = true;
3116             if (this.browser.ie) {
3117                 this._getDoc().body.style.margin = '0';
3118             }
3119             if (!this.get('disabled')) {
3120                 this._setDesignMode('on');
3121                 this._contentTimerCounter = 0;
3122             }
3123             if (!this._getDoc().body) {
3124                 YAHOO.log('Body is null, check again', 'error', 'SimpleEditor');
3125                 this._contentTimerCounter = 0;
3126                 this._editorInit = false;
3127                 this._checkLoaded();
3128                 return false;
3129             }
3130             
3131             YAHOO.log('editorLoaded', 'info', 'SimpleEditor');
3132             if (!raw) {
3133                 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3134             }
3135             if (!this.get('disabled')) {
3136                 this._initEditorEvents();
3137                 this.toolbar.set('disabled', false);
3138             }
3139
3140             if (raw) {
3141                 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this });
3142             } else {
3143                 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3144             }
3145             this._fixWebkitDivs();
3146             if (this.get('dompath')) {
3147                 YAHOO.log('Delayed DomPath write', 'info', 'SimpleEditor');
3148                 var self = this;
3149                 setTimeout(function() {
3150                     self._writeDomPath.call(self);
3151                     self._setupResize.call(self);
3152                 }, 150);
3153             }
3154             var br = [];
3155             for (var i in this.browser) {
3156                 if (this.browser[i]) {
3157                     br.push(i);
3158                 }
3159             }
3160             if (this.get('ptags')) {
3161                 br.push('ptags');
3162             }
3163             Dom.addClass(this._getDoc().body, br.join(' '));
3164             this.nodeChange(true);
3165         },
3166         /**
3167         * @private
3168         * @method _checkLoaded
3169         * @param {Boolean} raw Don't add events.
3170         * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3171         */
3172         _checkLoaded: function(raw) {
3173             this._editorInit = false;
3174             this._contentTimerCounter++;
3175             if (this._contentTimer) {
3176                 clearTimeout(this._contentTimer);
3177             }
3178             if (this._contentTimerCounter > this._contentTimerMax) {
3179                 YAHOO.log('ERROR: Body Did Not load', 'error', 'SimpleEditor');
3180                 return false;
3181             }
3182             var init = false;
3183             try {
3184                 if (this._getDoc() && this._getDoc().body) {
3185                     if (this.browser.ie) {
3186                         if (this._getDoc().body.readyState == 'complete') {
3187                             init = true;
3188                         }
3189                     } else {
3190                         if (this._getDoc().body._rteLoaded === true) {
3191                             init = true;
3192                         }
3193                     }
3194                 }
3195             } catch (e) {
3196                 init = false;
3197                 YAHOO.log('checking body (e)' + e, 'error', 'SimpleEditor');
3198             }
3199
3200             if (init === true) {
3201                 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3202                 YAHOO.log('Firing _initEditor', 'info', 'SimpleEditor');
3203                 this._initEditor(raw);
3204             } else {
3205                 var self = this;
3206                 this._contentTimer = setTimeout(function() {
3207                     self._checkLoaded.call(self, raw);
3208                 }, 20);
3209             }
3210         },
3211         /**
3212         * @private
3213         * @method _setInitialContent
3214         * @param {Boolean} raw Don't add events.
3215         * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3216         */
3217         _setInitialContent: function(raw) {
3218             YAHOO.log('Populating editor body with contents of the text area', 'info', 'SimpleEditor');
3219
3220             var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3221                 doc = null;
3222
3223             if (value === '') {
3224                 value = '<br>';
3225             }
3226
3227             var html = Lang.substitute(this.get('html'), {
3228                 TITLE: this.STR_TITLE,
3229                 CONTENT: this._cleanIncomingHTML(value),
3230                 CSS: this.get('css'),
3231                 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3232                 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3233             }),
3234             check = true;
3235
3236             html = html.replace(/RIGHT_BRACKET/gi, '{');
3237             html = html.replace(/LEFT_BRACKET/gi, '}');
3238
3239             if (document.compatMode != 'BackCompat') {
3240                 YAHOO.log('Adding Doctype to editable area', 'info', 'SimpleEditor');
3241                 html = this._docType + "\n" + html;
3242             } else {
3243                 YAHOO.log('DocType skipped because we are in BackCompat Mode.', 'warn', 'SimpleEditor');
3244             }
3245
3246             if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3247                 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3248                 try {
3249                     //Adobe AIR Code
3250                     if (this.browser.air) {
3251                         doc = this._getDoc().implementation.createHTMLDocument();
3252                         var origDoc = this._getDoc();
3253                         origDoc.open();
3254                         origDoc.close();
3255                         doc.open();
3256                         doc.write(html);
3257                         doc.close();
3258                         var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3259                         origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3260                         origDoc.body._rteLoaded = true;
3261                     } else {
3262                         doc = this._getDoc();
3263                         doc.open();
3264                         doc.write(html);
3265                         doc.close();
3266                     }
3267                 } catch (e) {
3268                     YAHOO.log('Setting doc failed.. (_setInitialContent)', 'error', 'SimpleEditor');
3269                     //Safari will only be here if we are hidden
3270                     check = false;
3271                 }
3272             } else {
3273                 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3274                 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3275             }
3276             this.get('iframe').setStyle('visibility', '');
3277             if (check) {
3278                 this._checkLoaded(raw);
3279             }            
3280         },
3281         /**
3282         * @private
3283         * @method _setMarkupType
3284         * @param {String} action The action to take. Possible values are: css, default or semantic
3285         * @description This method will turn on/off the useCSS execCommand.
3286         */
3287         _setMarkupType: function(action) {
3288             switch (this.get('markup')) {
3289                 case 'css':
3290                     this._setEditorStyle(true);
3291                     break;
3292                 case 'default':
3293                     this._setEditorStyle(false);
3294                     break;
3295                 case 'semantic':
3296                 case 'xhtml':
3297                     if (this._semantic[action]) {
3298                         this._setEditorStyle(false);
3299                     } else {
3300                         this._setEditorStyle(true);
3301                     }
3302                     break;
3303             }
3304         },
3305         /**
3306         * Set the editor to use CSS instead of HTML
3307         * @param {Booleen} stat True/False
3308         */
3309         _setEditorStyle: function(stat) {
3310             try {
3311                 this._getDoc().execCommand('useCSS', false, !stat);
3312             } catch (ex) {
3313             }
3314         },
3315         /**
3316         * @private
3317         * @method _getSelectedElement
3318         * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3319         * @return {HTMLElement} The currently selected element.
3320         */
3321         _getSelectedElement: function() {
3322             var doc = this._getDoc(),
3323                 range = null,
3324                 sel = null,
3325                 elm = null,
3326                 check = true;
3327
3328             if (this.browser.ie) {
3329                 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3330                 range = this._getRange();
3331                 if (range) {
3332                     elm = range.item ? range.item(0) : range.parentElement();
3333                     if (this._hasSelection()) {
3334                         //TODO
3335                         //WTF.. Why can't I get an element reference here?!??!
3336                     }
3337                     if (elm === doc.body) {
3338                         elm = null;
3339                     }
3340                 }
3341                 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3342                     elm = Event.getTarget(this.currentEvent);
3343                 }
3344             } else {
3345                 sel = this._getSelection();
3346                 range = this._getRange();
3347
3348                 if (!sel || !range) {
3349                     return null;
3350                 }
3351                 //TODO
3352                 if (!this._hasSelection() && this.browser.webkit3) {
3353                     //check = false;
3354                 }
3355                 if (this.browser.gecko) {
3356                     //Added in 2.6.0
3357                     if (range.startContainer) {
3358                         if (range.startContainer.nodeType === 3) {
3359                             elm = range.startContainer.parentNode;
3360                         } else if (range.startContainer.nodeType === 1) {
3361                             elm = range.startContainer;
3362                         }
3363                         //Added in 2.7.0
3364                         if (this.currentEvent) {
3365                             var tar = Event.getTarget(this.currentEvent);
3366                             if (!this._isElement(tar, 'html')) {
3367                                 if (elm !== tar) {
3368                                     elm = tar;
3369                                 }
3370                             }
3371                         }
3372                     }
3373                 }
3374                 
3375                 if (check) {
3376                     if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3377                         if (sel.anchorNode.parentNode) { //next check parentNode
3378                             elm = sel.anchorNode.parentNode;
3379                         }
3380                         if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3381                             elm = sel.anchorNode.nextSibling;
3382                         }
3383                     }
3384                     if (this._isElement(elm, 'br')) {
3385                         elm = null;
3386                     }
3387                     if (!elm) {
3388                         elm = range.commonAncestorContainer;
3389                         if (!range.collapsed) {
3390                             if (range.startContainer == range.endContainer) {
3391                                 if (range.startOffset - range.endOffset < 2) {
3392                                     if (range.startContainer.hasChildNodes()) {
3393                                         elm = range.startContainer.childNodes[range.startOffset];
3394                                     }
3395                                 }
3396                             }
3397                         }
3398                     }
3399                }
3400             }
3401             
3402             if (this.currentEvent !== null) {
3403                 try {
3404                     switch (this.currentEvent.type) {
3405                         case 'click':
3406                         case 'mousedown':
3407                         case 'mouseup':
3408                             if (this.browser.webkit) {
3409                                 elm = Event.getTarget(this.currentEvent);
3410                             }
3411                             break;
3412                         default:
3413                             //Do nothing
3414                             break;
3415                     }
3416                 } catch (e) {
3417                     YAHOO.log('Firefox 1.5 errors here: ' + e, 'error', 'SimpleEditor');
3418                 }
3419             } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3420                 //TODO is this still needed?
3421                 //elm = this.currentElement[0];
3422             }
3423
3424
3425             if (this.browser.opera || this.browser.webkit) {
3426                 if (this.currentEvent && !elm) {
3427                     elm = YAHOO.util.Event.getTarget(this.currentEvent);
3428                 }
3429             }
3430             if (!elm || !elm.tagName) {
3431                 elm = doc.body;
3432             }
3433             if (this._isElement(elm, 'html')) {
3434                 //Safari sometimes gives us the HTML node back..
3435                 elm = doc.body;
3436             }
3437             if (this._isElement(elm, 'body')) {
3438                 //make sure that body means this body not the parent..
3439                 elm = doc.body;
3440             }
3441             if (elm && !elm.parentNode) { //Not in document
3442                 elm = doc.body;
3443             }
3444             if (elm === undefined) {
3445                 elm = null;
3446             }
3447             return elm;
3448         },
3449         /**
3450         * @private
3451         * @method _getDomPath
3452         * @description This method will attempt to build the DOM path from the currently selected element.
3453         * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3454         * @return {Array} An array of node references that will create the DOM Path.
3455         */
3456         _getDomPath: function(el) {
3457             if (!el) {
3458                             el = this._getSelectedElement();
3459             }
3460                         var domPath = [];
3461             while (el !== null) {
3462                 if (el.ownerDocument != this._getDoc()) {
3463                     el = null;
3464                     break;
3465                 }
3466                 //Check to see if we get el.nodeName and nodeType
3467                 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3468                     domPath[domPath.length] = el;
3469                 }
3470
3471                 if (this._isElement(el, 'body')) {
3472                     break;
3473                 }
3474
3475                 el = el.parentNode;
3476             }
3477             if (domPath.length === 0) {
3478                 if (this._getDoc() && this._getDoc().body) {
3479                     domPath[0] = this._getDoc().body;
3480                 }
3481             }
3482             return domPath.reverse();
3483         },
3484         /**
3485         * @private
3486         * @method _writeDomPath
3487         * @description Write the current DOM path out to the dompath container below the editor.
3488         */
3489         _writeDomPath: function() { 
3490             var path = this._getDomPath(),
3491                 pathArr = [],
3492                 classPath = '',
3493                 pathStr = '';
3494
3495             for (var i = 0; i < path.length; i++) {
3496                 var tag = path[i].tagName.toLowerCase();
3497                 if ((tag == 'ol') && (path[i].type)) {
3498                     tag += ':' + path[i].type;
3499                 }
3500                 if (Dom.hasClass(path[i], 'yui-tag')) {
3501                     tag = path[i].getAttribute('tag');
3502                 }
3503                 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3504                     switch (tag) {
3505                         case 'b': tag = 'strong'; break;
3506                         case 'i': tag = 'em'; break;
3507                     }
3508                 }
3509                 if (!Dom.hasClass(path[i], 'yui-non')) {
3510                     if (Dom.hasClass(path[i], 'yui-tag')) {
3511                         pathStr = tag;
3512                     } else {
3513                         classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3514                         if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3515                             classPath = '';
3516                         }
3517                         pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3518                     }
3519                     switch (tag) {
3520                         case 'body':
3521                             pathStr = 'body';
3522                             break;
3523                         case 'a':
3524                             if (path[i].getAttribute('href', 2)) {
3525                                 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3526                             }
3527                             break;
3528                         case 'img':
3529                             var h = path[i].height;
3530                             var w = path[i].width;
3531                             if (path[i].style.height) {
3532                                 h = parseInt(path[i].style.height, 10);
3533                             }
3534                             if (path[i].style.width) {
3535                                 w = parseInt(path[i].style.width, 10);
3536                             }
3537                             pathStr += '(' + w + 'x' + h + ')';
3538                         break;
3539                     }
3540
3541                     if (pathStr.length > 10) {
3542                         pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3543                     } else {
3544                         pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3545                     }
3546                     pathArr[pathArr.length] = pathStr;
3547                 }
3548             }
3549             var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3550             //Prevent flickering
3551             if (this.dompath.innerHTML != str) {
3552                 this.dompath.innerHTML = str;
3553             }
3554         },
3555         /**
3556         * @private
3557         * @method _fixNodes
3558         * @description Fix href and imgs as well as remove invalid HTML.
3559         */
3560         _fixNodes: function() {
3561             try {
3562                 var doc = this._getDoc(),
3563                     els = [];
3564
3565                 for (var v in this.invalidHTML) {
3566                     if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3567                         if (v.toLowerCase() != 'span') {
3568                             var tags = doc.body.getElementsByTagName(v);
3569                             if (tags.length) {
3570                                 for (var i = 0; i < tags.length; i++) {
3571                                     els.push(tags[i]);
3572                                 }
3573                             }
3574                         }
3575                     }
3576                 }
3577                 for (var h = 0; h < els.length; h++) {
3578                     if (els[h].parentNode) {
3579                         if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3580                             this._swapEl(els[h], 'span', function(el) {
3581                                 el.className = 'yui-non';
3582                             });
3583                         } else {
3584                             els[h].parentNode.removeChild(els[h]);
3585                         }
3586                     }
3587                 }
3588                 var imgs = this._getDoc().getElementsByTagName('img');
3589                 Dom.addClass(imgs, 'yui-img');
3590             } catch(e) {}
3591         },
3592         /**
3593         * @private
3594         * @method _isNonEditable
3595         * @param Event ev The Dom event being checked
3596         * @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.
3597         * 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
3598         * disable and enable the Editor's toolbar based on the noedit state.
3599         * @return Boolean
3600         */
3601         _isNonEditable: function(ev) {
3602             if (this.get('allowNoEdit')) {
3603                 var el = Event.getTarget(ev);
3604                 if (this._isElement(el, 'html')) {
3605                     el = null;
3606                 }
3607                 var path = this._getDomPath(el);
3608                 for (var i = (path.length - 1); i > -1; i--) {
3609                     if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3610                         //if (this.toolbar.get('disabled') === false) {
3611                         //    this.toolbar.set('disabled', true);
3612                         //}
3613                         try {
3614                              this._getDoc().execCommand('enableObjectResizing', false, 'false');
3615                         } catch (e) {}
3616                         this.nodeChange();
3617                         Event.stopEvent(ev);
3618                         YAHOO.log('CLASS_NOEDIT found in DOM Path, stopping event', 'info', 'SimpleEditor');
3619                         return true;
3620                     }
3621                 }
3622                 //if (this.toolbar.get('disabled') === true) {
3623                     //Should only happen once..
3624                     //this.toolbar.set('disabled', false);
3625                     try {
3626                          this._getDoc().execCommand('enableObjectResizing', false, 'true');
3627                     } catch (e2) {}
3628                 //}
3629             }
3630             return false;
3631         },
3632         /**
3633         * @private
3634         * @method _setCurrentEvent
3635         * @param {Event} ev The event to cache
3636         * @description Sets the current event property
3637         */
3638         _setCurrentEvent: function(ev) {
3639             this.currentEvent = ev;
3640         },
3641         /**
3642         * @private
3643         * @method _handleClick
3644         * @param {Event} ev The event we are working on.
3645         * @description Handles all click events inside the iFrame document.
3646         */
3647         _handleClick: function(ev) {
3648             var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3649             if (ret === false) {
3650                 return false;
3651             }
3652             if (this._isNonEditable(ev)) {
3653                 return false;
3654             }
3655             this._setCurrentEvent(ev);
3656             if (this.currentWindow) {
3657                 this.closeWindow();
3658             }
3659             if (this.currentWindow) {
3660                 this.closeWindow();
3661             }
3662             if (this.browser.webkit) {
3663                 var tar =Event.getTarget(ev);
3664                 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3665                     Event.stopEvent(ev);
3666                     this.nodeChange();
3667                 }
3668             } else {
3669                 this.nodeChange();
3670             }
3671             this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3672         },
3673         /**
3674         * @private
3675         * @method _handleMouseUp
3676         * @param {Event} ev The event we are working on.
3677         * @description Handles all mouseup events inside the iFrame document.
3678         */
3679         _handleMouseUp: function(ev) {
3680             var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3681             if (ret === false) {
3682                 return false;
3683             }
3684             if (this._isNonEditable(ev)) {
3685                 return false;
3686             }
3687             //Don't set current event for mouseup.
3688             //It get's fired after a menu is closed and gives up a bogus event to work with
3689             //this._setCurrentEvent(ev);
3690             var self = this;
3691             if (this.browser.opera) {
3692                 /*
3693                 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3694                 * @browser Opera
3695                 * @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.
3696                 */
3697                 var sel = Event.getTarget(ev);
3698                 if (this._isElement(sel, 'img')) {
3699                     this.nodeChange();
3700                     if (this.operaEvent) {
3701                         clearTimeout(this.operaEvent);
3702                         this.operaEvent = null;
3703                         this._handleDoubleClick(ev);
3704                     } else {
3705                         this.operaEvent = window.setTimeout(function() {
3706                             self.operaEvent = false;
3707                         }, 700);
3708                     }
3709                 }
3710             }
3711             //This will stop Safari from selecting the entire document if you select all the text in the editor
3712             if (this.browser.webkit || this.browser.opera) {
3713                 if (this.browser.webkit) {
3714                     Event.stopEvent(ev);
3715                 }
3716             }
3717             this.nodeChange();
3718             this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3719         },
3720         /**
3721         * @private
3722         * @method _handleMouseDown
3723         * @param {Event} ev The event we are working on.
3724         * @description Handles all mousedown events inside the iFrame document.
3725         */
3726         _handleMouseDown: function(ev) {
3727             var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3728             if (ret === false) {
3729                 return false;
3730             }
3731             if (this._isNonEditable(ev)) {
3732                 return false;
3733             }
3734             this._setCurrentEvent(ev);
3735             var sel = Event.getTarget(ev);
3736             if (this.browser.webkit && this._hasSelection()) {
3737                 var _sel = this._getSelection();
3738                 if (!this.browser.webkit3) {
3739                     _sel.collapse(true);
3740                 } else {
3741                     _sel.collapseToStart();
3742                 }
3743             }
3744             if (this.browser.webkit && this._lastImage) {
3745                 Dom.removeClass(this._lastImage, 'selected');
3746                 this._lastImage = null;
3747             }
3748             if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3749                 if (this.browser.webkit) {
3750                     Event.stopEvent(ev);
3751                     if (this._isElement(sel, 'img')) {
3752                         Dom.addClass(sel, 'selected');
3753                         this._lastImage = sel;
3754                     }
3755                 }
3756                 if (this.currentWindow) {
3757                     this.closeWindow();
3758                 }
3759                 this.nodeChange();
3760             }
3761             this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3762         },
3763         /**
3764         * @private
3765         * @method _handleDoubleClick
3766         * @param {Event} ev The event we are working on.
3767         * @description Handles all doubleclick events inside the iFrame document.
3768         */
3769         _handleDoubleClick: function(ev) {
3770             var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3771             if (ret === false) {
3772                 return false;
3773             }
3774             if (this._isNonEditable(ev)) {
3775                 return false;
3776             }
3777             this._setCurrentEvent(ev);
3778             var sel = Event.getTarget(ev);
3779             if (this._isElement(sel, 'img')) {
3780                 this.currentElement[0] = sel;
3781                 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3782                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3783             } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3784                 this.currentElement[0] = this._hasParent(sel, 'a');
3785                 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3786                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3787             }
3788             this.nodeChange();
3789             this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3790         },
3791         /**
3792         * @private
3793         * @method _handleKeyUp
3794         * @param {Event} ev The event we are working on.
3795         * @description Handles all keyup events inside the iFrame document.
3796         */
3797         _handleKeyUp: function(ev) {
3798             var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3799             if (ret === false) {
3800                 return false;
3801             }
3802             if (this._isNonEditable(ev)) {
3803                 return false;
3804             }
3805             this._storeUndo();
3806             this._setCurrentEvent(ev);
3807             switch (ev.keyCode) {
3808                 case this._keyMap.SELECT_ALL.key:
3809                     if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3810                         this.nodeChange();
3811                     }
3812                     break;
3813                 case 32: //Space Bar
3814                 case 35: //End
3815                 case 36: //Home
3816                 case 37: //Left Arrow
3817                 case 38: //Up Arrow
3818                 case 39: //Right Arrow
3819                 case 40: //Down Arrow
3820                 case 46: //Forward Delete
3821                 case 8: //Delete
3822                 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3823                     if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3824                         if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3825                             this.closeWindow();
3826                         }
3827                     } else {
3828                         if (!this.browser.ie) {
3829                             if (this._nodeChangeTimer) {
3830                                 clearTimeout(this._nodeChangeTimer);
3831                             }
3832                             var self = this;
3833                             this._nodeChangeTimer = setTimeout(function() {
3834                                 self._nodeChangeTimer = null;
3835                                 self.nodeChange.call(self);
3836                             }, 100);
3837                         } else {
3838                             this.nodeChange();
3839                         }
3840                         this.editorDirty = true;
3841                     }
3842                     break;
3843             }
3844             this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3845         },
3846         /**
3847         * @private
3848         * @method _handleKeyPress
3849         * @param {Event} ev The event we are working on.
3850         * @description Handles all keypress events inside the iFrame document.
3851         */
3852         _handleKeyPress: function(ev) {
3853             var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3854             if (ret === false) {
3855                 return false;
3856             }
3857
3858             if (this.get('allowNoEdit')) {
3859                 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3860                 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3861                     //Forward delete key
3862                     YAHOO.log('allowNoEdit is set, forward delete key has been disabled', 'warn', 'SimpleEditor');
3863                     Event.stopEvent(ev);
3864                 }
3865             }
3866             if (this._isNonEditable(ev)) {
3867                 return false;
3868             }
3869             this._setCurrentEvent(ev);
3870             this._storeUndo();
3871             if (this.browser.opera) {
3872                 if (ev.keyCode === 13) {
3873                     var tar = this._getSelectedElement();
3874                     if (!this._isElement(tar, 'li')) {
3875                         this.execCommand('inserthtml', '<br>');
3876                         Event.stopEvent(ev);
3877                     }
3878                 }
3879             }
3880             if (this.browser.webkit) {
3881                 if (!this.browser.webkit3) {
3882                     if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3883                         //This is CMD + z (for undo)
3884                         if (this._hasParent(this._getSelectedElement(), 'li')) {
3885                             YAHOO.log('We are in an LI and we found CMD + z, stopping the event', 'warn', 'SimpleEditor');
3886                             Event.stopEvent(ev);
3887                         }
3888                     }
3889                 }
3890                 this._listFix(ev);
3891             }
3892             this._fixListDupIds();
3893             this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3894         },
3895         /**
3896         * @private
3897         * @method _handleKeyDown
3898         * @param {Event} ev The event we are working on.
3899         * @description Handles all keydown events inside the iFrame document.
3900         */
3901         _handleKeyDown: function(ev) {
3902             var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3903             if (ret === false) {
3904                 return false;
3905             }
3906             var tar = null, _range = null;
3907             if (this._isNonEditable(ev)) {
3908                 return false;
3909             }
3910             this._setCurrentEvent(ev);
3911             if (this.currentWindow) {
3912                 this.closeWindow();
3913             }
3914             if (this.currentWindow) {
3915                 this.closeWindow();
3916             }
3917             var doExec = false,
3918                 action = null,
3919                 value = null,
3920                 exec = false;
3921
3922             //YAHOO.log('keyCode: ' + ev.keyCode, 'info', 'SimpleEditor');
3923
3924             switch (ev.keyCode) {
3925                 case this._keyMap.FOCUS_TOOLBAR.key:
3926                     if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3927                         var h = this.toolbar.getElementsByTagName('h2')[0];
3928                         if (h && h.firstChild) {
3929                             h.firstChild.focus();
3930                         }
3931                     } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3932                         //Focus After Element - Esc
3933                         this.afterElement.focus();
3934                     }
3935                     Event.stopEvent(ev);
3936                     doExec = false;
3937                     break;
3938                 //case 76: //L
3939                 case this._keyMap.CREATE_LINK.key: //L
3940                     if (this._hasSelection()) {
3941                         if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3942                             var makeLink = true;
3943                             if (this.get('limitCommands')) {
3944                                 if (!this.toolbar.getButtonByValue('createlink')) {
3945                                     YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
3946                                     makeLink = false;
3947                                 }
3948                             }
3949                             if (makeLink) {
3950                                 this.execCommand('createlink', '');
3951                                 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3952                                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3953                                 doExec = false;
3954                             }
3955                         }
3956                     }
3957                     break;
3958                 //case 90: //Z
3959                 case this._keyMap.UNDO.key:
3960                 case this._keyMap.REDO.key:
3961                     if (this._checkKey(this._keyMap.REDO, ev)) {
3962                         action = 'redo';
3963                         doExec = true;
3964                     } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3965                         action = 'undo';
3966                         doExec = true;
3967                     }
3968                     break;
3969                 //case 66: //B
3970                 case this._keyMap.BOLD.key:
3971                     if (this._checkKey(this._keyMap.BOLD, ev)) {
3972                         action = 'bold';
3973                         doExec = true;
3974                     }
3975                     break;
3976                 case this._keyMap.FONT_SIZE_UP.key:
3977                 case this._keyMap.FONT_SIZE_DOWN.key:
3978                     var uk = false, dk = false;
3979                     if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3980                         uk = true;
3981                     }
3982                     if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3983                         dk = true;
3984                     }
3985                     if (uk || dk) {
3986                         var fs_button = this.toolbar.getButtonByValue('fontsize'),
3987                             label = parseInt(fs_button.get('label'), 10),
3988                             newValue = (label + 1);
3989
3990                         if (dk) {
3991                             newValue = (label - 1);
3992                         }
3993
3994                         action = 'fontsize';
3995                         value = newValue + 'px';
3996                         doExec = true;
3997                     }
3998                     break;
3999                 //case 73: //I
4000                 case this._keyMap.ITALIC.key:
4001                     if (this._checkKey(this._keyMap.ITALIC, ev)) {
4002                         action = 'italic';
4003                         doExec = true;
4004                     }
4005                     break;
4006                 //case 85: //U
4007                 case this._keyMap.UNDERLINE.key:
4008                     if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
4009                         action = 'underline';
4010                         doExec = true;
4011                     }
4012                     break;
4013                 case 9:
4014                     if (this.browser.ie) {
4015                         //Insert a tab in Internet Explorer
4016                         _range = this._getRange();
4017                         tar = this._getSelectedElement();
4018                         if (!this._isElement(tar, 'li')) {
4019                             if (_range) {
4020                                 _range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
4021                                 _range.collapse(false);
4022                                 _range.select();
4023                             }
4024                             Event.stopEvent(ev);
4025                         }
4026                     }
4027                     //Firefox 3 code
4028                     if (this.browser.gecko > 1.8) {
4029                         tar = this._getSelectedElement();
4030                         if (this._isElement(tar, 'li')) {
4031                             if (ev.shiftKey) {
4032                                 this._getDoc().execCommand('outdent', null, '');
4033                             } else {
4034                                 this._getDoc().execCommand('indent', null, '');
4035                             }
4036                             
4037                         } else if (!this._hasSelection()) {
4038                             this.execCommand('inserthtml', '&nbsp;&nbsp;&nbsp;&nbsp;');
4039                         }
4040                         Event.stopEvent(ev);
4041                     }
4042                     break;
4043                 case 13:
4044                     var p = null, i = 0;
4045                     if (this.get('ptags') && !ev.shiftKey) {
4046                         if (this.browser.gecko) {
4047                             tar = this._getSelectedElement();
4048                             if (!this._hasParent(tar, 'li')) {
4049                                 if (this._hasParent(tar, 'p')) {
4050                                     p = this._getDoc().createElement('p');
4051                                     p.innerHTML = '&nbsp;';
4052                                     Dom.insertAfter(p, tar);
4053                                     this._selectNode(p.firstChild);
4054                                 } else if (this._isElement(tar, 'body')) {
4055                                     this.execCommand('insertparagraph', null);
4056                                     var ps = this._getDoc().body.getElementsByTagName('p');
4057                                     for (i = 0; i < ps.length; i++) {
4058                                         if (ps[i].getAttribute('_moz_dirty') !== null) {
4059                                             p = this._getDoc().createElement('p');
4060                                             p.innerHTML = '&nbsp;';
4061                                             Dom.insertAfter(p, ps[i]);
4062                                             this._selectNode(p.firstChild);
4063                                             ps[i].removeAttribute('_moz_dirty');
4064                                         }
4065                                     }
4066                                 } else {
4067                                     YAHOO.log('Something went wrong with paragraphs, please file a bug!!', 'error', 'SimpleEditor');
4068                                     doExec = true;
4069                                     action = 'insertparagraph';
4070                                 }
4071                                 Event.stopEvent(ev);
4072                             }
4073                         }
4074                         if (this.browser.webkit) {
4075                             tar = this._getSelectedElement();
4076                             if (!this._hasParent(tar, 'li')) {
4077                                 this.execCommand('insertparagraph', null);
4078                                 var divs = this._getDoc().body.getElementsByTagName('div');
4079                                 for (i = 0; i < divs.length; i++) {
4080                                     if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4081                                         Dom.addClass(divs[i], 'yui-wk-p');
4082                                     }
4083                                 }
4084                                 Event.stopEvent(ev);
4085                             }
4086                         }
4087                     } else {
4088                         if (this.browser.webkit) {
4089                             tar = this._getSelectedElement();
4090                             if (!this._hasParent(tar, 'li')) {
4091                                 if (this.browser.webkit4) {
4092                                     this.execCommand('insertlinebreak');
4093                                 } else {
4094                                     this.execCommand('inserthtml', '<var id="yui-br"></var>');
4095                                     var holder = this._getDoc().getElementById('yui-br'),
4096                                         br = this._getDoc().createElement('br'),
4097                                         caret = this._getDoc().createElement('span');
4098
4099                                     holder.parentNode.replaceChild(br, holder);
4100                                     caret.className = 'yui-non';
4101                                     caret.innerHTML = '&nbsp;';
4102                                     Dom.insertAfter(caret, br);
4103                                     this._selectNode(caret);
4104                                 }
4105                                 Event.stopEvent(ev);
4106                             }
4107                         }
4108                         if (this.browser.ie) {
4109                             YAHOO.log('Stopping P tags', 'info', 'SimpleEditor');
4110                             //Insert a <br> instead of a <p></p> in Internet Explorer
4111                             _range = this._getRange();
4112                             tar = this._getSelectedElement();
4113                             if (!this._isElement(tar, 'li')) {
4114                                 if (_range) {
4115                                     _range.pasteHTML('<br>');
4116                                     _range.collapse(false);
4117                                     _range.select();
4118                                 }
4119                                 Event.stopEvent(ev);
4120                             }
4121                         }
4122                     }
4123                     break;
4124             }
4125             if (this.browser.ie) {
4126                 this._listFix(ev);
4127             }
4128             if (doExec && action) {
4129                 this.execCommand(action, value);
4130                 Event.stopEvent(ev);
4131                 this.nodeChange();
4132             }
4133             this._storeUndo();
4134             this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4135         },
4136         /**
4137         * @private
4138         * @property _fixListRunning
4139         * @type Boolean
4140         * @description Keeps more than one _fixListDupIds from running at the same time.
4141         */
4142         _fixListRunning: null,
4143         /**
4144         * @private
4145         * @method _fixListDupIds
4146         * @description Some browsers will duplicate the id of an LI when created in designMode.
4147         * This method will fix the duplicate id issue. However it will only preserve the first element 
4148         * in the document list with the unique id. 
4149         */
4150         _fixListDupIds: function() {
4151             if (this._fixListRunning) {
4152                 return false;
4153             }
4154             if (this._getDoc()) {
4155                 this._fixListRunning = true;
4156                 var lis = this._getDoc().body.getElementsByTagName('li'),
4157                     i = 0, ids = {};
4158                 for (i = 0; i < lis.length; i++) {
4159                     if (lis[i].id) {
4160                         if (ids[lis[i].id]) {
4161                             lis[i].id = '';
4162                         }
4163                         ids[lis[i].id] = true;
4164                     }
4165                 }
4166                 this._fixListRunning = false;
4167             }
4168         },
4169         /**
4170         * @private
4171         * @method _listFix
4172         * @param {Event} ev The event we are working on.
4173         * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4174         */
4175         _listFix: function(ev) {
4176             //YAHOO.log('Lists Fix (' + ev.keyCode + ')', 'info', 'SimpleEditor');
4177             var testLi = null, par = null, preContent = false, range = null;
4178             //Enter Key
4179             if (this.browser.webkit) {
4180                 if (ev.keyCode && (ev.keyCode == 13)) {
4181                     if (this._hasParent(this._getSelectedElement(), 'li')) {
4182                         var tar = this._hasParent(this._getSelectedElement(), 'li');
4183                         if (tar.previousSibling) {
4184                             if (tar.firstChild && (tar.firstChild.length == 1)) {
4185                                 this._selectNode(tar);
4186                             }
4187                         }
4188                     }
4189                 }
4190             }
4191             //Shift + Tab Key
4192             if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4193                 testLi = this._getSelectedElement();
4194                 if (this._hasParent(testLi, 'li')) {
4195                     testLi = this._hasParent(testLi, 'li');
4196                     YAHOO.log('We have a SHIFT tab in an LI, reverse it..', 'info', 'SimpleEditor');
4197                     if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4198                         YAHOO.log('We have a double parent, move up a level', 'info', 'SimpleEditor');
4199                         par = this._hasParent(testLi, 'ul');
4200                         if (!par) {
4201                             par = this._hasParent(testLi, 'ol');
4202                         }
4203                         //YAHOO.log(par.previousSibling + ' :: ' + par.previousSibling.innerHTML);
4204                         if (this._isElement(par.previousSibling, 'li')) {
4205                             par.removeChild(testLi);
4206                             par.parentNode.insertBefore(testLi, par.nextSibling);
4207                             if (this.browser.ie) {
4208                                 range = this._getDoc().body.createTextRange();
4209                                 range.moveToElementText(testLi);
4210                                 range.collapse(false);
4211                                 range.select();
4212                             }
4213                             if (this.browser.webkit) {
4214                                 this._selectNode(testLi.firstChild);
4215                             }
4216                             Event.stopEvent(ev);
4217                         }
4218                     }
4219                 }
4220             }
4221             //Tab Key
4222             if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4223                 YAHOO.log('List Fix - Tab', 'info', 'SimpleEditor');
4224                 var preLi = this._getSelectedElement();
4225                 if (this._hasParent(preLi, 'li')) {
4226                     preContent = this._hasParent(preLi, 'li').innerHTML;
4227                 }
4228                 //YAHOO.log('preLI: ' + preLi.tagName + ' :: ' + preLi.innerHTML);
4229                 if (this.browser.webkit) {
4230                     this._getDoc().execCommand('inserttext', false, '\t');
4231                 }
4232                 testLi = this._getSelectedElement();
4233                 if (this._hasParent(testLi, 'li')) {
4234                     YAHOO.log('We have a tab in an LI', 'info', 'SimpleEditor');
4235                     par = this._hasParent(testLi, 'li');
4236                     YAHOO.log('parLI: ' + par.tagName + ' :: ' + par.innerHTML);
4237                     var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4238                     if (this.browser.webkit) {
4239                         var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4240                         //Remove the span element that Safari puts in
4241                         if (span[0]) {
4242                             par.removeChild(span[0]);
4243                             par.innerHTML = Lang.trim(par.innerHTML);
4244                             //Put the HTML from the LI into this new LI
4245                             if (preContent) {
4246                                 par.innerHTML = '<span class="yui-non">' + preContent + '</span>&nbsp;';
4247                             } else {
4248                                 par.innerHTML = '<span class="yui-non">&nbsp;</span>&nbsp;';
4249                             }
4250                         }
4251                     } else {
4252                         if (preContent) {
4253                             par.innerHTML = preContent + '&nbsp;';
4254                         } else {
4255                             par.innerHTML = '&nbsp;';
4256                         }
4257                     }
4258
4259                     par.parentNode.replaceChild(newUl, par);
4260                     newUl.appendChild(par);
4261                     if (this.browser.webkit) {
4262                         this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4263                         if (!this.browser.webkit3) {
4264                             par.parentNode.parentNode.style.display = 'list-item';
4265                             setTimeout(function() {
4266                                 par.parentNode.parentNode.style.display = 'block';
4267                             }, 1);
4268                         }
4269                     } else if (this.browser.ie) {
4270                         range = this._getDoc().body.createTextRange();
4271                         range.moveToElementText(par);
4272                         range.collapse(false);
4273                         range.select();
4274                     } else {
4275                         this._selectNode(par);
4276                     }
4277                     Event.stopEvent(ev);
4278                 }
4279                 if (this.browser.webkit) {
4280                     Event.stopEvent(ev);
4281                 }
4282                 this.nodeChange();
4283             }
4284         },
4285         /**
4286         * @method nodeChange
4287         * @param {Boolean} force Optional paramenter to skip the threshold counter
4288         * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4289         */
4290         nodeChange: function(force) {
4291             var NCself = this;
4292             this._storeUndo();
4293             if (this.get('nodeChangeDelay')) {
4294                 this._nodeChangeDelayTimer = window.setTimeout(function() {
4295                     NCself._nodeChangeDelayTimer = null;
4296                     NCself._nodeChange.apply(NCself, arguments);
4297                 }, 0);
4298             } else {
4299                 this._nodeChange();
4300             }
4301         },
4302         /**
4303         * @private
4304         * @method _nodeChange
4305         * @param {Boolean} force Optional paramenter to skip the threshold counter
4306         * @description Fired from nodeChange in a setTimeout.
4307         */
4308         _nodeChange: function(force) {
4309             var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4310                 thisNodeChange = Math.round(new Date().getTime() / 1000),
4311                 self = this;
4312
4313             if (force === true) {
4314                 this._lastNodeChange = 0;
4315             }
4316             
4317             if ((this._lastNodeChange + threshold) < thisNodeChange) {
4318                 if (this._fixNodesTimer === null) {
4319                     this._fixNodesTimer = window.setTimeout(function() {
4320                         self._fixNodes.call(self);
4321                         self._fixNodesTimer = null;
4322                     }, 0);
4323                 }
4324             }
4325             this._lastNodeChange = thisNodeChange;
4326             if (this.currentEvent) {
4327                 try {
4328                     this._lastNodeChangeEvent = this.currentEvent.type;
4329                 } catch (e) {}
4330             }
4331
4332             var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4333             if (beforeNodeChange === false) {
4334                 return false;
4335             }
4336             if (this.get('dompath')) {
4337                 window.setTimeout(function() {
4338                     self._writeDomPath.call(self);
4339                 }, 0);
4340             }
4341             //Check to see if we are disabled before continuing
4342             if (!this.get('disabled')) {
4343                 if (this.STOP_NODE_CHANGE) {
4344                     //Reset this var for next action
4345                     this.STOP_NODE_CHANGE = false;
4346                     return false;
4347                 } else {
4348                     var sel = this._getSelection(),
4349                         range = this._getRange(),
4350                         el = this._getSelectedElement(),
4351                         fn_button = this.toolbar.getButtonByValue('fontname'),
4352                         fs_button = this.toolbar.getButtonByValue('fontsize'),
4353                         undo_button = this.toolbar.getButtonByValue('undo'),
4354                         redo_button = this.toolbar.getButtonByValue('redo');
4355
4356                     //Handle updating the toolbar with active buttons
4357                     var _ex = {};
4358                     if (this._lastButton) {
4359                         _ex[this._lastButton.id] = true;
4360                         //this._lastButton = null;
4361                     }
4362                     if (!this._isElement(el, 'body')) {
4363                         if (fn_button) {
4364                             _ex[fn_button.get('id')] = true;
4365                         }
4366                         if (fs_button) {
4367                             _ex[fs_button.get('id')] = true;
4368                         }
4369                     }
4370                     if (redo_button) {
4371                         delete _ex[redo_button.get('id')];
4372                     }
4373                     this.toolbar.resetAllButtons(_ex);
4374
4375                     //Handle disabled buttons
4376                     for (var d = 0; d < this._disabled.length; d++) {
4377                         var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4378                         if (_button && _button.get) {
4379                             if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4380                                 //Skip
4381                             } else {
4382                                 if (!this._hasSelection() && !this.get('insert')) {
4383                                     switch (this._disabled[d]) {
4384                                         case 'fontname':
4385                                         case 'fontsize':
4386                                             break;
4387                                         default:
4388                                             //No Selection - disable
4389                                             this.toolbar.disableButton(_button);
4390                                     }
4391                                 } else {
4392                                     if (!this._alwaysDisabled[this._disabled[d]]) {
4393                                         this.toolbar.enableButton(_button);
4394                                     }
4395                                 }
4396                                 if (!this._alwaysEnabled[this._disabled[d]]) {
4397                                     this.toolbar.deselectButton(_button);
4398                                 }
4399                             }
4400                         }
4401                     }
4402                     var path = this._getDomPath();
4403                     var tag = null, cmd = null;
4404                     for (var i = 0; i < path.length; i++) {
4405                         tag = path[i].tagName.toLowerCase();
4406                         if (path[i].getAttribute('tag')) {
4407                             tag = path[i].getAttribute('tag').toLowerCase();
4408                         }
4409                         cmd = this._tag2cmd[tag];
4410                         if (cmd === undefined) {
4411                             cmd = [];
4412                         }
4413                         if (!Lang.isArray(cmd)) {
4414                             cmd = [cmd];
4415                         }
4416
4417                         //Bold and Italic styles
4418                         if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4419                             cmd[cmd.length] = 'bold';
4420                         }
4421                         if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4422                             cmd[cmd.length] = 'italic';
4423                         }
4424                         if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4425                             cmd[cmd.length] = 'underline';
4426                         }
4427                         if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4428                             cmd[cmd.length] = 'strikethrough';
4429                         }
4430                         if (cmd.length > 0) {
4431                             for (var j = 0; j < cmd.length; j++) {
4432                                 this.toolbar.selectButton(cmd[j]);
4433                                 this.toolbar.enableButton(cmd[j]);
4434                             }
4435                         }
4436                         //Handle Alignment
4437                         switch (path[i].style.textAlign.toLowerCase()) {
4438                             case 'left':
4439                             case 'right':
4440                             case 'center':
4441                             case 'justify':
4442                                 var alignType = path[i].style.textAlign.toLowerCase();
4443                                 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4444                                     alignType = 'full';
4445                                 }
4446                                 this.toolbar.selectButton('justify' + alignType);
4447                                 this.toolbar.enableButton('justify' + alignType);
4448                                 break;
4449                         }
4450                     }
4451                     //After for loop
4452
4453                     //Reset Font Family and Size to the inital configs
4454                     if (fn_button) {
4455                         var family = fn_button._configs.label._initialConfig.value;
4456                         fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4457                         this._updateMenuChecked('fontname', family);
4458                     }
4459
4460                     if (fs_button) {
4461                         fs_button.set('label', fs_button._configs.label._initialConfig.value);
4462                     }
4463
4464                     var hd_button = this.toolbar.getButtonByValue('heading');
4465                     if (hd_button) {
4466                         hd_button.set('label', hd_button._configs.label._initialConfig.value);
4467                         this._updateMenuChecked('heading', 'none');
4468                     }
4469                     var img_button = this.toolbar.getButtonByValue('insertimage');
4470                     if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4471                         this.toolbar.disableButton(img_button);
4472                     }
4473                     if (this._lastButton && this._lastButton.isSelected) {
4474                         this.toolbar.deselectButton(this._lastButton.id);
4475                     }
4476                     this._undoNodeChange();
4477                 }
4478             }
4479
4480             this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4481         },
4482         /**
4483         * @private
4484         * @method _updateMenuChecked
4485         * @param {Object} button The command identifier of the button you want to check
4486         * @param {String} value The value of the menu item you want to check
4487         * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar) 
4488         * @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.
4489         */
4490         _updateMenuChecked: function(button, value, tbar) {
4491             if (!tbar) {
4492                 tbar = this.toolbar;
4493             }
4494             var _button = tbar.getButtonByValue(button);
4495             _button.checkValue(value);
4496         },
4497         /**
4498         * @private
4499         * @method _handleToolbarClick
4500         * @param {Event} ev The event that triggered the button click
4501         * @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.
4502         */
4503         _handleToolbarClick: function(ev) {
4504             var value = '';
4505             var str = '';
4506             var cmd = ev.button.value;
4507             if (ev.button.menucmd) {
4508                 value = cmd;
4509                 cmd = ev.button.menucmd;
4510             }
4511             this._lastButton = ev.button;
4512             if (this.STOP_EXEC_COMMAND) {
4513                 YAHOO.log('execCommand skipped because we found the STOP_EXEC_COMMAND flag set to true', 'warn', 'SimpleEditor');
4514                 YAHOO.log('NOEXEC::execCommand::(' + cmd + '), (' + value + ')', 'warn', 'SimpleEditor');
4515                 this.STOP_EXEC_COMMAND = false;
4516                 return false;
4517             } else {
4518                 this.execCommand(cmd, value);
4519                 if (!this.browser.webkit) {
4520                      var Fself = this;
4521                      setTimeout(function() {
4522                          Fself.focus.call(Fself);
4523                      }, 5);
4524                  }
4525             }
4526             Event.stopEvent(ev);
4527         },
4528         /**
4529         * @private
4530         * @method _setupAfterElement
4531         * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4532         */
4533         _setupAfterElement: function() {
4534             if (!this.beforeElement) {
4535                 this.beforeElement = document.createElement('h2');
4536                 this.beforeElement.className = 'yui-editor-skipheader';
4537                 this.beforeElement.tabIndex = '-1';
4538                 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4539                 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4540             }
4541             if (!this.afterElement) {
4542                 this.afterElement = document.createElement('h2');
4543                 this.afterElement.className = 'yui-editor-skipheader';
4544                 this.afterElement.tabIndex = '-1';
4545                 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4546                 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4547             }
4548         },
4549         /**
4550         * @private
4551         * @method _disableEditor
4552         * @param {Boolean} disabled Pass true to disable, false to enable
4553         * @description Creates a mask to place over the Editor.
4554         */
4555         _disableEditor: function(disabled) {
4556             var iframe, par, html, height;
4557             if (!this.get('disabled_iframe')) {
4558                 iframe = this._createIframe();
4559                 iframe.set('id', 'disabled_' + this.get('iframe').get('id'));
4560                 iframe.setStyle('height', '100%');
4561                 iframe.setStyle('display', 'none');
4562                 iframe.setStyle('visibility', 'visible');
4563                 this.set('disabled_iframe', iframe);
4564                 par = this.get('iframe').get('parentNode');
4565                 par.appendChild(iframe.get('element'));
4566             }
4567             if (!iframe) {
4568                 iframe = this.get('disabled_iframe');
4569             }
4570             if (disabled) {
4571                 this._orgIframe = this.get('iframe');
4572
4573                 if (this.toolbar) {
4574                     this.toolbar.set('disabled', true);
4575                 }
4576
4577                 html = this.getEditorHTML();
4578                 height = this.get('iframe').get('offsetHeight');
4579                 iframe.setStyle('visibility', '');
4580                 iframe.setStyle('position', '');
4581                 iframe.setStyle('top', '');
4582                 iframe.setStyle('left', '');
4583                 this._orgIframe.setStyle('visibility', 'hidden');
4584                 this._orgIframe.setStyle('position', 'absolute');
4585                 this._orgIframe.setStyle('top', '-99999px');
4586                 this._orgIframe.setStyle('left', '-99999px');
4587                 this.set('iframe', iframe);
4588                 this._setInitialContent(true);
4589                 
4590                 if (!this._mask) {
4591                     this._mask = document.createElement('DIV');
4592                     Dom.addClass(this._mask, 'yui-editor-masked');
4593                     if (this.browser.ie) {
4594                         this._mask.style.height = height + 'px';
4595                     }
4596                     this.get('iframe').get('parentNode').appendChild(this._mask);
4597                 }
4598                 this.on('editorContentReloaded', function() {
4599                     this._getDoc().body._rteLoaded = false;
4600                     this.setEditorHTML(html);
4601                     iframe.setStyle('display', 'block');
4602                     this.unsubscribeAll('editorContentReloaded');
4603                 });
4604             } else {
4605                 if (this._mask) {
4606                     this._mask.parentNode.removeChild(this._mask);
4607                     this._mask = null;
4608                     if (this.toolbar) {
4609                         this.toolbar.set('disabled', false);
4610                     }
4611                     iframe.setStyle('visibility', 'hidden');
4612                     iframe.setStyle('position', 'absolute');
4613                     iframe.setStyle('top', '-99999px');
4614                     iframe.setStyle('left', '-99999px');
4615                     this._orgIframe.setStyle('visibility', '');
4616                     this._orgIframe.setStyle('position', '');
4617                     this._orgIframe.setStyle('top', '');
4618                     this._orgIframe.setStyle('left', '');
4619                     this.set('iframe', this._orgIframe);
4620
4621                     this.focus();
4622                     var self = this;
4623                     window.setTimeout(function() {
4624                         self.nodeChange.call(self);
4625                     }, 100);
4626                 }
4627             }
4628         },
4629         /**
4630         * @property SEP_DOMPATH
4631         * @description The value to place in between the Dom path items
4632         * @type String
4633         */
4634         SEP_DOMPATH: '<',
4635         /**
4636         * @property STR_LEAVE_EDITOR
4637         * @description The accessibility string for the element after the iFrame
4638         * @type String
4639         */
4640         STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4641         /**
4642         * @property STR_BEFORE_EDITOR
4643         * @description The accessibility string for the element before the iFrame
4644         * @type String
4645         */
4646         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>',
4647         /**
4648         * @property STR_TITLE
4649         * @description The Title of the HTML document that is created in the iFrame
4650         * @type String
4651         */
4652         STR_TITLE: 'Rich Text Area.',
4653         /**
4654         * @property STR_IMAGE_HERE
4655         * @description The text to place in the URL textbox when using the blankimage.
4656         * @type String
4657         */
4658         STR_IMAGE_HERE: 'Image URL Here',
4659         /**
4660         * @property STR_IMAGE_URL
4661         * @description The label string for Image URL
4662         * @type String
4663         */
4664         STR_IMAGE_URL: 'Image URL',        
4665         /**
4666         * @property STR_LINK_URL
4667         * @description The label string for the Link URL.
4668         * @type String
4669         */
4670         STR_LINK_URL: 'Link URL',
4671         /**
4672         * @protected
4673         * @property STOP_EXEC_COMMAND
4674         * @description Set to true when you want the default execCommand function to not process anything
4675         * @type Boolean
4676         */
4677         STOP_EXEC_COMMAND: false,
4678         /**
4679         * @protected
4680         * @property STOP_NODE_CHANGE
4681         * @description Set to true when you want the default nodeChange function to not process anything
4682         * @type Boolean
4683         */
4684         STOP_NODE_CHANGE: false,
4685         /**
4686         * @protected
4687         * @property CLASS_NOEDIT
4688         * @description CSS class applied to elements that are not editable.
4689         * @type String
4690         */
4691         CLASS_NOEDIT: 'yui-noedit',
4692         /**
4693         * @protected
4694         * @property CLASS_CONTAINER
4695         * @description Default CSS class to apply to the editors container element
4696         * @type String
4697         */
4698         CLASS_CONTAINER: 'yui-editor-container',
4699         /**
4700         * @protected
4701         * @property CLASS_EDITABLE
4702         * @description Default CSS class to apply to the editors iframe element
4703         * @type String
4704         */
4705         CLASS_EDITABLE: 'yui-editor-editable',
4706         /**
4707         * @protected
4708         * @property CLASS_EDITABLE_CONT
4709         * @description Default CSS class to apply to the editors iframe's parent element
4710         * @type String
4711         */
4712         CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4713         /**
4714         * @protected
4715         * @property CLASS_PREFIX
4716         * @description Default prefix for dynamically created class names
4717         * @type String
4718         */
4719         CLASS_PREFIX: 'yui-editor',
4720         /** 
4721         * @property browser
4722         * @description Standard browser detection
4723         * @type Object
4724         */
4725         browser: function() {
4726             var br = YAHOO.env.ua;
4727             //Check for webkit3
4728             if (br.webkit >= 420) {
4729                 br.webkit3 = br.webkit;
4730             } else {
4731                 br.webkit3 = 0;
4732             }
4733             if (br.webkit >= 530) {
4734                 br.webkit4 = br.webkit;
4735             } else {
4736                 br.webkit4 = 0;
4737             }
4738             br.mac = false;
4739             //Check for Mac
4740             if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4741                 br.mac = true;
4742             }
4743
4744             return br;
4745         }(),
4746         /** 
4747         * @method init
4748         * @description The Editor class' initialization method
4749         */
4750         init: function(p_oElement, p_oAttributes) {
4751             YAHOO.log('init', 'info', 'SimpleEditor');
4752
4753             if (!this._defaultToolbar) {
4754                 this._defaultToolbar = {
4755                     collapse: true,
4756                     titlebar: 'Text Editing Tools',
4757                     draggable: false,
4758                     buttons: [
4759                         { group: 'fontstyle', label: 'Font Name and Size',
4760                             buttons: [
4761                                 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4762                                     menu: [
4763                                         { text: 'Arial', checked: true },
4764                                         { text: 'Arial Black' },
4765                                         { text: 'Comic Sans MS' },
4766                                         { text: 'Courier New' },
4767                                         { text: 'Lucida Console' },
4768                                         { text: 'Tahoma' },
4769                                         { text: 'Times New Roman' },
4770                                         { text: 'Trebuchet MS' },
4771                                         { text: 'Verdana' }
4772                                     ]
4773                                 },
4774                                 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4775                             ]
4776                         },
4777                         { type: 'separator' },
4778                         { group: 'textstyle', label: 'Font Style',
4779                             buttons: [
4780                                 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4781                                 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4782                                 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4783                                 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4784                                 { type: 'separator' },
4785                                 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4786                                 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4787                                 
4788                             ]
4789                         },
4790                         { type: 'separator' },
4791                         { group: 'indentlist', label: 'Lists',
4792                             buttons: [
4793                                 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4794                                 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4795                             ]
4796                         },
4797                         { type: 'separator' },
4798                         { group: 'insertitem', label: 'Insert Item',
4799                             buttons: [
4800                                 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4801                                 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4802                             ]
4803                         }
4804                     ]
4805                 };
4806             }
4807
4808             YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4809             YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4810
4811
4812             this.currentElement = [];
4813             this.on('contentReady', function() {
4814                 this.DOMReady = true;
4815                 this.fireQueue();
4816             }, this, true);
4817
4818         },
4819         /**
4820         * @method initAttributes
4821         * @description Initializes all of the configuration attributes used to create 
4822         * the editor.
4823         * @param {Object} attr Object literal specifying a set of 
4824         * configuration attributes used to create the editor.
4825         */
4826         initAttributes: function(attr) {
4827             YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4828             var self = this;
4829
4830             /**
4831             * @config setDesignMode
4832             * @description Should the Editor set designMode on the document. Default: true.
4833             * @default true
4834             * @type Boolean
4835             */
4836             this.setAttributeConfig('setDesignMode', {
4837                 value: ((attr.setDesignMode === false) ? false : true)
4838             });
4839             /**
4840             * @config nodeChangeDelay
4841             * @description Do we wrap the nodeChange method in a timeout for performance. Default: true.
4842             * @default true
4843             * @type Boolean
4844             */
4845             this.setAttributeConfig('nodeChangeDelay', {
4846                 value: ((attr.nodeChangeDelay === false) ? false : true)
4847             });
4848             /**
4849             * @config maxUndo
4850             * @description The max number of undo levels to store.
4851             * @default 30
4852             * @type Number
4853             */
4854             this.setAttributeConfig('maxUndo', {
4855                 writeOnce: true,
4856                 value: attr.maxUndo || 30
4857             });
4858
4859             /**
4860             * @config ptags
4861             * @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;)
4862             * @default false
4863             * @type Boolean
4864             */
4865             this.setAttributeConfig('ptags', {
4866                 writeOnce: true,
4867                 value: attr.ptags || false
4868             });
4869             /**
4870             * @config insert
4871             * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4872             * @default false
4873             * @type Boolean
4874             */
4875             this.setAttributeConfig('insert', {
4876                 writeOnce: true,
4877                 value: attr.insert || false,
4878                 method: function(insert) {
4879                     if (insert) {
4880                         var buttons = {
4881                             fontname: true,
4882                             fontsize: true,
4883                             forecolor: true,
4884                             backcolor: true
4885                         };
4886                         var tmp = this._defaultToolbar.buttons;
4887                         for (var i = 0; i < tmp.length; i++) {
4888                             if (tmp[i].buttons) {
4889                                 for (var a = 0; a < tmp[i].buttons.length; a++) {
4890                                     if (tmp[i].buttons[a].value) {
4891                                         if (buttons[tmp[i].buttons[a].value]) {
4892                                             delete tmp[i].buttons[a].disabled;
4893                                         }
4894                                     }
4895                                 }
4896                             }
4897                         }
4898                     }
4899                 }
4900             });
4901             /**
4902             * @config container
4903             * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4904             * We will create one and place it in this container. If no container is passed we will append to document.body.
4905             * @default false
4906             * @type HTMLElement
4907             */
4908             this.setAttributeConfig('container', {
4909                 writeOnce: true,
4910                 value: attr.container || false
4911             });
4912             /**
4913             * @config plainText
4914             * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4915             * @default false
4916             * @type Boolean
4917             */
4918             this.setAttributeConfig('plainText', {
4919                 writeOnce: true,
4920                 value: attr.plainText || false
4921             });
4922             /**
4923             * @private
4924             * @config iframe
4925             * @description Internal config for holding the iframe element.
4926             * @default null
4927             * @type HTMLElement
4928             */
4929             this.setAttributeConfig('iframe', {
4930                 value: null
4931             });
4932             /**
4933             * @private
4934             * @config disabled_iframe
4935             * @description Internal config for holding the iframe element used when disabling the Editor.
4936             * @default null
4937             * @type HTMLElement
4938             */
4939             this.setAttributeConfig('disabled_iframe', {
4940                 value: null
4941             });
4942             /**
4943             * @private
4944             * @depreciated - No longer used, should use this.get('element')
4945             * @config textarea
4946             * @description Internal config for holding the textarea element (replaced with element).
4947             * @default null
4948             * @type HTMLElement
4949             */
4950             this.setAttributeConfig('textarea', {
4951                 value: null,
4952                 writeOnce: true
4953             });
4954             /**
4955             * @config nodeChangeThreshold
4956             * @description The number of seconds that need to be in between nodeChange processing
4957             * @default 3
4958             * @type Number
4959             */            
4960             this.setAttributeConfig('nodeChangeThreshold', {
4961                 value: attr.nodeChangeThreshold || 3,
4962                 validator: YAHOO.lang.isNumber
4963             });
4964             /**
4965             * @config allowNoEdit
4966             * @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.
4967             * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4968             * @default false
4969             * @type Boolean
4970             */            
4971             this.setAttributeConfig('allowNoEdit', {
4972                 value: attr.allowNoEdit || false,
4973                 validator: YAHOO.lang.isBoolean
4974             });
4975             /**
4976             * @config limitCommands
4977             * @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.
4978             * @default false
4979             * @type Boolean
4980             */            
4981             this.setAttributeConfig('limitCommands', {
4982                 value: attr.limitCommands || false,
4983                 validator: YAHOO.lang.isBoolean
4984             });
4985             /**
4986             * @config element_cont
4987             * @description Internal config for the editors container
4988             * @default false
4989             * @type HTMLElement
4990             */
4991             this.setAttributeConfig('element_cont', {
4992                 value: attr.element_cont
4993             });
4994             /**
4995             * @private
4996             * @config editor_wrapper
4997             * @description The outter wrapper for the entire editor.
4998             * @default null
4999             * @type HTMLElement
5000             */
5001             this.setAttributeConfig('editor_wrapper', {
5002                 value: attr.editor_wrapper || null,
5003                 writeOnce: true
5004             });
5005             /**
5006             * @attribute height
5007             * @description The height of the editor iframe container, not including the toolbar..
5008             * @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
5009             * @type String
5010             */
5011             this.setAttributeConfig('height', {
5012                 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
5013                 method: function(height) {
5014                     if (this._rendered) {
5015                         //We have been rendered, change the height
5016                         if (this.get('animate')) {
5017                             var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
5018                                 height: {
5019                                     to: parseInt(height, 10)
5020                                 }
5021                             }, 0.5);
5022                             anim.animate();
5023                         } else {
5024                             Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
5025                         }
5026                     }
5027                 }
5028             });
5029             /**
5030             * @config autoHeight
5031             * @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.
5032             * @default false
5033             * @type Boolean || Number
5034             */
5035             this.setAttributeConfig('autoHeight', {
5036                 value: attr.autoHeight || false,
5037                 method: function(a) {
5038                     if (a) {
5039                         if (this.get('iframe')) {
5040                             this.get('iframe').get('element').setAttribute('scrolling', 'no');
5041                         }
5042                         this.on('afterNodeChange', this._handleAutoHeight, this, true);
5043                         this.on('editorKeyDown', this._handleAutoHeight, this, true);
5044                         this.on('editorKeyPress', this._handleAutoHeight, this, true);
5045                     } else {
5046                         if (this.get('iframe')) {
5047                             this.get('iframe').get('element').setAttribute('scrolling', 'auto');
5048                         }
5049                         this.unsubscribe('afterNodeChange', this._handleAutoHeight);
5050                         this.unsubscribe('editorKeyDown', this._handleAutoHeight);
5051                         this.unsubscribe('editorKeyPress', this._handleAutoHeight);
5052                     }
5053                 }
5054             });
5055             /**
5056             * @attribute width
5057             * @description The width of the editor container.
5058             * @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
5059             * @type String
5060             */            
5061             this.setAttributeConfig('width', {
5062                 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
5063                 method: function(width) {
5064                     if (this._rendered) {
5065                         //We have been rendered, change the width
5066                         if (this.get('animate')) {
5067                             var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
5068                                 width: {
5069                                     to: parseInt(width, 10)
5070                                 }
5071                             }, 0.5);
5072                             anim.animate();
5073                         } else {
5074                             this.get('element_cont').setStyle('width', width);
5075                         }
5076                     }
5077                 }
5078             });
5079                         
5080             /**
5081             * @attribute blankimage
5082             * @description The URL for the image placeholder to put in when inserting an image.
5083             * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
5084             * @type String
5085             */            
5086             this.setAttributeConfig('blankimage', {
5087                 value: attr.blankimage || this._getBlankImage()
5088             });
5089             /**
5090             * @attribute css
5091             * @description The Base CSS used to format the content of the editor
5092             * @default <code><pre>html {
5093                 height: 95%;
5094             }
5095             body {
5096                 height: 100%;
5097                 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
5098             }
5099             a {
5100                 color: blue;
5101                 text-decoration: underline;
5102                 cursor: pointer;
5103             }
5104             .warning-localfile {
5105                 border-bottom: 1px dashed red !important;
5106             }
5107             .yui-busy {
5108                 cursor: wait !important;
5109             }
5110             img.selected { //Safari image selection
5111                 border: 2px dotted #808080;
5112             }
5113             img {
5114                 cursor: pointer !important;
5115                 border: none;
5116             }
5117             </pre></code>
5118             * @type String
5119             */            
5120             this.setAttributeConfig('css', {
5121                 value: attr.css || this._defaultCSS,
5122                 writeOnce: true
5123             });
5124             /**
5125             * @attribute html
5126             * @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)
5127             * @default This HTML requires a few things if you are to override:
5128                 <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>
5129                 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
5130                 <code>
5131                 <pre>
5132                 &lt;html&gt;
5133                     &lt;head&gt;
5134                         &lt;title&gt;{TITLE}&lt;/title&gt;
5135                         &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
5136                         &lt;style&gt;
5137                         {CSS}
5138                         &lt;/style&gt;
5139                         &lt;style&gt;
5140                         {HIDDEN_CSS}
5141                         &lt;/style&gt;
5142                         &lt;style&gt;
5143                         {EXTRA_CSS}
5144                         &lt;/style&gt;
5145                     &lt;/head&gt;
5146                 &lt;body onload="document.body._rteLoaded = true;"&gt;
5147                 {CONTENT}
5148                 &lt;/body&gt;
5149                 &lt;/html&gt;
5150                 </pre>
5151                 </code>
5152             * @type String
5153             */            
5154             this.setAttributeConfig('html', {
5155                 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>',
5156                 writeOnce: true
5157             });
5158
5159             /**
5160             * @attribute extracss
5161             * @description Extra user defined css to load after the default SimpleEditor CSS
5162             * @default ''
5163             * @type String
5164             */            
5165             this.setAttributeConfig('extracss', {
5166                 value: attr.extracss || '',
5167                 writeOnce: true
5168             });
5169
5170             /**
5171             * @attribute handleSubmit
5172             * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
5173             If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
5174             Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
5175             * @default false
5176             * @type Boolean
5177             */            
5178             this.setAttributeConfig('handleSubmit', {
5179                 value: attr.handleSubmit || false,
5180                 method: function(exec) {
5181                     if (this.get('element').form) {
5182                         if (!this._formButtons) {
5183                             this._formButtons = [];
5184                         }
5185                         if (exec) {
5186                             Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5187                             var i = this.get('element').form.getElementsByTagName('input');
5188                             for (var s = 0; s < i.length; s++) {
5189                                 var type = i[s].getAttribute('type');
5190                                 if (type && (type.toLowerCase() == 'submit')) {
5191                                     Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5192                                     this._formButtons[this._formButtons.length] = i[s];
5193                                 }
5194                             }
5195                         } else {
5196                             Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5197                             if (this._formButtons) {
5198                                 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5199                             }
5200                         }
5201                     }
5202                 }
5203             });
5204             /**
5205             * @attribute disabled
5206             * @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.
5207             All Toolbar buttons are also disabled so they cannot be used.
5208             * @default false
5209             * @type Boolean
5210             */
5211
5212             this.setAttributeConfig('disabled', {
5213                 value: false,
5214                 method: function(disabled) {
5215                     if (this._rendered) {
5216                         this._disableEditor(disabled);
5217                     }
5218                 }
5219             });
5220             /**
5221             * @config saveEl
5222             * @description When save HTML is called, this element will be updated as well as the source of data.
5223             * @default element
5224             * @type HTMLElement
5225             */
5226             this.setAttributeConfig('saveEl', {
5227                 value: this.get('element')
5228             });
5229             /**
5230             * @config toolbar_cont
5231             * @description Internal config for the toolbars container
5232             * @default false
5233             * @type Boolean
5234             */
5235             this.setAttributeConfig('toolbar_cont', {
5236                 value: null,
5237                 writeOnce: true
5238             });
5239             /**
5240             * @attribute toolbar
5241             * @description The default toolbar config.
5242             * @type Object
5243             */            
5244             this.setAttributeConfig('toolbar', {
5245                 value: attr.toolbar || this._defaultToolbar,
5246                 writeOnce: true,
5247                 method: function(toolbar) {
5248                     if (!toolbar.buttonType) {
5249                         toolbar.buttonType = this._defaultToolbar.buttonType;
5250                     }
5251                     this._defaultToolbar = toolbar;
5252                 }
5253             });
5254             /**
5255             * @attribute animate
5256             * @description Should the editor animate window movements
5257             * @default false unless Animation is found, then true
5258             * @type Boolean
5259             */            
5260             this.setAttributeConfig('animate', {
5261                 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5262                 validator: function(value) {
5263                     var ret = true;
5264                     if (!YAHOO.util.Anim) {
5265                         ret = false;
5266                     }
5267                     return ret;
5268                 }
5269             });
5270             /**
5271             * @config panel
5272             * @description A reference to the panel we are using for windows.
5273             * @default false
5274             * @type Boolean
5275             */            
5276             this.setAttributeConfig('panel', {
5277                 value: null,
5278                 writeOnce: true,
5279                 validator: function(value) {
5280                     var ret = true;
5281                     if (!YAHOO.widget.Overlay) {
5282                         ret = false;
5283                     }
5284                     return ret;
5285                 }               
5286             });
5287             /**
5288             * @attribute focusAtStart
5289             * @description Should we focus the window when the content is ready?
5290             * @default false
5291             * @type Boolean
5292             */            
5293             this.setAttributeConfig('focusAtStart', {
5294                 value: attr.focusAtStart || false,
5295                 writeOnce: true,
5296                 method: function(fs) {
5297                     if (fs) {
5298                         this.on('editorContentLoaded', function() {
5299                             var self = this;
5300                             setTimeout(function() {
5301                                 self.focus.call(self);
5302                                 self.editorDirty = false;
5303                             }, 400);
5304                         }, this, true);
5305                     }
5306                 }
5307             });
5308             /**
5309             * @attribute dompath
5310             * @description Toggle the display of the current Dom path below the editor
5311             * @default false
5312             * @type Boolean
5313             */            
5314             this.setAttributeConfig('dompath', {
5315                 value: attr.dompath || false,
5316                 method: function(dompath) {
5317                     if (dompath && !this.dompath) {
5318                         this.dompath = document.createElement('DIV');
5319                         this.dompath.id = this.get('id') + '_dompath';
5320                         Dom.addClass(this.dompath, 'dompath');
5321                         this.get('element_cont').get('firstChild').appendChild(this.dompath);
5322                         if (this.get('iframe')) {
5323                             this._writeDomPath();
5324                         }
5325                     } else if (!dompath && this.dompath) {
5326                         this.dompath.parentNode.removeChild(this.dompath);
5327                         this.dompath = null;
5328                     }
5329                 }
5330             });
5331             /**
5332             * @attribute markup
5333             * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5334             * @default "semantic"
5335             * @type String
5336             */            
5337             this.setAttributeConfig('markup', {
5338                 value: attr.markup || 'semantic',
5339                 validator: function(markup) {
5340                     switch (markup.toLowerCase()) {
5341                         case 'semantic':
5342                         case 'css':
5343                         case 'default':
5344                         case 'xhtml':
5345                         return true;
5346                     }
5347                     return false;
5348                 }
5349             });
5350             /**
5351             * @attribute removeLineBreaks
5352             * @description Should we remove linebreaks and extra spaces on cleanup
5353             * @default false
5354             * @type Boolean
5355             */            
5356             this.setAttributeConfig('removeLineBreaks', {
5357                 value: attr.removeLineBreaks || false,
5358                 validator: YAHOO.lang.isBoolean
5359             });
5360             
5361             /**
5362             * @config drag
5363             * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5364             * @type {Boolean/String}
5365             */
5366             this.setAttributeConfig('drag', {
5367                 writeOnce: true,
5368                 value: attr.drag || false
5369             });
5370
5371             /**
5372             * @config resize
5373             * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5374             * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5375             * @type Boolean
5376             */
5377             this.setAttributeConfig('resize', {
5378                 writeOnce: true,
5379                 value: attr.resize || false
5380             });
5381
5382             /**
5383             * @config filterWord
5384             * @description Attempt to filter out MS Word HTML from the Editor's output.
5385             * @type Boolean
5386             */
5387             this.setAttributeConfig('filterWord', {
5388                 value: attr.filterWord || false,
5389                 validator: YAHOO.lang.isBoolean
5390             });
5391
5392         },
5393         /**
5394         * @private
5395         * @method _getBlankImage
5396         * @description Retrieves the full url of the image to use as the blank image.
5397         * @return {String} The URL to the blank image
5398         */
5399         _getBlankImage: function() {
5400             if (!this.DOMReady) {
5401                 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5402                 return '';
5403             }
5404             var img = '';
5405             if (!this._blankImageLoaded) {
5406                 if (YAHOO.widget.EditorInfo.blankImage) {
5407                     this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5408                     this._blankImageLoaded = true;
5409                 } else {
5410                     var div = document.createElement('div');
5411                     div.style.position = 'absolute';
5412                     div.style.top = '-9999px';
5413                     div.style.left = '-9999px';
5414                     div.className = this.CLASS_PREFIX + '-blankimage';
5415                     document.body.appendChild(div);
5416                     img = YAHOO.util.Dom.getStyle(div, 'background-image');
5417                     img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5418                     //Adobe AIR Code
5419                     img = img.replace('app:/', '');             
5420                     this.set('blankimage', img);
5421                     this._blankImageLoaded = true;
5422                     div.parentNode.removeChild(div);
5423                     YAHOO.widget.EditorInfo.blankImage = img;
5424                 }
5425             } else {
5426                 img = this.get('blankimage');
5427             }
5428             return img;
5429         },
5430         /**
5431         * @private
5432         * @method _handleAutoHeight
5433         * @description Handles resizing the editor's height based on the content
5434         */
5435         _handleAutoHeight: function() {
5436             var doc = this._getDoc(),
5437                 body = doc.body,
5438                 docEl = doc.documentElement;
5439
5440             var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5441             var newHeight = body.scrollHeight;
5442             if (this.browser.webkit) {
5443                 newHeight = docEl.scrollHeight;
5444             }
5445             if (newHeight < parseInt(this.get('height'), 10)) {
5446                 newHeight = parseInt(this.get('height'), 10);
5447             }
5448             if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {   
5449                 var anim = this.get('animate');
5450                 this.set('animate', false);
5451                 this.set('height', newHeight + 'px');
5452                 this.set('animate', anim);
5453                 if (this.browser.ie) {
5454                     //Internet Explorer needs this
5455                     this.get('iframe').setStyle('height', '99%');
5456                     this.get('iframe').setStyle('zoom', '1');
5457                     var self = this;
5458                     window.setTimeout(function() {
5459                         self.get('iframe').setStyle('height', '100%');
5460                     }, 1);
5461                 }
5462             }
5463         },
5464         /**
5465         * @private
5466         * @property _formButtons
5467         * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5468         * @type Array
5469         */
5470         _formButtons: null,
5471         /**
5472         * @private
5473         * @property _formButtonClicked
5474         * @description The form button that was clicked to submit the form.
5475         * @type HTMLElement
5476         */
5477         _formButtonClicked: null,
5478         /**
5479         * @private
5480         * @method _handleFormButtonClick
5481         * @description The click listener assigned to each submit button in the Editor's parent form.
5482         * @param {Event} ev The click event
5483         */
5484         _handleFormButtonClick: function(ev) {
5485             var tar = Event.getTarget(ev);
5486             this._formButtonClicked = tar;
5487         },
5488         /**
5489         * @private
5490         * @method _handleFormSubmit
5491         * @description Handles the form submission.
5492         * @param {Object} ev The Form Submit Event
5493         */
5494         _handleFormSubmit: function(ev) {
5495             this.saveHTML();
5496
5497             var form = this.get('element').form,
5498                 tar = this._formButtonClicked || false;
5499
5500             Event.removeListener(form, 'submit', this._handleFormSubmit);
5501             if (YAHOO.env.ua.ie) {
5502                 //form.fireEvent("onsubmit");
5503                 if (tar && !tar.disabled) {
5504                     tar.click();
5505                 }
5506             } else {  // Gecko, Opera, and Safari
5507                 if (tar && !tar.disabled) {
5508                     tar.click();
5509                 }
5510                 var oEvent = document.createEvent("HTMLEvents");
5511                 oEvent.initEvent("submit", true, true);
5512                 form.dispatchEvent(oEvent);
5513                 if (YAHOO.env.ua.webkit) {
5514                     if (YAHOO.lang.isFunction(form.submit)) {
5515                         form.submit();
5516                     }
5517                 }
5518             }
5519             //2.6.0
5520             //Removed this, not need since removing Safari 2.x
5521             //Event.stopEvent(ev);
5522         },
5523         /**
5524         * @private
5525         * @method _handleFontSize
5526         * @description Handles the font size button in the toolbar.
5527         * @param {Object} o Object returned from Toolbar's buttonClick Event
5528         */
5529         _handleFontSize: function(o) {
5530             var button = this.toolbar.getButtonById(o.button.id);
5531             var value = button.get('label') + 'px';
5532             this.execCommand('fontsize', value);
5533             return false;
5534         },
5535         /**
5536         * @private
5537         * @description Handles the colorpicker buttons in the toolbar.
5538         * @param {Object} o Object returned from Toolbar's buttonClick Event
5539         */
5540         _handleColorPicker: function(o) {
5541             var cmd = o.button;
5542             var value = '#' + o.color;
5543             if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5544                 this.execCommand(cmd, value);
5545             }
5546         },
5547         /**
5548         * @private
5549         * @method _handleAlign
5550         * @description Handles the alignment buttons in the toolbar.
5551         * @param {Object} o Object returned from Toolbar's buttonClick Event
5552         */
5553         _handleAlign: function(o) {
5554             var cmd = null;
5555             for (var i = 0; i < o.button.menu.length; i++) {
5556                 if (o.button.menu[i].value == o.button.value) {
5557                     cmd = o.button.menu[i].value;
5558                 }
5559             }
5560             var value = this._getSelection();
5561
5562             this.execCommand(cmd, value);
5563             return false;
5564         },
5565         /**
5566         * @private
5567         * @method _handleAfterNodeChange
5568         * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5569         */
5570         _handleAfterNodeChange: function() {
5571             var path = this._getDomPath(),
5572                 elm = null,
5573                 family = null,
5574                 fontsize = null,
5575                 validFont = false,
5576                 fn_button = this.toolbar.getButtonByValue('fontname'),
5577                 fs_button = this.toolbar.getButtonByValue('fontsize'),
5578                 hd_button = this.toolbar.getButtonByValue('heading');
5579
5580             for (var i = 0; i < path.length; i++) {
5581                 elm = path[i];
5582
5583                 var tag = elm.tagName.toLowerCase();
5584
5585
5586                 if (elm.getAttribute('tag')) {
5587                     tag = elm.getAttribute('tag');
5588                 }
5589
5590                 family = elm.getAttribute('face');
5591                 if (Dom.getStyle(elm, 'font-family')) {
5592                     family = Dom.getStyle(elm, 'font-family');
5593                     //Adobe AIR Code
5594                     family = family.replace(/'/g, '');                    
5595                 }
5596
5597                 if (tag.substring(0, 1) == 'h') {
5598                     if (hd_button) {
5599                         for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5600                             if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5601                                 hd_button.set('label', hd_button._configs.menu.value[h].text);
5602                             }
5603                         }
5604                         this._updateMenuChecked('heading', tag);
5605                     }
5606                 }
5607             }
5608
5609             if (fn_button) {
5610                 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5611                     if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5612                         validFont = true;
5613                         family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5614                     }
5615                 }
5616                 if (!validFont) {
5617                     family = fn_button._configs.label._initialConfig.value;
5618                 }
5619                 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5620                 if (fn_button.get('label') != familyLabel) {
5621                     fn_button.set('label', familyLabel);
5622                     this._updateMenuChecked('fontname', family);
5623                 }
5624             }
5625
5626             if (fs_button) {
5627                 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5628                 if ((fontsize === null) || isNaN(fontsize)) {
5629                     fontsize = fs_button._configs.label._initialConfig.value;
5630                 }
5631                 fs_button.set('label', ''+fontsize);
5632             }
5633             
5634             if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5635                 this.toolbar.enableButton(fn_button);
5636                 this.toolbar.enableButton(fs_button);
5637                 this.toolbar.enableButton('forecolor');
5638                 this.toolbar.enableButton('backcolor');
5639             }
5640             if (this._isElement(elm, 'img')) {
5641                 if (YAHOO.widget.Overlay) {
5642                     this.toolbar.enableButton('createlink');
5643                 }
5644             }
5645             if (this._hasParent(elm, 'blockquote')) {
5646                 this.toolbar.selectButton('indent');
5647                 this.toolbar.disableButton('indent');
5648                 this.toolbar.enableButton('outdent');
5649             }
5650             if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5651                 this.toolbar.disableButton('indent');
5652             }
5653             this._lastButton = null;
5654             
5655         },
5656         /**
5657         * @private
5658         * @method _handleInsertImageClick
5659         * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5660         */
5661         _handleInsertImageClick: function() {
5662             if (this.get('limitCommands')) {
5663                 if (!this.toolbar.getButtonByValue('insertimage')) {
5664                     YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'SimpleEditor');
5665                     return false;
5666                 }
5667             }
5668         
5669             this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5670             var _handleAEC = function() {
5671                 var el = this.currentElement[0],
5672                     src = 'http://';
5673                 if (!el) {
5674                     el = this._getSelectedElement();
5675                 }
5676                 if (el) {
5677                     if (el.getAttribute('src')) {
5678                         src = el.getAttribute('src', 2);
5679                         if (src.indexOf(this.get('blankimage')) != -1) {
5680                             src = this.STR_IMAGE_HERE;
5681                         }
5682                     }
5683                 }
5684                 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5685                 if ((str !== '') && (str !== null)) {
5686                     el.setAttribute('src', str);
5687                 } else if (str === '') {
5688                     el.parentNode.removeChild(el);
5689                     this.currentElement = [];
5690                     this.nodeChange();
5691                 } else if ((str === null)) {
5692                     src = el.getAttribute('src', 2);
5693                     if (src.indexOf(this.get('blankimage')) != -1) {
5694                         el.parentNode.removeChild(el);
5695                         this.currentElement = [];
5696                         this.nodeChange();
5697                     }
5698                 }
5699                 this.closeWindow();
5700                 this.toolbar.set('disabled', false);
5701                 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5702             };
5703             this.on('afterExecCommand', _handleAEC, this, true);
5704         },
5705         /**
5706         * @private
5707         * @method _handleInsertImageWindowClose
5708         * @description Handles the closing of the Image Properties Window.
5709         */
5710         _handleInsertImageWindowClose: function() {
5711             this.nodeChange();
5712         },
5713         /**
5714         * @private
5715         * @method _isLocalFile
5716         * @param {String} url THe url/string to check
5717         * @description Checks to see if a string (href or img src) is possibly a local file reference..
5718         */
5719         _isLocalFile: function(url) {
5720             if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5721                 return true;
5722             }
5723             return false;
5724         },
5725         /**
5726         * @private
5727         * @method _handleCreateLinkClick
5728         * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5729         */
5730         _handleCreateLinkClick: function() {
5731             if (this.get('limitCommands')) {
5732                 if (!this.toolbar.getButtonByValue('createlink')) {
5733                     YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
5734                     return false;
5735                 }
5736             }
5737         
5738             this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5739
5740             var _handleAEC = function() {
5741                 var el = this.currentElement[0],
5742                     url = '';
5743
5744                 if (el) {
5745                     if (el.getAttribute('href', 2) !== null) {
5746                         url = el.getAttribute('href', 2);
5747                     }
5748                 }
5749                 var str = prompt(this.STR_LINK_URL + ': ', url);
5750                 if ((str !== '') && (str !== null)) {
5751                     var urlValue = str;
5752                     if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5753                         if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5754                             //Found an @ sign, prefix with mailto:
5755                             urlValue = 'mailto:' + urlValue;
5756                         } else {
5757                             /* :// not found adding */
5758                             if (urlValue.substring(0, 1) != '#') {
5759                                 //urlValue = 'http:/'+'/' + urlValue;
5760                             }
5761                         }
5762                     }
5763                     el.setAttribute('href', urlValue);
5764                 } else if (str !== null) {
5765                     var _span = this._getDoc().createElement('span');
5766                     _span.innerHTML = el.innerHTML;
5767                     Dom.addClass(_span, 'yui-non');
5768                     el.parentNode.replaceChild(_span, el);
5769                 }
5770                 this.closeWindow();
5771                 this.toolbar.set('disabled', false);
5772                 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5773             };
5774             this.on('afterExecCommand', _handleAEC, this);
5775
5776         },
5777         /**
5778         * @private
5779         * @method _handleCreateLinkWindowClose
5780         * @description Handles the closing of the Link Properties Window.
5781         */
5782         _handleCreateLinkWindowClose: function() {
5783             this.nodeChange();
5784             this.currentElement = [];
5785         },
5786         /**
5787         * @method render
5788         * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5789         */
5790         render: function() {
5791             if (this._rendered) {
5792                 return false;
5793             }
5794             YAHOO.log('Render', 'info', 'SimpleEditor');
5795             if (!this.DOMReady) {
5796                 YAHOO.log('!DOMReady', 'info', 'SimpleEditor');
5797                 this._queue[this._queue.length] = ['render', arguments];
5798                 return false;
5799             }
5800             if (this.get('element')) {
5801                 if (this.get('element').tagName) {
5802                     this._textarea = true;
5803                     if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5804                         this._textarea = false;
5805                     }
5806                 } else {
5807                     YAHOO.log('No Valid Element', 'error', 'SimpleEditor');
5808                     return false;
5809                 }
5810             } else {
5811                 YAHOO.log('No Element', 'error', 'SimpleEditor');
5812                 return false;
5813             }
5814             this._rendered = true;
5815             var self = this;
5816             window.setTimeout(function() {
5817                 self._render.call(self);
5818             }, 4);
5819         },
5820         /**
5821         * @private
5822         * @method _render
5823         * @description Causes the toolbar and the editor to render and replace the textarea.
5824         */
5825         _render: function() {
5826             var self = this;
5827             this.set('textarea', this.get('element'));
5828
5829             this.get('element_cont').setStyle('display', 'none');
5830             this.get('element_cont').addClass(this.CLASS_CONTAINER);
5831             
5832             this.set('iframe', this._createIframe());
5833
5834             window.setTimeout(function() {
5835                 self._setInitialContent.call(self);
5836             }, 10);
5837
5838             this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5839
5840             if (this.get('disabled')) {
5841                 this._disableEditor(true);
5842             }
5843
5844             var tbarConf = this.get('toolbar');
5845             //Create Toolbar instance
5846             if (tbarConf instanceof Toolbar) {
5847                 this.toolbar = tbarConf;
5848                 //Set the toolbar to disabled until content is loaded
5849                 this.toolbar.set('disabled', true);
5850             } else {
5851                 //Set the toolbar to disabled until content is loaded
5852                 tbarConf.disabled = true;
5853                 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5854             }
5855
5856             YAHOO.log('fireEvent::toolbarLoaded', 'info', 'SimpleEditor');
5857             this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5858
5859             
5860             this.toolbar.on('toolbarCollapsed', function() {
5861                 if (this.currentWindow) {
5862                     this.moveWindow();
5863                 }
5864             }, this, true);
5865             this.toolbar.on('toolbarExpanded', function() {
5866                 if (this.currentWindow) {
5867                     this.moveWindow();
5868                 }
5869             }, this, true);
5870             this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5871             
5872             this.toolbar.on('colorPickerClicked', function(o) {
5873                 this._handleColorPicker(o);
5874                 return false; //Stop the buttonClick event
5875             }, this, true);
5876
5877             this.toolbar.on('alignClick', this._handleAlign, this, true);
5878             this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5879             this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5880             this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5881             this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5882             this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5883             
5884
5885             //Replace Textarea with editable area
5886             this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5887
5888             
5889             this.setStyle('visibility', 'hidden');
5890             this.setStyle('position', 'absolute');
5891             this.setStyle('top', '-9999px');
5892             this.setStyle('left', '-9999px');
5893             this.get('element_cont').appendChild(this.get('element'));
5894             this.get('element_cont').setStyle('display', 'block');
5895
5896
5897             Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5898             this.get('iframe').addClass(this.CLASS_EDITABLE);
5899
5900             //Set height and width of editor container
5901             this.get('element_cont').setStyle('width', this.get('width'));
5902             Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5903
5904             this.get('iframe').setStyle('width', '100%'); //WIDTH
5905             this.get('iframe').setStyle('height', '100%');
5906
5907             this._setupDD();
5908
5909             window.setTimeout(function() {
5910                 self._setupAfterElement.call(self);
5911             }, 0);
5912             this.fireEvent('afterRender', { type: 'afterRender', target: this });
5913         },
5914         /**
5915         * @method execCommand
5916         * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5917         * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5918         * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5919         */
5920         execCommand: function(action, value) {
5921             var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5922             if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5923                 this.STOP_EXEC_COMMAND = false;
5924                 return false;
5925             }
5926             this._lastCommand = action;
5927             this._setMarkupType(action);
5928             if (this.browser.ie) {
5929                 this._getWindow().focus();
5930             }
5931             var exec = true;
5932             
5933             if (this.get('limitCommands')) {
5934                 if (!this.toolbar.getButtonByValue(action)) {
5935                     YAHOO.log('Toolbar Button for (' + action + ') was not found, skipping exec.', 'info', 'SimpleEditor');
5936                     exec = false;
5937                 }
5938             }
5939
5940             this.editorDirty = true;
5941             
5942             if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5943                 YAHOO.log('Found execCommand override method: (cmd_' + action.toLowerCase() + ')', 'info', 'SimpleEditor');
5944                 var retValue = this['cmd_' + action.toLowerCase()](value);
5945                 exec = retValue[0];
5946                 if (retValue[1]) {
5947                     action = retValue[1];
5948                 }
5949                 if (retValue[2]) {
5950                     value = retValue[2];
5951                 }
5952             }
5953             if (exec) {
5954                 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'SimpleEditor');
5955                 try {
5956                     this._getDoc().execCommand(action, false, value);
5957                 } catch(e) {
5958                     YAHOO.log('execCommand Failed', 'error', 'SimpleEditor');
5959                 }
5960             } else {
5961                 YAHOO.log('OVERRIDE::execCommand::(' + action + '),(' + value + ') skipped', 'warn', 'SimpleEditor');
5962             }
5963             this.on('afterExecCommand', function() {
5964                 this.unsubscribeAll('afterExecCommand');
5965                 this.nodeChange();
5966             }, this, true);
5967             this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5968             
5969         },
5970     /* {{{  Command Overrides */
5971
5972         /**
5973         * @method cmd_bold
5974         * @param value Value passed from the execCommand method
5975         * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5976         */
5977         cmd_bold: function(value) {
5978             if (!this.browser.webkit) {
5979                 var el = this._getSelectedElement();
5980                 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5981                     if (el.style.fontWeight == 'bold') {
5982                         el.style.fontWeight = '';
5983                         var b = this._getDoc().createElement('b'),
5984                         par = el.parentNode;
5985                         par.replaceChild(b, el);
5986                         b.appendChild(el);
5987                     }
5988                 }
5989             }
5990             return [true];
5991         },
5992         /**
5993         * @method cmd_italic
5994         * @param value Value passed from the execCommand method
5995         * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5996         */
5997
5998         cmd_italic: function(value) {
5999             if (!this.browser.webkit) {
6000                 var el = this._getSelectedElement();
6001                 if (el && this._isElement(el, 'span') && this._hasSelection()) {
6002                     if (el.style.fontStyle == 'italic') {
6003                         el.style.fontStyle = '';
6004                         var i = this._getDoc().createElement('i'),
6005                         par = el.parentNode;
6006                         par.replaceChild(i, el);
6007                         i.appendChild(el);
6008                     }
6009                 }
6010             }
6011             return [true];
6012         },
6013
6014
6015         /**
6016         * @method cmd_underline
6017         * @param value Value passed from the execCommand method
6018         * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
6019         */
6020         cmd_underline: function(value) {
6021             if (!this.browser.webkit) {
6022                 var el = this._getSelectedElement();
6023                 if (el && this._isElement(el, 'span')) {
6024                     if (el.style.textDecoration == 'underline') {
6025                         el.style.textDecoration = 'none';
6026                     } else {
6027                         el.style.textDecoration = 'underline';
6028                     }
6029                     return [false];
6030                 }
6031             }
6032             return [true];
6033         },
6034         /**
6035         * @method cmd_backcolor
6036         * @param value Value passed from the execCommand method
6037         * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
6038         */
6039         cmd_backcolor: function(value) {
6040             var exec = true,
6041                 el = this._getSelectedElement(),
6042                 action = 'backcolor';
6043
6044             if (this.browser.gecko || this.browser.opera) {
6045                 this._setEditorStyle(true);
6046                 action = 'hilitecolor';
6047             }
6048
6049             if (!this._isElement(el, 'body') && !this._hasSelection()) {
6050                 el.style.backgroundColor = value;
6051                 this._selectNode(el);
6052                 exec = false;
6053             } else {
6054                 if (this.get('insert')) {
6055                     el = this._createInsertElement({ backgroundColor: value });
6056                 } else {
6057                     this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
6058                     this._selectNode(this.currentElement[0]);
6059                 }
6060                 exec = false;
6061             }
6062
6063             return [exec, action];
6064         },
6065         /**
6066         * @method cmd_forecolor
6067         * @param value Value passed from the execCommand method
6068         * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
6069         */
6070         cmd_forecolor: function(value) {
6071             var exec = true,
6072                 el = this._getSelectedElement();
6073                 
6074                 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6075                     Dom.setStyle(el, 'color', value);
6076                     this._selectNode(el);
6077                     exec = false;
6078                 } else {
6079                     if (this.get('insert')) {
6080                         el = this._createInsertElement({ color: value });
6081                     } else {
6082                         this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
6083                         this._selectNode(this.currentElement[0]);
6084                     }
6085                     exec = false;
6086                 }
6087                 return [exec];
6088         },
6089         /**
6090         * @method cmd_unlink
6091         * @param value Value passed from the execCommand method
6092         * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
6093         */
6094         cmd_unlink: function(value) {
6095             this._swapEl(this.currentElement[0], 'span', function(el) {
6096                 el.className = 'yui-non';
6097             });
6098             return [false];
6099         },
6100         /**
6101         * @method cmd_createlink
6102         * @param value Value passed from the execCommand method
6103         * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
6104         */
6105         cmd_createlink: function(value) {
6106             var el = this._getSelectedElement(), _a = null;
6107             if (this._hasParent(el, 'a')) {
6108                 this.currentElement[0] = this._hasParent(el, 'a');
6109             } else if (this._isElement(el, 'li')) {
6110                 _a = this._getDoc().createElement('a');
6111                 _a.innerHTML = el.innerHTML;
6112                 el.innerHTML = '';
6113                 el.appendChild(_a);
6114                 this.currentElement[0] = _a;
6115             } else if (!this._isElement(el, 'a')) {
6116                 this._createCurrentElement('a');
6117                 _a = this._swapEl(this.currentElement[0], 'a');
6118                 this.currentElement[0] = _a;
6119             } else {
6120                 this.currentElement[0] = el;
6121             }
6122             return [false];
6123         },
6124         /**
6125         * @method cmd_insertimage
6126         * @param value Value passed from the execCommand method
6127         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
6128         */
6129         cmd_insertimage: function(value) {
6130             var exec = true, _img = null, action = 'insertimage',
6131                 el = this._getSelectedElement();
6132
6133             if (value === '') {
6134                 value = this.get('blankimage');
6135             }
6136
6137             /*
6138             * @knownissue Safari Cursor Position
6139             * @browser Safari 2.x
6140             * @description The issue here is that we have no way of knowing where the cursor position is
6141             * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6142             */
6143             
6144             YAHOO.log('InsertImage: ' + el.tagName, 'info', 'SimpleEditor');
6145             if (this._isElement(el, 'img')) {
6146                 this.currentElement[0] = el;
6147                 exec = false;
6148             } else {
6149                 if (this._getDoc().queryCommandEnabled(action)) {
6150                     this._getDoc().execCommand(action, false, value);
6151                     var imgs = this._getDoc().getElementsByTagName('img');
6152                     for (var i = 0; i < imgs.length; i++) {
6153                         if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
6154                             YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
6155                             this.currentElement[0] = imgs[i];
6156                         }
6157                     }
6158                     exec = false;
6159                 } else {
6160                     if (el == this._getDoc().body) {
6161                         _img = this._getDoc().createElement('img');
6162                         _img.setAttribute('src', value);
6163                         YAHOO.util.Dom.addClass(_img, 'yui-img');
6164                         this._getDoc().body.appendChild(_img);
6165                     } else {
6166                         this._createCurrentElement('img');
6167                         _img = this._getDoc().createElement('img');
6168                         _img.setAttribute('src', value);
6169                         YAHOO.util.Dom.addClass(_img, 'yui-img');
6170                         this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
6171                     }
6172                     this.currentElement[0] = _img;
6173                     exec = false;
6174                 }
6175             }
6176             return [exec];
6177         },
6178         /**
6179         * @method cmd_inserthtml
6180         * @param value Value passed from the execCommand method
6181         * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6182         */
6183         cmd_inserthtml: function(value) {
6184             var exec = true, action = 'inserthtml', _span = null, _range = null;
6185             /*
6186             * @knownissue Safari cursor position
6187             * @browser Safari 2.x
6188             * @description The issue here is that we have no way of knowing where the cursor position is
6189             * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6190             */
6191             if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6192                 YAHOO.log('More Safari DOM tricks (inserthtml)', 'info', 'EditorSafari');
6193                 this._createCurrentElement('img');
6194                 _span = this._getDoc().createElement('span');
6195                 _span.innerHTML = value;
6196                 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6197                 exec = false;
6198             } else if (this.browser.ie) {
6199                 _range = this._getRange();
6200                 if (_range.item) {
6201                     _range.item(0).outerHTML = value;
6202                 } else {
6203                     _range.pasteHTML(value);
6204                 }
6205                 exec = false;                    
6206             }
6207             return [exec];
6208         },
6209         /**
6210         * @method cmd_list
6211         * @param tag The tag of the list you want to create (eg, ul or ol)
6212         * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6213         */
6214         cmd_list: function(tag) {
6215             var exec = true, list = null, li = 0, el = null, str = '',
6216                 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6217                 if (tag == 'ul') {
6218                     action = 'insertunorderedlist';
6219                 }
6220             /*
6221             * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6222             * @browser Safari 2.x
6223             * The issue with this workaround is that when applied to a set of text
6224             * that has BR's in it, Safari may or may not pick up the individual items as
6225             * list items. This is fixed in WebKit (Safari 3)
6226             * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6227             */
6228             //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6229             if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) {
6230                 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6231                     YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6232                     el = selEl.parentNode;
6233                     list = this._getDoc().createElement('span');
6234                     YAHOO.util.Dom.addClass(list, 'yui-non');
6235                     str = '';
6236                     var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div');
6237                     for (li = 0; li < lis.length; li++) {
6238                         str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>';
6239                     }
6240                     list.innerHTML = str;
6241                     this.currentElement[0] = el;
6242                     this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6243                 } else {
6244                     YAHOO.log('Create list item', 'info', 'SimpleEditor');
6245                     this._createCurrentElement(tag.toLowerCase());
6246                     list = this._getDoc().createElement(tag);
6247                     for (li = 0; li < this.currentElement.length; li++) {
6248                         var newli = this._getDoc().createElement('li');
6249                         newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non">&nbsp;</span>&nbsp;';
6250                         list.appendChild(newli);
6251                         if (li > 0) {
6252                             this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6253                         }
6254                     }
6255                     var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'),
6256                     items = list.firstChild.innerHTML.split(b_tag), i, item;
6257                     if (items.length > 0) {
6258                         list.innerHTML = '';
6259                         for (i = 0; i < items.length; i++) {
6260                             item = this._getDoc().createElement('li');
6261                             item.innerHTML = items[i];
6262                             list.appendChild(item);
6263                         }
6264                     }
6265
6266                     this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6267                     this.currentElement[0] = list;
6268                     var _h = this.currentElement[0].firstChild;
6269                     _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6270                     if (this.browser.webkit) {
6271                         this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6272                     }
6273                 }
6274                 exec = false;
6275             } else {
6276                 el = this._getSelectedElement();
6277                 YAHOO.log(el.tagName);
6278                 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..
6279                     YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6280                     if (this.browser.ie) {
6281                         if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6282                             el = el.getElementsByTagName('li')[0];
6283                         }
6284                         YAHOO.log('Undo IE', 'info', 'SimpleEditor');
6285                         str = '';
6286                         var lis2 = el.parentNode.getElementsByTagName('li');
6287                         for (var j = 0; j < lis2.length; j++) {
6288                             str += lis2[j].innerHTML + '<br>';
6289                         }
6290                         var newEl = this._getDoc().createElement('span');
6291                         newEl.innerHTML = str;
6292                         el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6293                     } else {
6294                         this.nodeChange();
6295                         this._getDoc().execCommand(action, '', el.parentNode);
6296                         this.nodeChange();
6297                     }
6298                     exec = false;
6299                 }
6300                 if (this.browser.opera) {
6301                     var self = this;
6302                     window.setTimeout(function() {
6303                         var liso = self._getDoc().getElementsByTagName('li');
6304                         for (var i = 0; i < liso.length; i++) {
6305                             if (liso[i].innerHTML.toLowerCase() == '<br>') {
6306                                 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6307                             }
6308                         }
6309                     },30);
6310                 }
6311                 if (this.browser.ie && exec) {
6312                     var html = '';
6313                     if (this._getRange().html) {
6314                         html = '<li>' + this._getRange().html+ '</li>';
6315                     } else {
6316                         var t = this._getRange().text.split('\n');
6317                         if (t.length > 1) {
6318                             html = '';
6319                             for (var ie = 0; ie < t.length; ie++) {
6320                                 html += '<li>' + t[ie] + '</li>';
6321                             }
6322                         } else {
6323                             var txt = this._getRange().text;
6324                             if (txt === '') {
6325                                 html = '<li id="new_list_item">' + txt + '</li>';
6326                             } else {
6327                                 html = '<li>' + txt + '</li>';
6328                             }
6329                         }
6330                     }
6331                     this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6332                     var new_item = this._getDoc().getElementById('new_list_item');
6333                     if (new_item) {
6334                         var range = this._getDoc().body.createTextRange();
6335                         range.moveToElementText(new_item);
6336                         range.collapse(false);
6337                         range.select();                       
6338                         new_item.id = '';
6339                     }
6340                     exec = false;
6341                 }
6342             }
6343             return exec;
6344         },
6345         /**
6346         * @method cmd_insertorderedlist
6347         * @param value Value passed from the execCommand method
6348         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6349         */
6350         cmd_insertorderedlist: function(value) {
6351             return [this.cmd_list('ol')];
6352         },
6353         /**
6354         * @method cmd_insertunorderedlist 
6355         * @param value Value passed from the execCommand method
6356         * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6357         */
6358         cmd_insertunorderedlist: function(value) {
6359             return [this.cmd_list('ul')];
6360         },
6361         /**
6362         * @method cmd_fontname
6363         * @param value Value passed from the execCommand method
6364         * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6365         */
6366         cmd_fontname: function(value) {
6367             var exec = true,
6368                 selEl = this._getSelectedElement();
6369
6370             this.currentFont = value;
6371             if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6372                 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6373                 exec = false;
6374             } else if (this.get('insert') && !this._hasSelection()) {
6375                 YAHOO.log('No selection and no selected element and we are in insert mode', 'info', 'SimpleEditor');
6376                 var el = this._createInsertElement({ fontFamily: value });
6377                 exec = false;
6378             }
6379             return [exec];
6380         },
6381         /**
6382         * @method cmd_fontsize
6383         * @param value Value passed from the execCommand method
6384         * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6385         */
6386         cmd_fontsize: function(value) {
6387             var el = null, go = true;
6388             el = this._getSelectedElement();
6389             if (this.browser.webkit) {
6390                 if (this.currentElement[0]) {
6391                     if (el == this.currentElement[0]) {
6392                         go = false;
6393                         YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6394                         this._selectNode(el);
6395                         this.currentElement[0] = el;
6396                     }
6397                 }
6398             }
6399             if (go) {
6400                 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6401                     el = this._getSelectedElement();
6402                     YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6403                     if (this.get('insert') && this.browser.ie) {
6404                         var r = this._getRange();
6405                         r.collapse(false);
6406                         r.select();
6407                     } else {
6408                         this._selectNode(el);
6409                     }
6410                 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6411                     YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6412                 } else {
6413                     if (this.get('insert') && !this._hasSelection()) {
6414                         el = this._createInsertElement({ fontSize: value });
6415                         this.currentElement[0] = el;
6416                         this._selectNode(this.currentElement[0]);
6417                     } else {
6418                         this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6419                         this._selectNode(this.currentElement[0]);
6420                     }
6421                 }
6422             }
6423             return [false];
6424         },
6425     /* }}} */
6426         /**
6427         * @private
6428         * @method _swapEl
6429         * @param {HTMLElement} el The element to swap with
6430         * @param {String} tagName The tagname of the element that you wish to create
6431         * @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.
6432         * @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.
6433         */
6434         _swapEl: function(el, tagName, callback) {
6435             var _el = this._getDoc().createElement(tagName);
6436             if (el) {
6437                 _el.innerHTML = el.innerHTML;
6438             }
6439             if (typeof callback == 'function') {
6440                 callback.call(this, _el);
6441             }
6442             if (el) {
6443                 el.parentNode.replaceChild(_el, el);
6444             }
6445             return _el;
6446         },
6447         /**
6448         * @private
6449         * @method _createInsertElement
6450         * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6451         * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6452         * @return {HTMLElement}
6453         */
6454         _createInsertElement: function(css) {
6455             this._createCurrentElement('span', css);
6456             var el = this.currentElement[0];
6457             if (this.browser.webkit) {
6458                 //Little Safari Hackery here..
6459                 el.innerHTML = '<span class="yui-non">&nbsp;</span>';
6460                 el = el.firstChild;
6461                 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);                    
6462             } else if (this.browser.ie || this.browser.opera) {
6463                 el.innerHTML = '&nbsp;';
6464             }
6465             this.focus();
6466             this._selectNode(el, true);
6467             return el;
6468         },
6469         /**
6470         * @private
6471         * @method _createCurrentElement
6472         * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6473         * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6474         * @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.
6475         * 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 
6476         * <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.
6477         */
6478         _createCurrentElement: function(tagName, tagStyle) {
6479             tagName = ((tagName) ? tagName : 'a');
6480             var tar = null,
6481                 el = [],
6482                 _doc = this._getDoc();
6483             
6484             if (this.currentFont) {
6485                 if (!tagStyle) {
6486                     tagStyle = {};
6487                 }
6488                 tagStyle.fontFamily = this.currentFont;
6489                 this.currentFont = null;
6490             }
6491             this.currentElement = [];
6492
6493             var _elCreate = function(tagName, tagStyle) {
6494                 var el = null;
6495                 tagName = ((tagName) ? tagName : 'span');
6496                 tagName = tagName.toLowerCase();
6497                 switch (tagName) {
6498                     case 'h1':
6499                     case 'h2':
6500                     case 'h3':
6501                     case 'h4':
6502                     case 'h5':
6503                     case 'h6':
6504                         el = _doc.createElement(tagName);
6505                         break;
6506                     default:
6507                         el = _doc.createElement(tagName);
6508                         if (tagName === 'span') {
6509                             YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6510                             YAHOO.util.Dom.addClass(el, 'yui-tag');
6511                             el.setAttribute('tag', tagName);
6512                         }
6513
6514                         for (var k in tagStyle) {
6515                             if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6516                                 el.style[k] = tagStyle[k];
6517                             }
6518                         }
6519                         break;
6520                 }
6521                 return el;
6522             };
6523
6524             if (!this._hasSelection()) {
6525                 if (this._getDoc().queryCommandEnabled('insertimage')) {
6526                     this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6527                     var imgs = this._getDoc().getElementsByTagName('img');
6528                     for (var j = 0; j < imgs.length; j++) {
6529                         if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6530                             el = _elCreate(tagName, tagStyle);
6531                             imgs[j].parentNode.replaceChild(el, imgs[j]);
6532                             this.currentElement[this.currentElement.length] = el;
6533                         }
6534                     }
6535                 } else {
6536                     if (this.currentEvent) {
6537                         tar = YAHOO.util.Event.getTarget(this.currentEvent);
6538                     } else {
6539                         //For Safari..
6540                         tar = this._getDoc().body;                        
6541                     }
6542                 }
6543                 if (tar) {
6544                     /*
6545                     * @knownissue Safari Cursor Position
6546                     * @browser Safari 2.x
6547                     * @description The issue here is that we have no way of knowing where the cursor position is
6548                     * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6549                     */
6550                     el = _elCreate(tagName, tagStyle);
6551                     if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6552                         if (this._isElement(tar, 'html')) {
6553                             tar = this._getDoc().body;
6554                         }
6555                         tar.appendChild(el);
6556                     } else if (tar.nextSibling) {
6557                         tar.parentNode.insertBefore(el, tar.nextSibling);
6558                     } else {
6559                         tar.parentNode.appendChild(el);
6560                     }
6561                     //this.currentElement = el;
6562                     this.currentElement[this.currentElement.length] = el;
6563                     this.currentEvent = null;
6564                     if (this.browser.webkit) {
6565                         //Force Safari to focus the new element
6566                         this._getSelection().setBaseAndExtent(el, 0, el, 0);
6567                         if (this.browser.webkit3) {
6568                             this._getSelection().collapseToStart();
6569                         } else {
6570                             this._getSelection().collapse(true);
6571                         }
6572                     }
6573                 }
6574             } else {
6575                 //Force CSS Styling for this action...
6576                 this._setEditorStyle(true);
6577                 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6578                 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6579
6580                 if (!this._isElement(this._getSelectedElement(), 'body')) {
6581                     __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6582                     __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6583                 }
6584                 for (var _els = 0; _els < __els.length; _els++) {
6585                     var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6586                     for (var e = 0; e < _tmp1.length; e++) {
6587                         _tmp[_tmp.length] = _tmp1[e];
6588                     }
6589                 }
6590
6591                 
6592                 for (var i = 0; i < _tmp.length; i++) {
6593                     if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6594                         if (tagName !== 'span') {
6595                             el = _elCreate(tagName, tagStyle);
6596                         } else {
6597                             el = _elCreate(_tmp[i].tagName, tagStyle);
6598                         }
6599                         el.innerHTML = _tmp[i].innerHTML;
6600                         if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6601                             var fc = _tmp[i].getElementsByTagName('li')[0];
6602                             _tmp[i].style.fontFamily = 'inherit';
6603                             fc.style.fontFamily = 'inherit';
6604                             el.innerHTML = fc.innerHTML;
6605                             fc.innerHTML = '';
6606                             fc.appendChild(el);
6607                             this.currentElement[this.currentElement.length] = el;
6608                         } else if (this._isElement(_tmp[i], 'li')) {
6609                             _tmp[i].innerHTML = '';
6610                             _tmp[i].appendChild(el);
6611                             _tmp[i].style.fontFamily = 'inherit';
6612                             this.currentElement[this.currentElement.length] = el;
6613                         } else {
6614                             if (_tmp[i].parentNode) {
6615                                 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6616                                 this.currentElement[this.currentElement.length] = el;
6617                                 this.currentEvent = null;
6618                                 if (this.browser.webkit) {
6619                                     //Force Safari to focus the new element
6620                                     this._getSelection().setBaseAndExtent(el, 0, el, 0);
6621                                     if (this.browser.webkit3) {
6622                                         this._getSelection().collapseToStart();
6623                                     } else {
6624                                         this._getSelection().collapse(true);
6625                                     }
6626                                 }
6627                                 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6628                                     this._getSelection().empty();
6629                                 }
6630                                 if (this.browser.gecko) {
6631                                     this._getSelection().collapseToStart();
6632                                 }
6633                             }
6634                         }
6635                     }
6636                 }
6637                 var len = this.currentElement.length;
6638                 for (var o = 0; o < len; o++) {
6639                     if ((o + 1) != len) { //Skip the last one in the list
6640                         if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6641                             if (this._isElement(this.currentElement[o], 'br')) {
6642                                 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6643                             }
6644                         }
6645                     }
6646                 }
6647             }
6648         },
6649         /**
6650         * @method saveHTML
6651         * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6652         * @return String
6653         */
6654         saveHTML: function() {
6655             var html = this.cleanHTML();
6656             if (this._textarea) {
6657                 this.get('element').value = html;
6658             } else {
6659                 this.get('element').innerHTML = html;
6660             }
6661             if (this.get('saveEl') !== this.get('element')) {
6662                 var out = this.get('saveEl');
6663                 if (Lang.isString(out)) {
6664                     out = Dom.get(out);
6665                 }
6666                 if (out) {
6667                     if (out.tagName.toLowerCase() === 'textarea') {
6668                         out.value = html;
6669                     } else {
6670                         out.innerHTML = html;
6671                     }
6672                 }
6673             }
6674             return html;
6675         },
6676         /**
6677         * @method setEditorHTML
6678         * @param {String} incomingHTML The html content to load into the editor
6679         * @description Loads HTML into the editors body
6680         */
6681         setEditorHTML: function(incomingHTML) {
6682             var html = this._cleanIncomingHTML(incomingHTML);
6683             html = html.replace(/RIGHT_BRACKET/gi, '{');
6684             html = html.replace(/LEFT_BRACKET/gi, '}');
6685             this._getDoc().body.innerHTML = html;
6686             this.nodeChange();
6687         },
6688         /**
6689         * @method getEditorHTML
6690         * @description Gets the unprocessed/unfiltered HTML from the editor
6691         */
6692         getEditorHTML: function() {
6693             try {
6694                 var b = this._getDoc().body;
6695                 if (b === null) {
6696                     YAHOO.log('Body is null, returning null.', 'error', 'SimpleEditor');
6697                     return null;
6698                 }
6699                 return this._getDoc().body.innerHTML;
6700             } catch (e) {
6701                 return '';
6702             }
6703         },
6704         /**
6705         * @method show
6706         * @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.
6707         */
6708         show: function() {
6709             if (this.browser.gecko) {
6710                 this._setDesignMode('on');
6711                 this.focus();
6712             }
6713             if (this.browser.webkit) {
6714                 var self = this;
6715                 window.setTimeout(function() {
6716                     self._setInitialContent.call(self);
6717                 }, 10);
6718             }
6719             //Adding this will close all other Editor window's when showing this one.
6720             if (this.currentWindow) {
6721                 this.closeWindow();
6722             }
6723             //Put the iframe back in place
6724             this.get('iframe').setStyle('position', 'static');
6725             this.get('iframe').setStyle('left', '');
6726         },
6727         /**
6728         * @method hide
6729         * @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.
6730         */
6731         hide: function() {
6732             //Adding this will close all other Editor window's.
6733             if (this.currentWindow) {
6734                 this.closeWindow();
6735             }
6736             if (this._fixNodesTimer) {
6737                 clearTimeout(this._fixNodesTimer);
6738                 this._fixNodesTimer = null;
6739             }
6740             if (this._nodeChangeTimer) {
6741                 clearTimeout(this._nodeChangeTimer);
6742                 this._nodeChangeTimer = null;
6743             }
6744             this._lastNodeChange = 0;
6745             //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6746             this.get('iframe').setStyle('position', 'absolute');
6747             this.get('iframe').setStyle('left', '-9999px');
6748         },
6749         /**
6750         * @method _cleanIncomingHTML
6751         * @param {String} html The unfiltered HTML
6752         * @description Process the HTML with a few regexes to clean it up and stabilize the input
6753         * @return {String} The filtered HTML
6754         */
6755         _cleanIncomingHTML: function(html) {
6756             html = html.replace(/{/gi, 'RIGHT_BRACKET');
6757             html = html.replace(/}/gi, 'LEFT_BRACKET');
6758
6759             html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6760             html = html.replace(/<\/strong>/gi, '</b>');   
6761
6762             //replace embed before em check
6763             html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6764             html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6765
6766             html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6767             html = html.replace(/<\/em>/gi, '</i>');
6768             html = html.replace(/_moz_dirty=""/gi, '');
6769             
6770             //Put embed tags back in..
6771             html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6772             html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6773             if (this.get('plainText')) {
6774                 YAHOO.log('Filtering as plain text', 'info', 'SimpleEditor');
6775                 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6776                 html = html.replace(/  /gi, '&nbsp;&nbsp;'); //Replace all double spaces
6777                 html = html.replace(/\t/gi, '&nbsp;&nbsp;&nbsp;&nbsp;'); //Replace all tabs
6778             }
6779             //Removing Script Tags from the Editor
6780             html = html.replace(/<script([^>]*)>/gi, '<bad>');
6781             html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6782             html = html.replace(/&lt;script([^>]*)&gt;/gi, '<bad>');
6783             html = html.replace(/&lt;\/script([^>]*)&gt;/gi, '</bad>');
6784             //Replace the line feeds
6785             html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6786             
6787             //Remove Bad HTML elements (used to be script nodes)
6788             html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6789             //Replace the lines feeds
6790             html = html.replace(/<YUI_LF>/g, '\n');
6791             return html;
6792         },
6793         /**
6794         * @method cleanHTML
6795         * @param {String} html The unfiltered HTML
6796         * @description Process the HTML with a few regexes to clean it up and stabilize the output
6797         * @return {String} The filtered HTML
6798         */
6799         cleanHTML: function(html) {
6800             //Start Filtering Output
6801             //Begin RegExs..
6802             if (!html) { 
6803                 html = this.getEditorHTML();
6804             }
6805             var markup = this.get('markup');
6806             //Make some backups...
6807             html = this.pre_filter_linebreaks(html, markup);
6808
6809             //Filter MS Word
6810             html = this.filter_msword(html);
6811
6812                     html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6813                     html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6814
6815                     html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6816                     html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6817
6818                     html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6819                     html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6820                     html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6821                     html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6822
6823                     html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6824                     html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6825
6826             //Convert b and i tags to strong and em tags
6827             if ((markup == 'semantic') || (markup == 'xhtml')) {
6828                 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6829                 html = html.replace(/<\/i>/gi, '</em>');
6830                 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6831                 html = html.replace(/<\/b>/gi, '</strong>');
6832             }
6833
6834             html = html.replace(/_moz_dirty=""/gi, '');
6835
6836             //normalize strikethrough
6837             html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6838             html = html.replace(/\/strike>/gi, '/span>');
6839             
6840             
6841             //Case Changing
6842             if (this.browser.ie) {
6843                 html = html.replace(/text-decoration/gi, 'text-decoration');
6844                 html = html.replace(/font-weight/gi, 'font-weight');
6845                 html = html.replace(/_width="([^>]*)"/gi, '');
6846                 html = html.replace(/_height="([^>]*)"/gi, '');
6847                 //Cleanup Image URL's
6848                 var url = this._baseHREF.replace(/\//gi, '\\/'),
6849                     re = new RegExp('src="' + url, 'gi');
6850                 html = html.replace(re, 'src="');
6851             }
6852                     html = html.replace(/<font/gi, '<font');
6853                     html = html.replace(/<\/font>/gi, '</font>');
6854                     html = html.replace(/<span/gi, '<span');
6855                     html = html.replace(/<\/span>/gi, '</span>');
6856             if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6857                 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6858                 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6859                 if (this.browser.webkit) {
6860                     html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6861                     html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6862                 }
6863                 html = html.replace(/\/u>/gi, '/span>');
6864                 if (markup == 'css') {
6865                     html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6866                     html = html.replace(/<\/em>/gi, '</i>');
6867                     html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6868                     html = html.replace(/<\/strong>/gi, '</b>');
6869                     html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6870                     html = html.replace(/\/b>/gi, '/span>');
6871                     html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6872                     html = html.replace(/\/i>/gi, '/span>');
6873                 }
6874                 html = html.replace(/  /gi, ' '); //Replace all double spaces and replace with a single
6875             } else {
6876                         html = html.replace(/<u/gi, '<u');
6877                         html = html.replace(/\/u>/gi, '/u>');
6878             }
6879                     html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6880                     html = html.replace(/\/ol>/gi, '/ol>');
6881                     html = html.replace(/<li/gi, '<li');
6882                     html = html.replace(/\/li>/gi, '/li>');
6883             html = this.filter_safari(html);
6884
6885             html = this.filter_internals(html);
6886
6887             html = this.filter_all_rgb(html);
6888
6889             //Replace our backups with the real thing
6890             html = this.post_filter_linebreaks(html, markup);
6891
6892             if (markup == 'xhtml') {
6893                         html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6894                         html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6895             } else {
6896                         html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6897                         html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6898             }
6899                     html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6900                     html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6901
6902             html = this.filter_invalid_lists(html);
6903
6904                     html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6905                     html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6906
6907                     html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6908                     html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6909             
6910             //This should fix &amp;'s in URL's
6911             html = html.replace(/ &amp; /gi, ' YUI_AMP ');
6912             html = html.replace(/ &amp;/gi, ' YUI_AMP_F ');
6913             html = html.replace(/&amp; /gi, ' YUI_AMP_R ');
6914             html = html.replace(/&amp;/gi, '&');
6915             html = html.replace(/ YUI_AMP /gi, ' &amp; ');
6916             html = html.replace(/ YUI_AMP_F /gi, ' &amp;');
6917             html = html.replace(/ YUI_AMP_R /gi, '&amp; ');
6918
6919             //Trim the output, removing whitespace from the beginning and end
6920             html = YAHOO.lang.trim(html);
6921
6922             if (this.get('removeLineBreaks')) {
6923                 html = html.replace(/\n/g, '').replace(/\r/g, '');
6924                 html = html.replace(/  /gi, ' '); //Replace all double spaces and replace with a single
6925             }
6926             
6927             for (var v in this.invalidHTML) {
6928                 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6929                     if (Lang.isObject(v) && v.keepContents) {
6930                         html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6931                     } else {
6932                         html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6933                     }
6934                 }
6935             }
6936
6937             /* LATER -- Add DOM manipulation
6938             console.log(html);
6939             var frag = document.createDocumentFragment();
6940             frag.innerHTML = html;
6941
6942             var ps = frag.getElementsByTagName('p'),
6943                 len = ps.length;
6944             for (var i = 0; i < len; i++) {
6945                 var ps2 = ps[i].getElementsByTagName('p');
6946                 if (ps2.length) {
6947                     
6948                 }
6949                 
6950             }
6951             html = frag.innerHTML;
6952             console.log(html);
6953             */
6954
6955             this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6956
6957             return html;
6958         },
6959         /**
6960         * @method filter_msword
6961         * @param String html The HTML string to filter
6962         * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6963         */
6964         filter_msword: function(html) {
6965             if (!this.get('filterWord')) {
6966                 return html;
6967             }
6968             //Remove the ms o: tags
6969             html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6970             html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, '&nbsp;');
6971
6972             //Remove the ms w: tags
6973             html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6974
6975             //Remove mso-? styles.
6976             html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6977
6978             //Remove more bogus MS styles.
6979             html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6980             html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6981             html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6982             html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6983             html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6984             html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6985             html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6986             html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6987
6988             //Remove XML declarations
6989             html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6990
6991             //Remove lang
6992             html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6993
6994             //Remove language tags
6995             html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6996
6997             //Remove onmouseover and onmouseout events (from MS Word comments effect)
6998             html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6999             html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
7000             
7001             return html;
7002         },
7003         /**
7004         * @method filter_invalid_lists
7005         * @param String html The HTML string to filter
7006         * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
7007         */
7008         filter_invalid_lists: function(html) {
7009             html = html.replace(/<\/li>\n/gi, '</li>');
7010
7011             html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
7012             html = html.replace(/<\/ol>/gi, '</ol></li>');
7013             html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
7014
7015             html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
7016             html = html.replace(/<\/ul>/gi, '</ul></li>');
7017             html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
7018
7019             html = html.replace(/<\/li>/gi, "</li>");
7020             html = html.replace(/<\/ol>/gi, "</ol>");
7021             html = html.replace(/<ol>/gi, "<ol>");
7022             html = html.replace(/<ul>/gi, "<ul>");
7023             return html;
7024         },
7025         /**
7026         * @method filter_safari
7027         * @param String html The HTML string to filter
7028         * @description Filters strings specific to Safari
7029         * @return String
7030         */
7031         filter_safari: function(html) {
7032             if (this.browser.webkit) {
7033                 //<span class="Apple-tab-span" style="white-space:pre"> </span>
7034                 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, '&nbsp;&nbsp;&nbsp;&nbsp;');
7035                 html = html.replace(/Apple-style-span/gi, '');
7036                 html = html.replace(/style="line-height: normal;"/gi, '');
7037                 html = html.replace(/yui-wk-div/gi, '');
7038                 html = html.replace(/yui-wk-p/gi, '');
7039
7040
7041                 //Remove bogus LI's
7042                 html = html.replace(/<li><\/li>/gi, '');
7043                 html = html.replace(/<li> <\/li>/gi, '');
7044                 html = html.replace(/<li>  <\/li>/gi, '');
7045                 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
7046                 if (this.get('ptags')) {
7047                             html = html.replace(/<div([^>]*)>/g, '<p$1>');
7048                                     html = html.replace(/<\/div>/gi, '</p>');
7049                 } else {
7050                     //html = html.replace(/<div>/gi, '<br>');
7051                     html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>');
7052                                     html = html.replace(/<\/div>/gi, '');
7053                 }
7054             }
7055             return html;
7056         },
7057         /**
7058         * @method filter_internals
7059         * @param String html The HTML string to filter
7060         * @description Filters internal RTE strings and bogus attrs we don't want
7061         * @return String
7062         */
7063         filter_internals: function(html) {
7064                     html = html.replace(/\r/g, '');
7065             //Fix stuff we don't want
7066                 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
7067             //Fix last BR in LI
7068                     html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
7069
7070                     html = html.replace(/yui-tag-span/gi, '');
7071                     html = html.replace(/yui-tag/gi, '');
7072                     html = html.replace(/yui-non/gi, '');
7073                     html = html.replace(/yui-img/gi, '');
7074                     html = html.replace(/ tag="span"/gi, '');
7075                     html = html.replace(/ class=""/gi, '');
7076                     html = html.replace(/ style=""/gi, '');
7077                     html = html.replace(/ class=" "/gi, '');
7078                     html = html.replace(/ class="  "/gi, '');
7079                     html = html.replace(/ target=""/gi, '');
7080                     html = html.replace(/ title=""/gi, '');
7081
7082             if (this.browser.ie) {
7083                         html = html.replace(/ class= /gi, '');
7084                         html = html.replace(/ class= >/gi, '');
7085             }
7086             
7087             return html;
7088         },
7089         /**
7090         * @method filter_all_rgb
7091         * @param String str The HTML string to filter
7092         * @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"
7093         * @return String
7094         */
7095         filter_all_rgb: function(str) {
7096             var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
7097             var arr = str.match(exp);
7098             if (Lang.isArray(arr)) {
7099                 for (var i = 0; i < arr.length; i++) {
7100                     var color = this.filter_rgb(arr[i]);
7101                     str = str.replace(arr[i].toString(), color);
7102                 }
7103             }
7104             
7105             return str;
7106         },
7107         /**
7108         * @method filter_rgb
7109         * @param String css The CSS string containing rgb(#,#,#);
7110         * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
7111         * @return String
7112         */
7113         filter_rgb: function(css) {
7114             if (css.toLowerCase().indexOf('rgb') != -1) {
7115                 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
7116                 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
7117             
7118                 if (rgb.length == 5) {
7119                     var r = parseInt(rgb[1], 10).toString(16);
7120                     var g = parseInt(rgb[2], 10).toString(16);
7121                     var b = parseInt(rgb[3], 10).toString(16);
7122
7123                     r = r.length == 1 ? '0' + r : r;
7124                     g = g.length == 1 ? '0' + g : g;
7125                     b = b.length == 1 ? '0' + b : b;
7126
7127                     css = "#" + r + g + b;
7128                 }
7129             }
7130             return css;
7131         },
7132         /**
7133         * @method pre_filter_linebreaks
7134         * @param String html The HTML to filter
7135         * @param String markup The markup type to filter to
7136         * @description HTML Pre Filter
7137         * @return String
7138         */
7139         pre_filter_linebreaks: function(html, markup) {
7140             if (this.browser.webkit) {
7141                         html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
7142                         html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
7143             }
7144                     html = html.replace(/<br>/gi, '<YUI_BR>');
7145                     html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
7146                     html = html.replace(/<br\/>/gi, '<YUI_BR>');
7147                     html = html.replace(/<br \/>/gi, '<YUI_BR>');
7148                     html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
7149                     html = html.replace(/<p>(&nbsp;|&#160;)<\/p>/g, '<YUI_BR>');            
7150                     html = html.replace(/<p><br>&nbsp;<\/p>/gi, '<YUI_BR>');
7151                     html = html.replace(/<p>&nbsp;<\/p>/gi, '<YUI_BR>');
7152             //Fix last BR
7153                 html = html.replace(/<YUI_BR>$/, '');
7154             //Fix last BR in P
7155                 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
7156             if (this.browser.ie) {
7157                     html = html.replace(/&nbsp;&nbsp;&nbsp;&nbsp;/g, '\t');
7158             }
7159             return html;
7160         },
7161         /**
7162         * @method post_filter_linebreaks
7163         * @param String html The HTML to filter
7164         * @param String markup The markup type to filter to
7165         * @description HTML Pre Filter
7166         * @return String
7167         */
7168         post_filter_linebreaks: function(html, markup) {
7169             if (markup == 'xhtml') {
7170                         html = html.replace(/<YUI_BR>/g, '<br />');
7171             } else {
7172                         html = html.replace(/<YUI_BR>/g, '<br>');
7173             }
7174             return html;
7175         },
7176         /**
7177         * @method clearEditorDoc
7178         * @description Clear the doc of the Editor
7179         */
7180         clearEditorDoc: function() {
7181             this._getDoc().body.innerHTML = '&nbsp;';
7182         },
7183         /**
7184         * @method openWindow
7185         * @description Override Method for Advanced Editor
7186         */
7187         openWindow: function(win) {
7188         },
7189         /**
7190         * @method moveWindow
7191         * @description Override Method for Advanced Editor
7192         */
7193         moveWindow: function() {
7194         },
7195         /**
7196         * @private
7197         * @method _closeWindow
7198         * @description Override Method for Advanced Editor
7199         */
7200         _closeWindow: function() {
7201         },
7202         /**
7203         * @method closeWindow
7204         * @description Override Method for Advanced Editor
7205         */
7206         closeWindow: function() {
7207             //this.unsubscribeAll('afterExecCommand');
7208             this.toolbar.resetAllButtons();
7209             this.focus();        
7210         },
7211         /**
7212         * @method destroy
7213         * @description Destroys the editor, all of it's elements and objects.
7214         * @return {Boolean}
7215         */
7216         destroy: function() {
7217             if (this._nodeChangeDelayTimer) {
7218                 clearTimeout(this._nodeChangeDelayTimer);
7219             }
7220             this.hide();
7221         
7222             YAHOO.log('Destroying Editor', 'warn', 'SimpleEditor');
7223             if (this.resize) {
7224                 YAHOO.log('Destroying Resize', 'warn', 'SimpleEditor');
7225                 this.resize.destroy();
7226             }
7227             if (this.dd) {
7228                 YAHOO.log('Unreg DragDrop Instance', 'warn', 'SimpleEditor');
7229                 this.dd.unreg();
7230             }
7231             if (this.get('panel')) {
7232                 YAHOO.log('Destroying Editor Panel', 'warn', 'SimpleEditor');
7233                 this.get('panel').destroy();
7234             }
7235             this.saveHTML();
7236             this.toolbar.destroy();
7237             YAHOO.log('Restoring TextArea', 'info', 'SimpleEditor');
7238             this.setStyle('visibility', 'visible');
7239             this.setStyle('position', 'static');
7240             this.setStyle('top', '');
7241             this.setStyle('left', '');
7242             var textArea = this.get('element');
7243             this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7244             this.get('element_cont').get('element').innerHTML = '';
7245             this.set('handleSubmit', false); //Remove the submit handler
7246             return true;
7247         },        
7248         /**
7249         * @method toString
7250         * @description Returns a string representing the editor.
7251         * @return {String}
7252         */
7253         toString: function() {
7254             var str = 'SimpleEditor';
7255             if (this.get && this.get('element_cont')) {
7256                 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7257             }
7258             return str;
7259         }
7260     });
7261
7262 /**
7263 * @event toolbarLoaded
7264 * @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.
7265 * @type YAHOO.util.CustomEvent
7266 */
7267 /**
7268 * @event cleanHTML
7269 * @description Event is fired after the cleanHTML method is called.
7270 * @type YAHOO.util.CustomEvent
7271 */
7272 /**
7273 * @event afterRender
7274 * @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.
7275 * @type YAHOO.util.CustomEvent
7276 */
7277 /**
7278 * @event editorContentLoaded
7279 * @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.
7280 * @type YAHOO.util.CustomEvent
7281 */
7282 /**
7283 * @event beforeNodeChange
7284 * @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.
7285 * @type YAHOO.util.CustomEvent
7286 */
7287 /**
7288 * @event afterNodeChange
7289 * @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.
7290 * @type YAHOO.util.CustomEvent
7291 */
7292 /**
7293 * @event beforeExecCommand
7294 * @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.
7295 * @type YAHOO.util.CustomEvent
7296 */
7297 /**
7298 * @event afterExecCommand
7299 * @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.
7300 * @type YAHOO.util.CustomEvent
7301 */
7302 /**
7303 * @event editorMouseUp
7304 * @param {Event} ev The DOM Event that occured
7305 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7306 * @type YAHOO.util.CustomEvent
7307 */
7308 /**
7309 * @event editorMouseDown
7310 * @param {Event} ev The DOM Event that occured
7311 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7312 * @type YAHOO.util.CustomEvent
7313 */
7314 /**
7315 * @event editorDoubleClick
7316 * @param {Event} ev The DOM Event that occured
7317 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7318 * @type YAHOO.util.CustomEvent
7319 */
7320 /**
7321 * @event editorClick
7322 * @param {Event} ev The DOM Event that occured
7323 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7324 * @type YAHOO.util.CustomEvent
7325 */
7326 /**
7327 * @event editorKeyUp
7328 * @param {Event} ev The DOM Event that occured
7329 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7330 * @type YAHOO.util.CustomEvent
7331 */
7332 /**
7333 * @event editorKeyPress
7334 * @param {Event} ev The DOM Event that occured
7335 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7336 * @type YAHOO.util.CustomEvent
7337 */
7338 /**
7339 * @event editorKeyDown
7340 * @param {Event} ev The DOM Event that occured
7341 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7342 * @type YAHOO.util.CustomEvent
7343 */
7344 /**
7345 * @event beforeEditorMouseUp
7346 * @param {Event} ev The DOM Event that occured
7347 * @description Fires before editor event, returning false will stop the internal processing.
7348 * @type YAHOO.util.CustomEvent
7349 */
7350 /**
7351 * @event beforeEditorMouseDown
7352 * @param {Event} ev The DOM Event that occured
7353 * @description Fires before editor event, returning false will stop the internal processing.
7354 * @type YAHOO.util.CustomEvent
7355 */
7356 /**
7357 * @event beforeEditorDoubleClick
7358 * @param {Event} ev The DOM Event that occured
7359 * @description Fires before editor event, returning false will stop the internal processing.
7360 * @type YAHOO.util.CustomEvent
7361 */
7362 /**
7363 * @event beforeEditorClick
7364 * @param {Event} ev The DOM Event that occured
7365 * @description Fires before editor event, returning false will stop the internal processing.
7366 * @type YAHOO.util.CustomEvent
7367 */
7368 /**
7369 * @event beforeEditorKeyUp
7370 * @param {Event} ev The DOM Event that occured
7371 * @description Fires before editor event, returning false will stop the internal processing.
7372 * @type YAHOO.util.CustomEvent
7373 */
7374 /**
7375 * @event beforeEditorKeyPress
7376 * @param {Event} ev The DOM Event that occured
7377 * @description Fires before editor event, returning false will stop the internal processing.
7378 * @type YAHOO.util.CustomEvent
7379 */
7380 /**
7381 * @event beforeEditorKeyDown
7382 * @param {Event} ev The DOM Event that occured
7383 * @description Fires before editor event, returning false will stop the internal processing.
7384 * @type YAHOO.util.CustomEvent
7385 */
7386
7387 /**
7388 * @event editorWindowFocus
7389 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7390 * @type YAHOO.util.CustomEvent
7391 */
7392 /**
7393 * @event editorWindowBlur
7394 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7395 * @type YAHOO.util.CustomEvent
7396 */
7397
7398
7399 /**
7400  * @description Singleton object used to track the open window objects and panels across the various open editors
7401  * @class EditorInfo
7402  * @static
7403 */
7404 YAHOO.widget.EditorInfo = {
7405     /**
7406     * @private
7407     * @property _instances
7408     * @description A reference to all editors on the page.
7409     * @type Object
7410     */
7411     _instances: {},
7412     /**
7413     * @private
7414     * @property blankImage
7415     * @description A reference to the blankImage url
7416     * @type String 
7417     */
7418     blankImage: '',
7419     /**
7420     * @private
7421     * @property window
7422     * @description A reference to the currently open window object in any editor on the page.
7423     * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7424     */
7425     window: {},
7426     /**
7427     * @private
7428     * @property panel
7429     * @description A reference to the currently open panel in any editor on the page.
7430     * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7431     */
7432     panel: null,
7433     /**
7434     * @method getEditorById
7435     * @description Returns a reference to the Editor object associated with the given textarea
7436     * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7437     * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7438     */
7439     getEditorById: function(id) {
7440         if (!YAHOO.lang.isString(id)) {
7441             //Not a string, assume a node Reference
7442             id = id.id;
7443         }
7444         if (this._instances[id]) {
7445             return this._instances[id];
7446         }
7447         return false;
7448     },
7449     /**
7450     * @method saveAll
7451     * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved.
7452     * @param {HTMLElement} form The form to check if this Editor instance belongs to
7453     */
7454     saveAll: function(form) {
7455         var i, e, items = YAHOO.widget.EditorInfo._instances;
7456         if (form) {
7457             for (i in items) {
7458                 if (Lang.hasOwnProperty(items, i)) {
7459                     e = items[i];
7460                     if (e.get('element').form && (e.get('element').form == form)) {
7461                         e.saveHTML();
7462                     }
7463                 }
7464             }
7465         } else {
7466             for (i in items) {
7467                 if (Lang.hasOwnProperty(items, i)) {
7468                     items[i].saveHTML();
7469                 }
7470             }
7471         }
7472     },
7473     /**
7474     * @method toString
7475     * @description Returns a string representing the EditorInfo.
7476     * @return {String}
7477     */
7478     toString: function() {
7479         var len = 0;
7480         for (var i in this._instances) {
7481             if (Lang.hasOwnProperty(this._instances, i)) {
7482                 len++;
7483             }
7484         }
7485         return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7486     }
7487 };
7488
7489
7490
7491     
7492 })();
7493 /**
7494  * @module editor
7495  * @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>
7496  * @namespace YAHOO.widget
7497  * @requires yahoo, dom, element, event, container_core, simpleeditor
7498  * @optional dragdrop, animation, menu, button, resize
7499  */
7500
7501 (function() {
7502 var Dom = YAHOO.util.Dom,
7503     Event = YAHOO.util.Event,
7504     Lang = YAHOO.lang,
7505     Toolbar = YAHOO.widget.Toolbar;
7506
7507     /**
7508      * 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.
7509      * @constructor
7510      * @class Editor
7511      * @extends YAHOO.widget.SimpleEditor
7512      * @param {String/HTMLElement} el The textarea element to turn into an editor.
7513      * @param {Object} attrs Object liternal containing configuration parameters.
7514     */
7515     
7516     YAHOO.widget.Editor = function(el, attrs) {
7517         YAHOO.log('Editor Initalizing', 'info', 'Editor');
7518         YAHOO.widget.Editor.superclass.constructor.call(this, el, attrs);
7519     };
7520
7521     YAHOO.extend(YAHOO.widget.Editor, YAHOO.widget.SimpleEditor, {
7522         /**
7523         * @private
7524         * @property _undoCache
7525         * @description An Array hash of the Undo Levels.
7526         * @type Array
7527         */
7528         _undoCache: null,
7529         /**
7530         * @private
7531         * @property _undoLevel
7532         * @description The index of the current undo state.
7533         * @type Number
7534         */
7535         _undoLevel: null,    
7536         /**
7537         * @private
7538         * @method _hasUndoLevel
7539         * @description Checks to see if we have an undo level available
7540         * @return Boolean
7541         */
7542         _hasUndoLevel: function() {
7543             return ((this._undoCache.length > 1) && this._undoLevel);
7544         },
7545         /**
7546         * @private
7547         * @method _undoNodeChange
7548         * @description nodeChange listener for undo processing
7549         */
7550         _undoNodeChange: function() {
7551             var undo_button = this.toolbar.getButtonByValue('undo'),
7552                 redo_button = this.toolbar.getButtonByValue('redo');
7553             if (undo_button && redo_button) {
7554                 if (this._hasUndoLevel()) {
7555                     this.toolbar.enableButton(undo_button);
7556                 }
7557                 if (this._undoLevel < this._undoCache.length) {
7558                     this.toolbar.enableButton(redo_button);
7559                 }
7560             }
7561             this._lastCommand = null;
7562         },
7563         /**
7564         * @private
7565         * @method _checkUndo
7566         * @description Prunes the undo cache when it reaches the maxUndo config
7567         */
7568         _checkUndo: function() {
7569             var len = this._undoCache.length,
7570             tmp = [];
7571             if (len >= this.get('maxUndo')) {
7572                 //YAHOO.log('Undo cache too large (' + len + '), pruning..', 'info', 'SimpleEditor');
7573                 for (var i = (len - this.get('maxUndo')); i < len; i++) {
7574                     tmp.push(this._undoCache[i]);
7575                 }
7576                 this._undoCache = tmp;
7577             }
7578         },
7579         /**
7580         * @private
7581         * @method _putUndo
7582         * @description Puts the content of the Editor into the _undoCache.
7583         * //TODO Convert the hash to a series of TEXTAREAS to store state in.
7584         * @param {String} str The content of the Editor
7585         */
7586         _putUndo: function(str) {
7587             if (this._undoLevel === this._undoCache.length) {
7588                 this._undoCache.push(str);
7589                 this._undoLevel = this._undoCache.length;
7590             } else {
7591                 var str = this.getEditorHTML();
7592                 var last = this._undoCache[this._undoLevel];
7593                 if (last) {
7594                     if (str !== last) {
7595                         this._undoCache = [];
7596                         this._undoLevel = 0;
7597                     }
7598                 }
7599             }
7600         },
7601         /**
7602         * @private
7603         * @method _getUndo
7604         * @description Get's a level from the undo cache.
7605         * @param {Number} index The index of the undo level we want to get.
7606         * @return {String}
7607         */
7608         _getUndo: function(index) {
7609             this._undoLevel = index;
7610             return this._undoCache[index];
7611         },
7612         /**
7613         * @private
7614         * @method _storeUndo
7615         * @description Method to call when you want to store an undo state. Currently called from nodeChange and _handleKeyUp
7616         */
7617         _storeUndo: function() {
7618             if (this._lastCommand === 'undo' || this._lastCommand === 'redo') {
7619                 return false;
7620             }
7621             if (!this._undoCache) {
7622                 this._undoCache = [];
7623                 this._undoLevel = 0;
7624             }
7625             this._checkUndo();
7626             var str = this.getEditorHTML();
7627             //var last = this._undoCache[this._undoCache.length - 1];
7628             var last = this._undoCache[this._undoLevel - 1];
7629             if (last) {
7630                 if (str !== last) {
7631                     //YAHOO.log('Storing Undo', 'info', 'SimpleEditor');
7632                     this._putUndo(str);
7633                 }
7634             } else {
7635                 //YAHOO.log('Storing Undo', 'info', 'SimpleEditor');
7636                 this._putUndo(str);
7637             }
7638             this._undoNodeChange();
7639         },    
7640         /**
7641         * @property STR_BEFORE_EDITOR
7642         * @description The accessibility string for the element before the iFrame
7643         * @type String
7644         */
7645         STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Control + Shift + T to place focus on the toolbar and navigate between option heading names. <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 [ aligns text left</li> <li>Control Shift | centers text</li> <li>Control Shift ] aligns text right</li> <li>Control Shift L adds an HTML link</li> <li>To exit this text editor use the keyboard shortcut Control + Shift + ESC.</li></ul>',    
7646         /**
7647         * @property STR_CLOSE_WINDOW
7648         * @description The Title of the close button in the Editor Window
7649         * @type String
7650         */
7651         STR_CLOSE_WINDOW: 'Close Window',
7652         /**
7653         * @property STR_CLOSE_WINDOW_NOTE
7654         * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
7655         * @type String
7656         */
7657         STR_CLOSE_WINDOW_NOTE: 'To close this window use the Control + Shift + W key',
7658         /**
7659         * @property STR_IMAGE_PROP_TITLE
7660         * @description The title for the Image Property Editor Window
7661         * @type String
7662         */
7663         STR_IMAGE_PROP_TITLE: 'Image Options',
7664         /**
7665         * @property STR_IMAGE_TITLE
7666         * @description The label string for Image Description
7667         * @type String
7668         */
7669         STR_IMAGE_TITLE: 'Description',
7670         /**
7671         * @property STR_IMAGE_SIZE
7672         * @description The label string for Image Size
7673         * @type String
7674         */
7675         STR_IMAGE_SIZE: 'Size',
7676         /**
7677         * @property STR_IMAGE_ORIG_SIZE
7678         * @description The label string for Original Image Size
7679         * @type String
7680         */
7681         STR_IMAGE_ORIG_SIZE: 'Original Size',
7682         /**
7683         * @property STR_IMAGE_COPY
7684         * @description The label string for the image copy and paste message for Opera and Safari
7685         * @type String
7686         */
7687         STR_IMAGE_COPY: '<span class="tip"><span class="icon icon-info"></span><strong>Note:</strong>To move this image just highlight it, cut, and paste where ever you\'d like.</span>',
7688         /**
7689         * @property STR_IMAGE_PADDING
7690         * @description The label string for the image padding.
7691         * @type String
7692         */
7693         STR_IMAGE_PADDING: 'Padding',
7694         /**
7695         * @property STR_IMAGE_BORDER
7696         * @description The label string for the image border.
7697         * @type String
7698         */
7699         STR_IMAGE_BORDER: 'Border',
7700         /**
7701         * @property STR_IMAGE_BORDER_SIZE
7702         * @description The label string for the image border size.
7703         * @type String
7704         */
7705         STR_IMAGE_BORDER_SIZE: 'Border Size',
7706         /**
7707         * @property STR_IMAGE_BORDER_TYPE
7708         * @description The label string for the image border type.
7709         * @type String
7710         */
7711         STR_IMAGE_BORDER_TYPE: 'Border Type',
7712         /**
7713         * @property STR_IMAGE_TEXTFLOW
7714         * @description The label string for the image text flow.
7715         * @type String
7716         */
7717         STR_IMAGE_TEXTFLOW: 'Text Flow',
7718         /**
7719         * @property STR_LOCAL_FILE_WARNING
7720         * @description The label string for the local file warning.
7721         * @type String
7722         */
7723         STR_LOCAL_FILE_WARNING: '<span class="tip"><span class="icon icon-warn"></span><strong>Note:</strong>This image/link points to a file on your computer and will not be accessible to others on the internet.</span>',
7724         /**
7725         * @property STR_LINK_PROP_TITLE
7726         * @description The label string for the Link Property Editor Window.
7727         * @type String
7728         */
7729         STR_LINK_PROP_TITLE: 'Link Options',
7730         /**
7731         * @property STR_LINK_PROP_REMOVE
7732         * @description The label string for the Remove link from text link inside the property editor.
7733         * @type String
7734         */
7735         STR_LINK_PROP_REMOVE: 'Remove link from text',
7736         /**
7737         * @property STR_LINK_NEW_WINDOW
7738         * @description The string for the open in a new window label.
7739         * @type String
7740         */
7741         STR_LINK_NEW_WINDOW: 'Open in a new window.',
7742         /**
7743         * @property STR_LINK_TITLE
7744         * @description The string for the link description.
7745         * @type String
7746         */
7747         STR_LINK_TITLE: 'Description',
7748         /**
7749         * @property STR_NONE
7750         * @description The string for the word none.
7751         * @type String
7752         */
7753         STR_NONE: 'none',
7754         /**
7755         * @protected
7756         * @property CLASS_LOCAL_FILE
7757         * @description CSS class applied to an element when it's found to have a local url.
7758         * @type String
7759         */
7760         CLASS_LOCAL_FILE: 'warning-localfile',
7761         /**
7762         * @protected
7763         * @property CLASS_HIDDEN
7764         * @description CSS class applied to the body when the hiddenelements button is pressed.
7765         * @type String
7766         */
7767         CLASS_HIDDEN: 'yui-hidden',
7768         /** 
7769         * @method init
7770         * @description The Editor class' initialization method
7771         */
7772         init: function(p_oElement, p_oAttributes) {
7773             YAHOO.log('init', 'info', 'Editor');
7774             
7775             this._windows = {};
7776             if (!this._defaultToolbar) {            
7777                 this._defaultToolbar = {
7778                     collapse: true,
7779                     titlebar: 'Text Editing Tools',
7780                     draggable: false,
7781                     buttonType: 'advanced',
7782                     buttons: [
7783                         { group: 'fontstyle', label: 'Font Name and Size',
7784                             buttons: [
7785                                 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
7786                                     menu: [
7787                                         { text: 'Arial', checked: true },
7788                                         { text: 'Arial Black' },
7789                                         { text: 'Comic Sans MS' },
7790                                         { text: 'Courier New' },
7791                                         { text: 'Lucida Console' },
7792                                         { text: 'Tahoma' },
7793                                         { text: 'Times New Roman' },
7794                                         { text: 'Trebuchet MS' },
7795                                         { text: 'Verdana' }
7796                                     ]
7797                                 },
7798                                 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
7799                             ]
7800                         },
7801                         { type: 'separator' },
7802                         { group: 'textstyle', label: 'Font Style',
7803                             buttons: [
7804                                 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
7805                                 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
7806                                 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
7807                                 { type: 'separator' },
7808                                 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true },
7809                                 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true }
7810                             ]
7811                         },
7812                         { type: 'separator' },
7813                         { group: 'textstyle2', label: '&nbsp;',
7814                             buttons: [
7815                                 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
7816                                 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true },
7817                                 { type: 'separator' },
7818                                 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true },
7819                                 { type: 'push', label: 'Show/Hide Hidden Elements', value: 'hiddenelements' }
7820                             ]
7821                         },
7822                         { type: 'separator' },
7823                         { group: 'undoredo', label: 'Undo/Redo',
7824                             buttons: [
7825                                 { type: 'push', label: 'Undo', value: 'undo', disabled: true },
7826                                 { type: 'push', label: 'Redo', value: 'redo', disabled: true }
7827                                 
7828                             ]
7829                         },
7830                         { type: 'separator' },
7831                         { group: 'alignment', label: 'Alignment',
7832                             buttons: [
7833                                 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
7834                                 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
7835                                 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
7836                                 { type: 'push', label: 'Justify', value: 'justifyfull' }
7837                             ]
7838                         },
7839                         { type: 'separator' },
7840                         { group: 'parastyle', label: 'Paragraph Style',
7841                             buttons: [
7842                             { type: 'select', label: 'Normal', value: 'heading', disabled: true,
7843                                 menu: [
7844                                     { text: 'Normal', value: 'none', checked: true },
7845                                     { text: 'Header 1', value: 'h1' },
7846                                     { text: 'Header 2', value: 'h2' },
7847                                     { text: 'Header 3', value: 'h3' },
7848                                     { text: 'Header 4', value: 'h4' },
7849                                     { text: 'Header 5', value: 'h5' },
7850                                     { text: 'Header 6', value: 'h6' }
7851                                 ]
7852                             }
7853                             ]
7854                         },
7855                         { type: 'separator' },
7856                         
7857                         { group: 'indentlist2', label: 'Indenting and Lists',
7858                             buttons: [
7859                                 { type: 'push', label: 'Indent', value: 'indent', disabled: true },
7860                                 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
7861                                 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
7862                                 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
7863                             ]
7864                         },
7865                         { type: 'separator' },
7866                         { group: 'insertitem', label: 'Insert Item',
7867                             buttons: [
7868                                 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
7869                                 { type: 'push', label: 'Insert Image', value: 'insertimage' }
7870                             ]
7871                         }
7872                     ]
7873                 };
7874             }
7875
7876             if (!this._defaultImageToolbarConfig) {
7877                 this._defaultImageToolbarConfig = {
7878                     buttonType: this._defaultToolbar.buttonType,
7879                     buttons: [
7880                         { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
7881                             buttons: [
7882                                 { type: 'push', label: 'Left', value: 'left' },
7883                                 { type: 'push', label: 'Inline', value: 'inline' },
7884                                 { type: 'push', label: 'Block', value: 'block' },
7885                                 { type: 'push', label: 'Right', value: 'right' }
7886                             ]
7887                         },
7888                         { type: 'separator' },
7889                         { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
7890                             buttons: [
7891                                 { type: 'spin', label: '0', value: 'padding', range: [0, 50] }
7892                             ]
7893                         },
7894                         { type: 'separator' },
7895                         { group: 'border', label: this.STR_IMAGE_BORDER + ':',
7896                             buttons: [
7897                                 { type: 'select', label: this.STR_IMAGE_BORDER_SIZE, value: 'bordersize',
7898                                     menu: [
7899                                         { text: 'none', value: '0', checked: true },
7900                                         { text: '1px', value: '1' },
7901                                         { text: '2px', value: '2' },
7902                                         { text: '3px', value: '3' },
7903                                         { text: '4px', value: '4' },
7904                                         { text: '5px', value: '5' }
7905                                     ]
7906                                 },
7907                                 { type: 'select', label: this.STR_IMAGE_BORDER_TYPE, value: 'bordertype', disabled: true,
7908                                     menu: [
7909                                         { text: 'Solid', value: 'solid', checked: true },
7910                                         { text: 'Dashed', value: 'dashed' },
7911                                         { text: 'Dotted', value: 'dotted' }
7912                                     ]
7913                                 },
7914                                 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
7915                             ]
7916                         }
7917                     ]
7918                 };
7919             }
7920
7921             YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes);
7922         },
7923         _render: function() {
7924             YAHOO.widget.Editor.superclass._render.apply(this, arguments);
7925             var self = this;
7926             //Render the panel in another thread and delay it a little..
7927             window.setTimeout(function() {
7928                 self._renderPanel.call(self);
7929             }, 800);
7930         },
7931         /**
7932         * @method initAttributes
7933         * @description Initializes all of the configuration attributes used to create 
7934         * the editor.
7935         * @param {Object} attr Object literal specifying a set of 
7936         * configuration attributes used to create the editor.
7937         */
7938         initAttributes: function(attr) {
7939             YAHOO.widget.Editor.superclass.initAttributes.call(this, attr);
7940
7941             /**
7942             * @attribute localFileWarning
7943             * @description Should we throw the warning if we detect a file that is local to their machine?
7944             * @default true
7945             * @type Boolean
7946             */            
7947             this.setAttributeConfig('localFileWarning', {
7948                 value: attr.locaFileWarning || true
7949             });
7950
7951             /**
7952             * @attribute hiddencss
7953             * @description The CSS used to show/hide hidden elements on the page, these rules must be prefixed with the class provided in <code>this.CLASS_HIDDEN</code>
7954             * @default <code><pre>
7955             .yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u,
7956             .yui-hidden div, .yui-hidden p, .yui-hidden span, .yui-hidden img, .yui-hidden ul, .yui-hidden ol,
7957             .yui-hidden li, .yui-hidden table {
7958                 border: 1px dotted #ccc;
7959             }
7960             .yui-hidden .yui-non {
7961                 border: none;
7962             }
7963             .yui-hidden img {
7964                 padding: 2px;
7965             }</pre></code>
7966             * @type String
7967             */            
7968             this.setAttributeConfig('hiddencss', {
7969                 value: attr.hiddencss || '.yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u, .yui-hidden div,.yui-hidden p,.yui-hidden span,.yui-hidden img, .yui-hidden ul, .yui-hidden ol, .yui-hidden li, .yui-hidden table { border: 1px dotted #ccc; } .yui-hidden .yui-non { border: none; } .yui-hidden img { padding: 2px; }',
7970                 writeOnce: true
7971             });
7972            
7973         },
7974         /**
7975         * @private
7976         * @method _windows
7977         * @description A reference to the HTML elements used for the body of Editor Windows.
7978         */
7979         _windows: null,
7980         /**
7981         * @private
7982         * @method _defaultImageToolbar
7983         * @description A reference to the Toolbar Object inside Image Editor Window.
7984         */
7985         _defaultImageToolbar: null,
7986         /**
7987         * @private
7988         * @method _defaultImageToolbarConfig
7989         * @description Config to be used for the default Image Editor Window.
7990         */
7991         _defaultImageToolbarConfig: null,
7992         /**
7993         * @private
7994         * @method _fixNodes
7995         * @description Fix href and imgs as well as remove invalid HTML.
7996         */
7997         _fixNodes: function() {
7998             YAHOO.widget.Editor.superclass._fixNodes.call(this);
7999             try {
8000                 var url = '';
8001
8002                 var imgs = this._getDoc().getElementsByTagName('img');
8003                 for (var im = 0; im < imgs.length; im++) {
8004                     if (imgs[im].getAttribute('href', 2)) {
8005                         url = imgs[im].getAttribute('src', 2);
8006                         if (this._isLocalFile(url)) {
8007                             Dom.addClass(imgs[im], this.CLASS_LOCAL_FILE);
8008                         } else {
8009                             Dom.removeClass(imgs[im], this.CLASS_LOCAL_FILE);
8010                         }
8011                     }
8012                 }
8013                 var fakeAs = this._getDoc().body.getElementsByTagName('a');
8014                 for (var a = 0; a < fakeAs.length; a++) {
8015                     if (fakeAs[a].getAttribute('href', 2)) {
8016                         url = fakeAs[a].getAttribute('href', 2);
8017                         if (this._isLocalFile(url)) {
8018                             Dom.addClass(fakeAs[a], this.CLASS_LOCAL_FILE);
8019                         } else {
8020                             Dom.removeClass(fakeAs[a], this.CLASS_LOCAL_FILE);
8021                         }
8022                     }
8023                 }
8024             } catch(e) {}
8025         },
8026         /**
8027         * @private
8028         * @property _disabled
8029         * @description The Toolbar items that should be disabled if there is no selection present in the editor.
8030         * @type Array
8031         */
8032         _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent' ],
8033         /**
8034         * @private
8035         * @property _alwaysDisabled
8036         * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
8037         * @type Object
8038         */
8039         _alwaysDisabled: { 'outdent': true },
8040         /**
8041         * @private
8042         * @property _alwaysEnabled
8043         * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
8044         * @type Object
8045         */
8046         _alwaysEnabled: { hiddenelements: true },
8047         /**
8048         * @private
8049         * @method _handleKeyDown
8050         * @param {Event} ev The event we are working on.
8051         * @description Override method that handles some new keydown events inside the iFrame document.
8052         */
8053         _handleKeyDown: function(ev) {
8054             YAHOO.widget.Editor.superclass._handleKeyDown.call(this, ev);
8055             var doExec = false,
8056                 action = null,
8057                 exec = false;
8058
8059             switch (ev.keyCode) {
8060                 //case 219: //Left
8061                 case this._keyMap.JUSTIFY_LEFT.key: //Left
8062                     if (this._checkKey(this._keyMap.JUSTIFY_LEFT, ev)) {
8063                         action = 'justifyleft';
8064                         doExec = true;
8065                     }
8066                     break;
8067                 //case 220: //Center
8068                 case this._keyMap.JUSTIFY_CENTER.key:
8069                     if (this._checkKey(this._keyMap.JUSTIFY_CENTER, ev)) {
8070                         action = 'justifycenter';
8071                         doExec = true;
8072                     }
8073                     break;
8074                 case 221: //Right
8075                 case this._keyMap.JUSTIFY_RIGHT.key:
8076                     if (this._checkKey(this._keyMap.JUSTIFY_RIGHT, ev)) {
8077                         action = 'justifyright';
8078                         doExec = true;
8079                     }
8080                     break;
8081             }
8082             if (doExec && action) {
8083                 this.execCommand(action, null);
8084                 Event.stopEvent(ev);
8085                 this.nodeChange();
8086             }
8087         },        
8088         /**
8089         * @private
8090         * @method _renderCreateLinkWindow
8091         * @description Pre renders the CreateLink window so we get faster window opening.
8092         */
8093         _renderCreateLinkWindow: function() {
8094                 var str = '<label for="' + this.get('id') + '_createlink_url"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_url" id="' + this.get('id') + '_createlink_url" value=""></label>';
8095                 str += '<label for="' + this.get('id') + '_createlink_target"><strong>&nbsp;</strong><input type="checkbox" name="' + this.get('id') + '_createlink_target" id="' + this.get('id') + '_createlink_target" value="_blank" class="createlink_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>';
8096                 str += '<label for="' + this.get('id') + '_createlink_title"><strong>' + this.STR_LINK_TITLE + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_title" id="' + this.get('id') + '_createlink_title" value=""></label>';
8097                 
8098                 var body = document.createElement('div');
8099                 body.innerHTML = str;
8100
8101                 var unlinkCont = document.createElement('div');
8102                 unlinkCont.className = 'removeLink';
8103                 var unlink = document.createElement('a');
8104                 unlink.href = '#';
8105                 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
8106                 unlink.title = this.STR_LINK_PROP_REMOVE;
8107                 Event.on(unlink, 'click', function(ev) {
8108                     Event.stopEvent(ev);
8109                     this.unsubscribeAll('afterExecCommand');
8110                     this.execCommand('unlink');
8111                     this.closeWindow();
8112                 }, this, true);
8113                 unlinkCont.appendChild(unlink);
8114                 body.appendChild(unlinkCont);
8115                 
8116                 this._windows.createlink = {};
8117                 this._windows.createlink.body = body;
8118                 //body.style.display = 'none';
8119                 Event.on(body, 'keyup', function(e) {
8120                     Event.stopPropagation(e);
8121                 });
8122                 this.get('panel').editor_form.appendChild(body);
8123                 this.fireEvent('windowCreateLinkRender', { type: 'windowCreateLinkRender', panel: this.get('panel'), body: body });
8124                 return body;
8125         },
8126         _handleCreateLinkClick: function() {
8127             var el = this._getSelectedElement();
8128             if (this._isElement(el, 'img')) {
8129                 this.STOP_EXEC_COMMAND = true;
8130                 this.currentElement[0] = el;
8131                 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
8132                 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
8133                 return false;
8134             }
8135             if (this.get('limitCommands')) {
8136                 if (!this.toolbar.getButtonByValue('createlink')) {
8137                     YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'Editor');
8138                     return false;
8139                 }
8140             }
8141             
8142             this.on('afterExecCommand', function() {
8143                 var win = new YAHOO.widget.EditorWindow('createlink', {
8144                     width: '350px'
8145                 });
8146                 
8147                 var el = this.currentElement[0],
8148                     url = '',
8149                     title = '',
8150                     target = '',
8151                     localFile = false;
8152                 if (el) {
8153                     win.el = el;
8154                     if (el.getAttribute('href', 2) !== null) {
8155                         url = el.getAttribute('href', 2);
8156                         if (this._isLocalFile(url)) {
8157                             //Local File throw Warning
8158                             YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8159                             win.setFooter(this.STR_LOCAL_FILE_WARNING);
8160                             localFile = true;
8161                         } else {
8162                             win.setFooter(' ');
8163                         }
8164                     }
8165                     if (el.getAttribute('title') !== null) {
8166                         title = el.getAttribute('title');
8167                     }
8168                     if (el.getAttribute('target') !== null) {
8169                         target = el.getAttribute('target');
8170                     }
8171                 }
8172                 var body = null;
8173                 if (this._windows.createlink && this._windows.createlink.body) {
8174                     body = this._windows.createlink.body;
8175                 } else {
8176                     body = this._renderCreateLinkWindow();
8177                 }
8178
8179                 win.setHeader(this.STR_LINK_PROP_TITLE);
8180                 win.setBody(body);
8181
8182                 Event.purgeElement(this.get('id') + '_createlink_url');
8183
8184                 Dom.get(this.get('id') + '_createlink_url').value = url;
8185                 Dom.get(this.get('id') + '_createlink_title').value = title;
8186                 Dom.get(this.get('id') + '_createlink_target').checked = ((target) ? true : false);
8187                 
8188
8189                 Event.onAvailable(this.get('id') + '_createlink_url', function() {
8190                     var id = this.get('id');
8191                     window.setTimeout(function() {
8192                         try {
8193                             YAHOO.util.Dom.get(id + '_createlink_url').focus();
8194                         } catch (e) {}
8195                     }, 50);
8196
8197                     if (this._isLocalFile(url)) {
8198                         //Local File throw Warning
8199                         Dom.addClass(this.get('id') + '_createlink_url', 'warning');
8200                         YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8201                         this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8202                     } else {
8203                         Dom.removeClass(this.get('id') + '_createlink_url', 'warning');
8204                         this.get('panel').setFooter(' ');
8205                     }
8206                     Event.on(this.get('id') + '_createlink_url', 'blur', function() {
8207                         var url = Dom.get(this.get('id') + '_createlink_url');
8208                         if (this._isLocalFile(url.value)) {
8209                             //Local File throw Warning
8210                             Dom.addClass(url, 'warning');
8211                             YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8212                             this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8213                         } else {
8214                             Dom.removeClass(url, 'warning');
8215                             this.get('panel').setFooter(' ');
8216                         }
8217                     }, this, true);
8218                 }, this, true);
8219                 
8220                 this.openWindow(win);
8221
8222             });
8223         },
8224         /**
8225         * @private
8226         * @method _handleCreateLinkWindowClose
8227         * @description Handles the closing of the Link Properties Window.
8228         */
8229         _handleCreateLinkWindowClose: function() {
8230             
8231             var url = Dom.get(this.get('id') + '_createlink_url'),
8232                 target = Dom.get(this.get('id') + '_createlink_target'),
8233                 title = Dom.get(this.get('id') + '_createlink_title'),
8234                 el = arguments[0].win.el,
8235                 a = el;
8236
8237             if (url && url.value) {
8238                 var urlValue = url.value;
8239                 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8240                     if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8241                         //Found an @ sign, prefix with mailto:
8242                         urlValue = 'mailto:' + urlValue;
8243                     } else {
8244                         // :// not found adding
8245                         if (urlValue.substring(0, 1) != '#') {
8246                             urlValue = 'http:/'+'/' + urlValue;
8247                         }
8248                         
8249                     }
8250                 }
8251                 el.setAttribute('href', urlValue);
8252                 if (target.checked) {
8253                     el.setAttribute('target', target.value);
8254                 } else {
8255                     el.setAttribute('target', '');
8256                 }
8257                 el.setAttribute('title', ((title.value) ? title.value : ''));
8258
8259             } else {
8260                 var _span = this._getDoc().createElement('span');
8261                 _span.innerHTML = el.innerHTML;
8262                 Dom.addClass(_span, 'yui-non');
8263                 el.parentNode.replaceChild(_span, el);
8264             }
8265             Dom.removeClass(url, 'warning');
8266             Dom.get(this.get('id') + '_createlink_url').value = '';
8267             Dom.get(this.get('id') + '_createlink_title').value = '';
8268             Dom.get(this.get('id') + '_createlink_target').checked = false;
8269             this.nodeChange();
8270             this.currentElement = [];
8271             
8272         },
8273         /**
8274         * @private
8275         * @method _renderInsertImageWindow
8276         * @description Pre renders the InsertImage window so we get faster window opening.
8277         */
8278         _renderInsertImageWindow: function() {
8279                 var el = this.currentElement[0];
8280                 var str = '<label for="' + this.get('id') + '_insertimage_url"><strong>' + this.STR_IMAGE_URL + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_url" value="" size="40"></label>';
8281                 var body = document.createElement('div');
8282                 body.innerHTML = str;
8283
8284                 var tbarCont = document.createElement('div');
8285                 tbarCont.id = this.get('id') + '_img_toolbar';
8286                 body.appendChild(tbarCont);
8287
8288                 var str2 = '<label for="' + this.get('id') + '_insertimage_title"><strong>' + this.STR_IMAGE_TITLE + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_title" value="" size="40"></label>';
8289                 str2 += '<label for="' + this.get('id') + '_insertimage_link"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_insertimage_link" id="' + this.get('id') + '_insertimage_link" value=""></label>';
8290                 str2 += '<label for="' + this.get('id') + '_insertimage_target"><strong>&nbsp;</strong><input type="checkbox" name="' + this.get('id') + '_insertimage_target_" id="' + this.get('id') + '_insertimage_target" value="_blank" class="insertimage_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>';
8291                 var div = document.createElement('div');
8292                 div.innerHTML = str2;
8293                 body.appendChild(div);
8294
8295                 var o = {};
8296                 Lang.augmentObject(o, this._defaultImageToolbarConfig); //Break the config reference
8297
8298                 var tbar = new YAHOO.widget.Toolbar(tbarCont, o);
8299                 tbar.editor_el = el;
8300                 this._defaultImageToolbar = tbar;
8301                 
8302                 var cont = tbar.get('cont');
8303                 var hw = document.createElement('div');
8304                 hw.className = 'yui-toolbar-group yui-toolbar-group-height-width height-width';
8305                 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
8306                 hw.innerHTML += '<span tabIndex="-1"><input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_width"> x <input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_height"></span>';
8307                 cont.insertBefore(hw, cont.firstChild);
8308
8309                 Event.onAvailable(this.get('id') + '_insertimage_width', function() {
8310                     Event.on(this.get('id') + '_insertimage_width', 'blur', function() {
8311                         var value = parseInt(Dom.get(this.get('id') + '_insertimage_width').value, 10);
8312                         if (value > 5) {
8313                            this._defaultImageToolbar.editor_el.style.width = value + 'px';
8314                             //Removed moveWindow call so the window doesn't jump
8315                             //this.moveWindow();
8316                         }
8317                     }, this, true);
8318                 }, this, true);
8319                 Event.onAvailable(this.get('id') + '_insertimage_height', function() {
8320                     Event.on(this.get('id') + '_insertimage_height', 'blur', function() {
8321                         var value = parseInt(Dom.get(this.get('id') + '_insertimage_height').value, 10);
8322                         if (value > 5) {
8323                             this._defaultImageToolbar.editor_el.style.height = value + 'px';
8324                             //Removed moveWindow call so the window doesn't jump
8325                             //this.moveWindow();
8326                         }
8327                     }, this, true);
8328                 }, this, true);
8329
8330
8331                 tbar.on('colorPickerClicked', function(o) {
8332                     var size = '1', type = 'solid', color = 'black', el = this._defaultImageToolbar.editor_el;
8333
8334                     if (el.style.borderLeftWidth) {
8335                         size = parseInt(el.style.borderLeftWidth, 10);
8336                     }
8337                     if (el.style.borderLeftStyle) {
8338                         type = el.style.borderLeftStyle;
8339                     }
8340                     if (el.style.borderLeftColor) {
8341                         color = el.style.borderLeftColor;
8342                     }
8343                     var borderString = size + 'px ' + type + ' #' + o.color;
8344                     el.style.border = borderString;
8345                 }, this, true);
8346
8347                 tbar.on('buttonClick', function(o) {
8348                     var value = o.button.value,
8349                         el = this._defaultImageToolbar.editor_el,
8350                         borderString = '';
8351                     if (o.button.menucmd) {
8352                         value = o.button.menucmd;
8353                     }
8354                     var size = '1', type = 'solid', color = 'black';
8355
8356                     /* All border calcs are done on the left border
8357                         since our default interface only supports
8358                         one border size/type and color */
8359                     if (el.style.borderLeftWidth) {
8360                         size = parseInt(el.style.borderLeftWidth, 10);
8361                     }
8362                     if (el.style.borderLeftStyle) {
8363                         type = el.style.borderLeftStyle;
8364                     }
8365                     if (el.style.borderLeftColor) {
8366                         color = el.style.borderLeftColor;
8367                     }
8368                     switch(value) {
8369                         case 'bordersize':
8370                             if (this.browser.webkit && this._lastImage) {
8371                                 Dom.removeClass(this._lastImage, 'selected');
8372                                 this._lastImage = null;
8373                             }
8374
8375                             borderString = parseInt(o.button.value, 10) + 'px ' + type + ' ' + color;
8376                             el.style.border = borderString;
8377                             if (parseInt(o.button.value, 10) > 0) {
8378                                 tbar.enableButton('bordertype');
8379                                 tbar.enableButton('bordercolor');
8380                             } else {
8381                                 tbar.disableButton('bordertype');
8382                                 tbar.disableButton('bordercolor');
8383                             }
8384                             break;
8385                         case 'bordertype':
8386                             if (this.browser.webkit && this._lastImage) {
8387                                 Dom.removeClass(this._lastImage, 'selected');
8388                                 this._lastImage = null;
8389                             }
8390                             borderString = size + 'px ' + o.button.value + ' ' + color;
8391                             el.style.border = borderString;
8392                             break;
8393                         case 'right':
8394                         case 'left':
8395                             tbar.deselectAllButtons();
8396                             el.style.display = '';
8397                             el.align = o.button.value;
8398                             break;
8399                         case 'inline':
8400                             tbar.deselectAllButtons();
8401                             el.style.display = '';
8402                             el.align = '';
8403                             break;
8404                         case 'block':
8405                             tbar.deselectAllButtons();
8406                             el.style.display = 'block';
8407                             el.align = 'center';
8408                             break;
8409                         case 'padding':
8410                             var _button = tbar.getButtonById(o.button.id);
8411                             el.style.margin = _button.get('label') + 'px';
8412                             break;
8413                     }
8414                     tbar.selectButton(o.button.value);
8415                     if (value !== 'padding') {
8416                         this.moveWindow();
8417                     }
8418                 }, this, true);
8419
8420
8421
8422                 if (this.get('localFileWarning')) {
8423                     Event.on(this.get('id') + '_insertimage_link', 'blur', function() {
8424                         var url = Dom.get(this.get('id') + '_insertimage_link');
8425                         if (this._isLocalFile(url.value)) {
8426                             //Local File throw Warning
8427                             Dom.addClass(url, 'warning');
8428                             YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8429                             this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8430                         } else {
8431                             Dom.removeClass(url, 'warning');
8432                             this.get('panel').setFooter(' ');
8433                             //Adobe AIR Code
8434                             if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {                
8435                                 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8436                             }
8437                         }
8438                     }, this, true);
8439                 }
8440
8441                 Event.on(this.get('id') + '_insertimage_url', 'blur', function() {
8442                     var url = Dom.get(this.get('id') + '_insertimage_url'),
8443                         el = this.currentElement[0];
8444
8445                     if (url.value && el) {
8446                         if (url.value == el.getAttribute('src', 2)) {
8447                             YAHOO.log('Images are the same, bail on blur handler', 'info', 'Editor');
8448                             return false;
8449                         }
8450                     }
8451                     YAHOO.log('Images are different, process blur handler', 'info', 'Editor');
8452                     if (this._isLocalFile(url.value)) {
8453                         //Local File throw Warning
8454                         Dom.addClass(url, 'warning');
8455                         YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8456                         this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8457                     } else if (this.currentElement[0]) {
8458                         Dom.removeClass(url, 'warning');
8459                         this.get('panel').setFooter(' ');
8460                         //Adobe AIR Code
8461                         if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {                
8462                             this.get('panel').setFooter(this.STR_IMAGE_COPY);
8463                         }
8464                         
8465                         if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8466                             this.currentElement[0].setAttribute('src', url.value);
8467                             var self = this,
8468                                 img = new Image();
8469
8470                             img.onerror = function() {
8471                                 url.value = self.STR_IMAGE_HERE;
8472                                 img.setAttribute('src', self.get('blankimage'));
8473                                 self.currentElement[0].setAttribute('src', self.get('blankimage'));
8474                                 YAHOO.util.Dom.get(self.get('id') + '_insertimage_height').value = img.height;
8475                                 YAHOO.util.Dom.get(self.get('id') + '_insertimage_width').value = img.width;
8476                             };
8477                             var id = this.get('id');
8478                             window.setTimeout(function() {
8479                                 YAHOO.util.Dom.get(id + '_insertimage_height').value = img.height;
8480                                 YAHOO.util.Dom.get(id + '_insertimage_width').value = img.width;
8481                                 if (self.currentElement && self.currentElement[0]) {
8482                                     if (!self.currentElement[0]._height) {
8483                                         self.currentElement[0]._height = img.height;
8484                                     }
8485                                     if (!self.currentElement[0]._width) {
8486                                         self.currentElement[0]._width = img.width;
8487                                     }
8488                                 }
8489                                 //Removed moveWindow call so the window doesn't jump
8490                                 //self.moveWindow();
8491                             }, 800); //Bumped the timeout up to account for larger images..
8492
8493                             if (url.value != this.STR_IMAGE_HERE) {
8494                                 img.src = url.value;
8495                             }
8496                         }
8497                     }
8498                     }, this, true);
8499
8500
8501
8502                 this._windows.insertimage = {};
8503                 this._windows.insertimage.body = body;
8504                 //body.style.display = 'none';
8505                 this.get('panel').editor_form.appendChild(body);
8506                 this.fireEvent('windowInsertImageRender', { type: 'windowInsertImageRender', panel: this.get('panel'), body: body, toolbar: tbar });
8507                 return body;
8508         },
8509         /**
8510         * @private
8511         * @method _handleInsertImageClick
8512         * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
8513         */
8514         _handleInsertImageClick: function() {
8515             if (this.get('limitCommands')) {
8516                 if (!this.toolbar.getButtonByValue('insertimage')) {
8517                     YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'Editor');
8518                     return false;
8519                 }
8520             }
8521             this.on('afterExecCommand', function() {
8522                 YAHOO.log('afterExecCommand :: _handleInsertImageClick', 'info', 'Editor');
8523                 var el = this.currentElement[0],
8524                     body = null,
8525                     link = '',
8526                     target = '',
8527                     tbar = null,
8528                     title = '',
8529                     src = '',
8530                     align = '',
8531                     height = 75,
8532                     width = 75,
8533                     padding = 0,
8534                     oheight = 0,
8535                     owidth = 0,
8536                     blankimage = false,
8537                     win = new YAHOO.widget.EditorWindow('insertimage', {
8538                         width: '415px'
8539                     });
8540
8541                 if (!el) {
8542                     el = this._getSelectedElement();
8543                 }
8544                 if (el) {
8545                     win.el = el;
8546                     if (el.getAttribute('src')) {
8547                         src = el.getAttribute('src', 2);
8548                         if (src.indexOf(this.get('blankimage')) != -1) {
8549                             src = this.STR_IMAGE_HERE;
8550                             blankimage = true;
8551                         }
8552                     }
8553                     if (el.getAttribute('alt', 2)) {
8554                         title = el.getAttribute('alt', 2);
8555                     }
8556                     if (el.getAttribute('title', 2)) {
8557                         title = el.getAttribute('title', 2);
8558                     }
8559
8560                     if (el.parentNode && this._isElement(el.parentNode, 'a')) {
8561                         link = el.parentNode.getAttribute('href', 2);
8562                         if (el.parentNode.getAttribute('target') !== null) {
8563                             target = el.parentNode.getAttribute('target');
8564                         }
8565                     }
8566                     height = parseInt(el.height, 10);
8567                     width = parseInt(el.width, 10);
8568                     if (el.style.height) {
8569                         height = parseInt(el.style.height, 10);
8570                     }
8571                     if (el.style.width) {
8572                         width = parseInt(el.style.width, 10);
8573                     }
8574                     if (el.style.margin) {
8575                         padding = parseInt(el.style.margin, 10);
8576                     }
8577                     if (!blankimage) {
8578                         if (!el._height) {
8579                             el._height = height;
8580                         }
8581                         if (!el._width) {
8582                             el._width = width;
8583                         }
8584                         oheight = el._height;
8585                         owidth = el._width;
8586                     }
8587                 }
8588                 if (this._windows.insertimage && this._windows.insertimage.body) {
8589                     body = this._windows.insertimage.body;
8590                     this._defaultImageToolbar.resetAllButtons();
8591                 } else {
8592                     body = this._renderInsertImageWindow();
8593                 }
8594
8595                 tbar = this._defaultImageToolbar;
8596                 tbar.editor_el = el;
8597                 
8598
8599                 var bsize = '0',
8600                     btype = 'solid';
8601
8602                 if (el.style.borderLeftWidth) {
8603                     bsize = parseInt(el.style.borderLeftWidth, 10);
8604                 }
8605                 if (el.style.borderLeftStyle) {
8606                     btype = el.style.borderLeftStyle;
8607                 }
8608                 var bs_button = tbar.getButtonByValue('bordersize'),
8609                     bSizeStr = ((parseInt(bsize, 10) > 0) ? '' : this.STR_NONE);
8610                 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">' + bSizeStr + '</span>');
8611                 this._updateMenuChecked('bordersize', bsize, tbar);
8612
8613                 var bt_button = tbar.getButtonByValue('bordertype');
8614                 bt_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">asdfa</span>');
8615                 this._updateMenuChecked('bordertype', btype, tbar);
8616                 if (parseInt(bsize, 10) > 0) {
8617                     tbar.enableButton(bt_button);
8618                     tbar.enableButton(bs_button);
8619                     tbar.enableButton('bordercolor');
8620                 }
8621
8622                 if ((el.align == 'right') || (el.align == 'left')) {
8623                     tbar.selectButton(el.align);
8624                 } else if (el.style.display == 'block') {
8625                     tbar.selectButton('block');
8626                 } else {
8627                     tbar.selectButton('inline');
8628                 }
8629                 if (parseInt(el.style.marginLeft, 10) > 0) {
8630                     tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft, 10));
8631                 }
8632                 if (el.style.borderSize) {
8633                     tbar.selectButton('bordersize');
8634                     tbar.selectButton(parseInt(el.style.borderSize, 10));
8635                 }
8636                 tbar.getButtonByValue('padding').set('label', ''+padding);
8637
8638
8639
8640                 win.setHeader(this.STR_IMAGE_PROP_TITLE);
8641                 win.setBody(body);
8642                 //Adobe AIR Code
8643                 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {                
8644                     win.setFooter(this.STR_IMAGE_COPY);
8645                 }
8646                 this.openWindow(win);
8647                 Dom.get(this.get('id') + '_insertimage_url').value = src;
8648                 Dom.get(this.get('id') + '_insertimage_title').value = title;
8649                 Dom.get(this.get('id') + '_insertimage_link').value = link;
8650                 Dom.get(this.get('id') + '_insertimage_target').checked = ((target) ? true : false);
8651                 Dom.get(this.get('id') + '_insertimage_width').value = width;
8652                 Dom.get(this.get('id') + '_insertimage_height').value = height;
8653
8654
8655                 if (((height != oheight) || (width != owidth)) && (!blankimage)) {
8656                     var s = document.createElement('span');
8657                     s.className = 'info';
8658                     s.innerHTML = this.STR_IMAGE_ORIG_SIZE + ': ('+ owidth +' x ' + oheight + ')';
8659                     if (Dom.get(this.get('id') + '_insertimage_height').nextSibling) {
8660                         var old = Dom.get(this.get('id') + '_insertimage_height').nextSibling;
8661                         old.parentNode.removeChild(old);
8662                     }
8663                     Dom.get(this.get('id') + '_insertimage_height').parentNode.appendChild(s);
8664                 }
8665
8666                 this.toolbar.selectButton('insertimage');
8667                 var id = this.get('id');
8668                 window.setTimeout(function() {
8669                     try {
8670                         YAHOO.util.Dom.get(id + '_insertimage_url').focus();
8671                         if (blankimage) {
8672                             YAHOO.util.Dom.get(id + '_insertimage_url').select();
8673                         }
8674                     } catch (e) {}
8675                 }, 50);
8676
8677             });
8678         },
8679         /**
8680         * @private
8681         * @method _handleInsertImageWindowClose
8682         * @description Handles the closing of the Image Properties Window.
8683         */
8684         _handleInsertImageWindowClose: function() {
8685             var url = Dom.get(this.get('id') + '_insertimage_url'),
8686                 title = Dom.get(this.get('id') + '_insertimage_title'),
8687                 link = Dom.get(this.get('id') + '_insertimage_link'),
8688                 target = Dom.get(this.get('id') + '_insertimage_target'),
8689                 el = arguments[0].win.el;
8690
8691             if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8692                 el.setAttribute('src', url.value);
8693                 el.setAttribute('title', title.value);
8694                 el.setAttribute('alt', title.value);
8695                 var par = el.parentNode;
8696                 if (link.value) {
8697                     var urlValue = link.value;
8698                     if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8699                         if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8700                             //Found an @ sign, prefix with mailto:
8701                             urlValue = 'mailto:' + urlValue;
8702                         } else {
8703                             // :// not found adding
8704                             urlValue = 'http:/'+'/' + urlValue;
8705                         }
8706                     }
8707                     if (par && this._isElement(par, 'a')) {
8708                         par.setAttribute('href', urlValue);
8709                         if (target.checked) {
8710                             par.setAttribute('target', target.value);
8711                         } else {
8712                             par.setAttribute('target', '');
8713                         }
8714                     } else {
8715                         var _a = this._getDoc().createElement('a');
8716                         _a.setAttribute('href', urlValue);
8717                         if (target.checked) {
8718                             _a.setAttribute('target', target.value);
8719                         } else {
8720                             _a.setAttribute('target', '');
8721                         }
8722                         el.parentNode.replaceChild(_a, el);
8723                         _a.appendChild(el);
8724                     }
8725                 } else {
8726                     if (par && this._isElement(par, 'a')) {
8727                         par.parentNode.replaceChild(el, par);
8728                     }
8729                 }
8730             } else {
8731                 //No url/src given, remove the node from the document
8732                 el.parentNode.removeChild(el);
8733             }
8734             Dom.get(this.get('id') + '_insertimage_url').value = '';
8735             Dom.get(this.get('id') + '_insertimage_title').value = '';
8736             Dom.get(this.get('id') + '_insertimage_link').value = '';
8737             Dom.get(this.get('id') + '_insertimage_target').checked = false;
8738             Dom.get(this.get('id') + '_insertimage_width').value = 0;
8739             Dom.get(this.get('id') + '_insertimage_height').value = 0;
8740             this._defaultImageToolbar.resetAllButtons();
8741             this.currentElement = [];
8742             this.nodeChange();
8743         },
8744         /**
8745         * @property EDITOR_PANEL_ID
8746         * @description HTML id to give the properties window in the DOM.
8747         * @type String
8748         */
8749         EDITOR_PANEL_ID: '-panel',
8750         /**
8751         * @private
8752         * @method _renderPanel
8753         * @description Renders the panel used for Editor Windows to the document so we can start using it..
8754         * @return {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>}
8755         */
8756         _renderPanel: function() {
8757             var panelEl = document.createElement('div');
8758             Dom.addClass(panelEl, 'yui-editor-panel');
8759             panelEl.id = this.get('id') + this.EDITOR_PANEL_ID;
8760             panelEl.style.position = 'absolute';
8761             panelEl.style.top = '-9999px';
8762             panelEl.style.left = '-9999px';
8763             document.body.appendChild(panelEl);
8764             this.get('element_cont').insertBefore(panelEl, this.get('element_cont').get('firstChild'));
8765
8766                 
8767
8768             var panel = new YAHOO.widget.Overlay(this.get('id') + this.EDITOR_PANEL_ID, {
8769                     width: '300px',
8770                     iframe: true,
8771                     visible: false,
8772                     underlay: 'none',
8773                     draggable: false,
8774                     close: false
8775                 });
8776             this.set('panel', panel);
8777
8778             panel.setBody('---');
8779             panel.setHeader(' ');
8780             panel.setFooter(' ');
8781
8782
8783             var body = document.createElement('div');
8784             body.className = this.CLASS_PREFIX + '-body-cont';
8785             for (var b in this.browser) {
8786                 if (this.browser[b]) {
8787                     Dom.addClass(body, b);
8788                     break;
8789                 }
8790             }
8791             Dom.addClass(body, ((YAHOO.widget.Button && (this._defaultToolbar.buttonType == 'advanced')) ? 'good-button' : 'no-button'));
8792
8793             var _note = document.createElement('h3');
8794             _note.className = 'yui-editor-skipheader';
8795             _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
8796             body.appendChild(_note);
8797             var form = document.createElement('fieldset');
8798             panel.editor_form = form;
8799
8800             body.appendChild(form);
8801             var _close = document.createElement('span');
8802             _close.innerHTML = 'X';
8803             _close.title = this.STR_CLOSE_WINDOW;
8804             _close.className = 'close';
8805             
8806             Event.on(_close, 'click', this.closeWindow, this, true);
8807
8808             var _knob = document.createElement('span');
8809             _knob.innerHTML = '^';
8810             _knob.className = 'knob';
8811             panel.editor_knob = _knob;
8812
8813             var _header = document.createElement('h3');
8814             panel.editor_header = _header;
8815             _header.innerHTML = '<span></span>';
8816
8817             panel.setHeader(' '); //Clear the current header
8818             panel.appendToHeader(_header);
8819             _header.appendChild(_close);
8820             _header.appendChild(_knob);
8821             panel.setBody(' '); //Clear the current body
8822             panel.setFooter(' '); //Clear the current footer
8823             panel.appendToBody(body); //Append the new DOM node to it
8824
8825             Event.on(panel.element, 'click', function(ev) {
8826                 Event.stopPropagation(ev);
8827             });
8828
8829             var fireShowEvent = function() {
8830                 panel.bringToTop();
8831                 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
8832                 this._handleWindowInputs(false);
8833             };
8834             panel.showEvent.subscribe(fireShowEvent, this, true);
8835             panel.hideEvent.subscribe(function() {
8836                 this._handleWindowInputs(true);
8837             }, this, true);
8838             panel.renderEvent.subscribe(function() {
8839                 this._renderInsertImageWindow();
8840                 this._renderCreateLinkWindow();
8841                 this.fireEvent('windowRender', { type: 'windowRender', panel: panel });
8842                 this._handleWindowInputs(true);
8843             }, this, true);
8844
8845             if (this.DOMReady) {
8846                 this.get('panel').render();
8847             } else {
8848                 Event.onDOMReady(function() {
8849                     this.get('panel').render();
8850                 }, this, true);
8851             }
8852             return this.get('panel');
8853         },
8854         /**
8855         * @method _handleWindowInputs
8856         * @param {Boolean} disable The state to set all inputs in all Editor windows to. Defaults to: false.
8857         * @description Disables/Enables all fields inside Editor windows. Used in show/hide events to keep window fields from submitting when the parent form is submitted.
8858         */
8859         _handleWindowInputs: function(disable) {
8860             if (!Lang.isBoolean(disable)) {
8861                 disable = false;
8862             }
8863             var inputs = this.get('panel').element.getElementsByTagName('input');
8864             for (var i = 0; i < inputs.length; i++) {
8865                 try {
8866                     inputs[i].disabled = disable;
8867                 } catch (e) {}
8868             }
8869         },
8870         /**
8871         * @method openWindow
8872         * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance
8873         * @description Opens a new "window/panel"
8874         */
8875         openWindow: function(win) {
8876             
8877             YAHOO.log('openWindow: ' + win.name, 'info', 'Editor');
8878             var self = this;
8879             window.setTimeout(function() {
8880                 self.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
8881             }, 10);
8882             Event.on(document, 'keydown', this._closeWindow, this, true);
8883             
8884             if (this.currentWindow) {
8885                 this.closeWindow();
8886             }
8887             
8888             var xy = Dom.getXY(this.currentElement[0]),
8889             elXY = Dom.getXY(this.get('iframe').get('element')),
8890             panel = this.get('panel'),
8891             newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8892             wWidth = (parseInt(win.attrs.width, 10) / 2),
8893             align = 'center',
8894             body = null;
8895
8896             this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
8897
8898             var form = panel.editor_form;
8899             
8900             var wins = this._windows;
8901             for (var b in wins) {
8902                 if (Lang.hasOwnProperty(wins, b)) {
8903                     if (wins[b] && wins[b].body) {
8904                         if (b == win.name) {
8905                             Dom.setStyle(wins[b].body, 'display', 'block');
8906                         } else {
8907                             Dom.setStyle(wins[b].body, 'display', 'none');
8908                         }
8909                     }
8910                 }
8911             }
8912             
8913             if (this._windows[win.name].body) {
8914                 Dom.setStyle(this._windows[win.name].body, 'display', 'block');
8915                 form.appendChild(this._windows[win.name].body);
8916             } else {
8917                 if (Lang.isObject(win.body)) { //Assume it's a reference
8918                     form.appendChild(win.body);
8919                 } else { //Assume it's a string
8920                     var _tmp = document.createElement('div');
8921                     _tmp.innerHTML = win.body;
8922                     form.appendChild(_tmp);
8923                 }
8924             }
8925             panel.editor_header.firstChild.innerHTML = win.header;
8926             if (win.footer !== null) {
8927                 panel.setFooter(win.footer);
8928             }
8929             panel.cfg.setProperty('width', win.attrs.width);
8930
8931             this.currentWindow = win;
8932             this.moveWindow(true);
8933             panel.show();
8934             this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
8935         },
8936         /**
8937         * @method moveWindow
8938         * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
8939         * @description Realign the window with the currentElement and reposition the knob above the panel.
8940         */
8941         moveWindow: function(force) {
8942             if (!this.currentWindow) {
8943                 return false;
8944             }
8945             var win = this.currentWindow,
8946                 xy = Dom.getXY(this.currentElement[0]),
8947                 elXY = Dom.getXY(this.get('iframe').get('element')),
8948                 panel = this.get('panel'),
8949                 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8950                 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
8951                 wWidth = (parseInt(win.attrs.width, 10) / 2),
8952                 align = 'center',
8953                 orgXY = panel.cfg.getProperty('xy') || [0,0],
8954                 _knob = panel.editor_knob,
8955                 xDiff = 0,
8956                 yDiff = 0,
8957                 anim = false;
8958
8959
8960             newXY[0] = ((newXY[0] - wWidth) + 20);
8961             //Account for the Scroll bars in a scrolled editor window.
8962             newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
8963             newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
8964             
8965             if (this._isElement(this.currentElement[0], 'img')) {
8966                 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
8967                     newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
8968                     newXY[1] = (newXY[1] + 75); //Placeholder sizea
8969                 } else {
8970                     var w = parseInt(this.currentElement[0].width, 10);
8971                     var h = parseInt(this.currentElement[0].height, 10);
8972                     newXY[0] = (newXY[0] + (w / 2));
8973                     newXY[1] = (newXY[1] + h);
8974                 }
8975                 newXY[1] = newXY[1] + 15;
8976             } else {
8977                 var fs = Dom.getStyle(this.currentElement[0], 'fontSize');
8978                 if (fs && fs.indexOf && fs.indexOf('px') != -1) {
8979                     newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize'), 10) + 5;
8980                 } else {
8981                     newXY[1] = newXY[1] + 20;
8982                 }
8983             }
8984             if (newXY[0] < elXY[0]) {
8985                 newXY[0] = elXY[0] + 5;
8986                 align = 'left';
8987             }
8988
8989             if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10))) {
8990                 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10)) - (wWidth * 2) - 5);
8991                 align = 'right';
8992             }
8993             
8994             try {
8995                 xDiff = (newXY[0] - orgXY[0]);
8996                 yDiff = (newXY[1] - orgXY[1]);
8997             } catch (e) {}
8998
8999
9000             var iTop = elXY[1] + parseInt(this.get('height'), 10);
9001             var iLeft = elXY[0] + parseInt(this.get('width'), 10);
9002             if (newXY[1] > iTop) {
9003                 newXY[1] = iTop;
9004             }
9005             if (newXY[0] > iLeft) {
9006                 newXY[0] = (iLeft / 2);
9007             }
9008             
9009             //Convert negative numbers to positive so we can get the difference in distance
9010             xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
9011             yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
9012
9013             if (((xDiff > 10) || (yDiff > 10)) || force) { //Only move the window if it's supposed to move more than 10px or force was passed (new window)
9014                 var _knobLeft = 0,
9015                     elW = 0;
9016
9017                 if (this.currentElement[0].width) {
9018                     elW = (parseInt(this.currentElement[0].width, 10) / 2);
9019                 }
9020
9021                 var leftOffset = xy[0] + elXY[0] + elW;
9022                 _knobLeft = leftOffset - newXY[0];
9023                 //Check to see if the knob will go off either side & reposition it
9024                 if (_knobLeft > (parseInt(win.attrs.width, 10) - 1)) {
9025                     _knobLeft = ((parseInt(win.attrs.width, 10) - 30) - 1);
9026                 } else if (_knobLeft < 40) {
9027                     _knobLeft = 1;
9028                 }
9029                 if (isNaN(_knobLeft)) {
9030                     _knobLeft = 1;
9031                 }
9032                 if (force) {
9033                     if (_knob) {
9034                         _knob.style.left = _knobLeft + 'px';
9035                     }
9036                     //Removed Animation from a forced move..
9037                     panel.cfg.setProperty('xy', newXY);
9038                 } else {
9039                     if (this.get('animate')) {
9040                         anim = new YAHOO.util.Anim(panel.element, {}, 0.5, YAHOO.util.Easing.easeOut);
9041                         anim.attributes = {
9042                             top: {
9043                                 to: newXY[1]
9044                             },
9045                             left: {
9046                                 to: newXY[0]
9047                             }
9048                         };
9049                         anim.onComplete.subscribe(function() {
9050                             panel.cfg.setProperty('xy', newXY);
9051                         });
9052                         //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
9053                         var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, 0.5, YAHOO.util.Easing.easeOut);
9054
9055                         var _knobAnim = new YAHOO.util.Anim(_knob, {
9056                             left: {
9057                                 to: _knobLeft
9058                             }
9059                         }, 0.6, YAHOO.util.Easing.easeOut);
9060                         anim.animate();
9061                         iframeAnim.animate();
9062                         _knobAnim.animate();
9063                     } else {
9064                         _knob.style.left = _knobLeft + 'px';
9065                         panel.cfg.setProperty('xy', newXY);
9066                     }
9067                 }
9068             }
9069         },
9070         /**
9071         * @private
9072         * @method _closeWindow
9073         * @description Close the currently open EditorWindow with the Escape key.
9074         * @param {Event} ev The keypress Event that we are trapping
9075         */
9076         _closeWindow: function(ev) {
9077             //if ((ev.charCode == 87) && ev.shiftKey && ev.ctrlKey) {
9078             if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {            
9079                 if (this.currentWindow) {
9080                     this.closeWindow();
9081                 }
9082             }
9083         },
9084         /**
9085         * @method closeWindow
9086         * @description Close the currently open EditorWindow.
9087         */
9088         closeWindow: function(keepOpen) {
9089             YAHOO.log('closeWindow: ' + this.currentWindow.name, 'info', 'Editor');
9090             this.fireEvent('window' + this.currentWindow.name + 'Close', { type: 'window' + this.currentWindow.name + 'Close', win: this.currentWindow, el: this.currentElement[0] });
9091             this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
9092             this.currentWindow = null;
9093             this.get('panel').hide();
9094             this.get('panel').cfg.setProperty('xy', [-900,-900]);
9095             this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
9096             this.unsubscribeAll('afterExecCommand');
9097             this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
9098             this.toolbar.resetAllButtons();
9099             this.focus();
9100             Event.removeListener(document, 'keydown', this._closeWindow);
9101         },
9102
9103         /* {{{  Command Overrides - These commands are only over written when we are using the advanced version */
9104         
9105         /**
9106         * @method cmd_undo
9107         * @description Pulls an item from the Undo stack and updates the Editor
9108         * @param value Value passed from the execCommand method
9109         */
9110         cmd_undo: function(value) {
9111             if (this._hasUndoLevel()) {
9112                 var c_html = this.getEditorHTML(), html;
9113                 if (!this._undoLevel) {
9114                     this._undoLevel = this._undoCache.length;
9115                 }
9116                 this._undoLevel = (this._undoLevel - 1);
9117                 if (this._undoCache[this._undoLevel]) {
9118                     html = this._getUndo(this._undoLevel);
9119                     if (html != c_html) {
9120                         this.setEditorHTML(html);
9121                     } else {
9122                         this._undoLevel = (this._undoLevel - 1);
9123                         html = this._getUndo(this._undoLevel);
9124                         if (html != c_html) {
9125                             this.setEditorHTML(html);
9126                         }
9127                     }
9128                 } else {
9129                     this._undoLevel = 0;
9130                     this.toolbar.disableButton('undo');
9131                 }
9132             }
9133             return [false];
9134         },
9135
9136         /**
9137         * @method cmd_redo
9138         * @description Pulls an item from the Undo stack and updates the Editor
9139         * @param value Value passed from the execCommand method
9140         */
9141         cmd_redo: function(value) {
9142             this._undoLevel = this._undoLevel + 1;
9143             if (this._undoLevel >= this._undoCache.length) {
9144                 this._undoLevel = this._undoCache.length;
9145             }
9146             YAHOO.log(this._undoLevel + ' :: ' + this._undoCache.length, 'warn', 'SimpleEditor');
9147             if (this._undoCache[this._undoLevel]) {
9148                 var html = this._getUndo(this._undoLevel);
9149                 this.setEditorHTML(html);
9150             } else {
9151                 this.toolbar.disableButton('redo');
9152             }
9153             return [false];
9154         },       
9155         
9156         /**
9157         * @method cmd_heading
9158         * @param value Value passed from the execCommand method
9159         * @description This is an execCommand override method. It is called from execCommand when the execCommand('heading') is used.
9160         */
9161         cmd_heading: function(value) {
9162             var exec = true,
9163                 el = null,
9164                 action = 'heading',
9165                 _sel = this._getSelection(),
9166                 _selEl = this._getSelectedElement();
9167
9168             if (_selEl) {
9169                 _sel = _selEl;
9170             }
9171             
9172             if (this.browser.ie) {
9173                 action = 'formatblock';
9174             }
9175             if (value == this.STR_NONE) {
9176                 if ((_sel && _sel.tagName && (_sel.tagName.toLowerCase().substring(0,1) == 'h')) || (_sel && _sel.parentNode && _sel.parentNode.tagName && (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h'))) {
9177                     if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
9178                         _sel = _sel.parentNode;
9179                     }
9180                     if (this._isElement(_sel, 'html')) {
9181                         return [false];
9182                     }
9183                     el = this._swapEl(_selEl, 'span', function(el) {
9184                         el.className = 'yui-non';
9185                     });
9186                     this._selectNode(el);
9187                     this.currentElement[0] = el;
9188                 }
9189                 exec = false;
9190             } else {
9191                 if (this._isElement(_selEl, 'h1') || this._isElement(_selEl, 'h2') || this._isElement(_selEl, 'h3') || this._isElement(_selEl, 'h4') || this._isElement(_selEl, 'h5') || this._isElement(_selEl, 'h6')) {
9192                     el = this._swapEl(_selEl, value);
9193                     this._selectNode(el);
9194                     this.currentElement[0] = el;
9195                 } else {
9196                     this._createCurrentElement(value);
9197                     this._selectNode(this.currentElement[0]);
9198                 }
9199                 exec = false;
9200             }
9201             return [exec, action];
9202         },
9203         /**
9204         * @method cmd_hiddenelements
9205         * @param value Value passed from the execCommand method
9206         * @description This is an execCommand override method. It is called from execCommand when the execCommand('hiddenelements') is used.
9207         */
9208         cmd_hiddenelements: function(value) {
9209             if (this._showingHiddenElements) {
9210                 //Don't auto highlight the hidden button
9211                 this._lastButton = null;
9212                 YAHOO.log('Enabling hidden CSS File', 'info', 'SimpleEditor');
9213                 this._showingHiddenElements = false;
9214                 this.toolbar.deselectButton('hiddenelements');
9215                 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN);
9216             } else {
9217                 YAHOO.log('Disabling hidden CSS File', 'info', 'SimpleEditor');
9218                 this._showingHiddenElements = true;
9219                 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN);
9220                 this.toolbar.selectButton('hiddenelements');
9221             }
9222             return [false];
9223         },
9224         /**
9225         * @method cmd_removeformat
9226         * @param value Value passed from the execCommand method
9227         * @description This is an execCommand override method. It is called from execCommand when the execCommand('removeformat') is used.
9228         */
9229         cmd_removeformat: function(value) {
9230             var exec = true;
9231             /*
9232             * @knownissue Remove Format issue
9233             * @browser Safari 2.x
9234             * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
9235             * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
9236             * So here we are making the best possible guess and acting on it.
9237             */
9238             if (this.browser.webkit && !this._getDoc().queryCommandEnabled('removeformat')) {
9239                 var _txt = this._getSelection()+'';
9240                 this._createCurrentElement('span');
9241                 this.currentElement[0].className = 'yui-non';
9242                 this.currentElement[0].innerHTML = _txt;
9243                 for (var i = 1; i < this.currentElement.length; i++) {
9244                     this.currentElement[i].parentNode.removeChild(this.currentElement[i]);
9245                 }
9246                 
9247                 exec = false;
9248             }
9249             return [exec];
9250         },
9251         /**
9252         * @method cmd_script
9253         * @param action action passed from the execCommand method
9254         * @param value Value passed from the execCommand method
9255         * @description This is a combined execCommand override method. It is called from the cmd_superscript and cmd_subscript methods.
9256         */
9257         cmd_script: function(action, value) {
9258             var exec = true, tag = action.toLowerCase().substring(0, 3),
9259                 _span = null, _selEl = this._getSelectedElement();
9260
9261             if (this.browser.webkit) {
9262                 YAHOO.log('Safari dom fun again (' + action + ')..', 'info', 'EditorSafari');
9263                 if (this._isElement(_selEl, tag)) {
9264                     YAHOO.log('we are a child of tag (' + tag + '), reverse process', 'info', 'EditorSafari');
9265                     _span = this._swapEl(this.currentElement[0], 'span', function(el) {
9266                         el.className = 'yui-non';
9267                     });
9268                     this._selectNode(_span);
9269                 } else {
9270                     this._createCurrentElement(tag);
9271                     var _sub = this._swapEl(this.currentElement[0], tag);
9272                     this._selectNode(_sub);
9273                     this.currentElement[0] = _sub;
9274                 }
9275                 exec = false;
9276             }
9277             return exec;
9278         },
9279         /**
9280         * @method cmd_superscript
9281         * @param value Value passed from the execCommand method
9282         * @description This is an execCommand override method. It is called from execCommand when the execCommand('superscript') is used.
9283         */
9284         cmd_superscript: function(value) {
9285             return [this.cmd_script('superscript', value)];
9286         },
9287         /**
9288         * @method cmd_subscript
9289         * @param value Value passed from the execCommand method
9290         * @description This is an execCommand override method. It is called from execCommand when the execCommand('subscript') is used.
9291         */
9292         cmd_subscript: function(value) {
9293             return [this.cmd_script('subscript', value)];
9294         },
9295         /**
9296         * @method cmd_indent
9297         * @param value Value passed from the execCommand method
9298         * @description This is an execCommand override method. It is called from execCommand when the execCommand('indent') is used.
9299         */
9300         cmd_indent: function(value) {
9301             var exec = true, selEl = this._getSelectedElement(), _bq = null;
9302
9303             //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9304             //if (this.browser.webkit || this.browser.ie) {
9305             if (this.browser.ie) {
9306                 if (this._isElement(selEl, 'blockquote')) {
9307                     _bq = this._getDoc().createElement('blockquote');
9308                     _bq.innerHTML = selEl.innerHTML;
9309                     selEl.innerHTML = '';
9310                     selEl.appendChild(_bq);
9311                     this._selectNode(_bq);
9312                 } else {
9313                     _bq = this._getDoc().createElement('blockquote');
9314                     var html = this._getRange().htmlText;
9315                     _bq.innerHTML = html;
9316                     this._createCurrentElement('blockquote');
9317                     /*
9318                     for (var i = 0; i < this.currentElement.length; i++) {
9319                         _bq = this._getDoc().createElement('blockquote');
9320                         _bq.innerHTML = this.currentElement[i].innerHTML;
9321                         this.currentElement[i].parentNode.replaceChild(_bq, this.currentElement[i]);
9322                         this.currentElement[i] = _bq;
9323                     }
9324                     */
9325                     this.currentElement[0].parentNode.replaceChild(_bq, this.currentElement[0]);
9326                     this.currentElement[0] = _bq;
9327                     this._selectNode(this.currentElement[0]);
9328                 }
9329                 exec = false;
9330             } else {
9331                 value = 'blockquote';
9332             }
9333             return [exec, 'formatblock', value];
9334         },
9335         /**
9336         * @method cmd_outdent
9337         * @param value Value passed from the execCommand method
9338         * @description This is an execCommand override method. It is called from execCommand when the execCommand('outdent') is used.
9339         */
9340         cmd_outdent: function(value) {
9341             var exec = true, selEl = this._getSelectedElement(), _bq = null, _span = null;
9342             //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9343             if (this.browser.webkit || this.browser.ie) {
9344             //if (this.browser.ie) {
9345                 selEl = this._getSelectedElement();
9346                 if (this._isElement(selEl, 'blockquote')) {
9347                     var par = selEl.parentNode;
9348                     if (this._isElement(selEl.parentNode, 'blockquote')) {
9349                         par.innerHTML = selEl.innerHTML;
9350                         this._selectNode(par);
9351                     } else {
9352                         _span = this._getDoc().createElement('span');
9353                         _span.innerHTML = selEl.innerHTML;
9354                         YAHOO.util.Dom.addClass(_span, 'yui-non');
9355                         par.replaceChild(_span, selEl);
9356                         this._selectNode(_span);
9357                     }
9358                 } else {
9359                     YAHOO.log('Can not outdent, we are not inside a blockquote', 'warn', 'Editor');
9360                 }
9361                 exec = false;
9362             } else {
9363                 value = false;
9364             }
9365             return [exec, 'outdent', value];
9366         },
9367         /**
9368         * @method cmd_justify
9369         * @param dir The direction to justify
9370         * @description This is a factory method for the justify family of commands.
9371         */
9372         cmd_justify: function(dir) {
9373             if (this.browser.ie) {
9374                 if (this._hasSelection()) {
9375                     this._createCurrentElement('span');
9376                     this._swapEl(this.currentElement[0], 'div', function(el) {
9377                         el.style.textAlign = dir;
9378                     });
9379                     
9380                     return [false];
9381                 }
9382             }
9383             return [true, 'justify' + dir, ''];
9384         },
9385         /**
9386         * @method cmd_justifycenter
9387         * @param value Value passed from the execCommand method
9388         * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifycenter') is used.
9389         */
9390         cmd_justifycenter: function() {
9391             return [this.cmd_justify('center')];
9392         },
9393         /**
9394         * @method cmd_justifyleft
9395         * @param value Value passed from the execCommand method
9396         * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyleft') is used.
9397         */
9398         cmd_justifyleft: function() {
9399             return [this.cmd_justify('left')];
9400         },
9401         /**
9402         * @method cmd_justifyright
9403         * @param value Value passed from the execCommand method
9404         * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyright') is used.
9405         */
9406         cmd_justifyright: function() {
9407             return [this.cmd_justify('right')];
9408         },
9409         /* }}}*/        
9410         /**
9411         * @method toString
9412         * @description Returns a string representing the editor.
9413         * @return {String}
9414         */
9415         toString: function() {
9416             var str = 'Editor';
9417             if (this.get && this.get('element_cont')) {
9418                 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
9419             }
9420             return str;
9421         }
9422     });
9423 /**
9424 * @event beforeOpenWindow
9425 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9426 * @param {Overlay} panel The Overlay object that is used to create the window.
9427 * @description Event fires before an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9428 * @type YAHOO.util.CustomEvent
9429 */
9430 /**
9431 * @event afterOpenWindow
9432 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9433 * @param {Overlay} panel The Overlay object that is used to create the window.
9434 * @description Event fires after an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9435 * @type YAHOO.util.CustomEvent
9436 */
9437 /**
9438 * @event closeWindow
9439 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9440 * @description Event fires after an Editor Window is closed. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9441 * @type YAHOO.util.CustomEvent
9442 */
9443 /**
9444 * @event windowCMDOpen
9445 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9446 * @param {Overlay} panel The Overlay object that is used to create the window.
9447 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is opened.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkOpen event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9448 * @type YAHOO.util.CustomEvent
9449 */
9450 /**
9451 * @event windowCMDClose
9452 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9453 * @param {Overlay} panel The Overlay object that is used to create the window.
9454 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is closed.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkClose event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9455 * @type YAHOO.util.CustomEvent
9456 */
9457 /**
9458 * @event windowRender
9459 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9460 * @param {Overlay} panel The Overlay object that is used to create the window.
9461 * @description Event fired when the initial Overlay is rendered. Can be used to manipulate the content of the panel.
9462 * @type YAHOO.util.CustomEvent
9463 */
9464 /**
9465 * @event windowInsertImageRender
9466 * @param {Overlay} panel The Overlay object that is used to create the window.
9467 * @param {HTMLElement} body The HTML element used as the body of the window..
9468 * @param {Toolbar} toolbar A reference to the toolbar object used inside this window.
9469 * @description Event fired when the pre render of the Insert Image window has finished.
9470 * @type YAHOO.util.CustomEvent
9471 */
9472 /**
9473 * @event windowCreateLinkRender
9474 * @param {Overlay} panel The Overlay object that is used to create the window.
9475 * @param {HTMLElement} body The HTML element used as the body of the window..
9476 * @description Event fired when the pre render of the Create Link window has finished.
9477 * @type YAHOO.util.CustomEvent
9478 */
9479
9480
9481
9482     /**
9483      * @description Class to hold Window information between uses. We use the same panel to show the windows, so using this will allow you to configure a window before it is shown.
9484      * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
9485      * @class EditorWindow
9486      * @param {String} name The name of the window.
9487      * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
9488     */
9489     YAHOO.widget.EditorWindow = function(name, attrs) {
9490         /**
9491         * @private
9492         * @property name
9493         * @description A unique name for the window
9494         */
9495         this.name = name.replace(' ', '_');
9496         /**
9497         * @private
9498         * @property attrs
9499         * @description The window attributes
9500         */
9501         this.attrs = attrs;
9502     };
9503
9504     YAHOO.widget.EditorWindow.prototype = {
9505         /**
9506         * @private
9507         * @property header
9508         * @description Holder for the header of the window, used in Editor.openWindow
9509         */
9510         header: null,
9511         /**
9512         * @private
9513         * @property body
9514         * @description Holder for the body of the window, used in Editor.openWindow
9515         */
9516         body: null,
9517         /**
9518         * @private
9519         * @property footer
9520         * @description Holder for the footer of the window, used in Editor.openWindow
9521         */
9522         footer: null,
9523         /**
9524         * @method setHeader
9525         * @description Sets the header for the window.
9526         * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
9527         */
9528         setHeader: function(str) {
9529             this.header = str;
9530         },
9531         /**
9532         * @method setBody
9533         * @description Sets the body for the window.
9534         * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
9535         */
9536         setBody: function(str) {
9537             this.body = str;
9538         },
9539         /**
9540         * @method setFooter
9541         * @description Sets the footer for the window.
9542         * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
9543         */
9544         setFooter: function(str) {
9545             this.footer = str;
9546         },
9547         /**
9548         * @method toString
9549         * @description Returns a string representing the EditorWindow.
9550         * @return {String}
9551         */
9552         toString: function() {
9553             return 'Editor Window (' + this.name + ')';
9554         }
9555     };
9556 })();
9557 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.8.0r4", build: "2449"});