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