2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 var Dom = YAHOO.util.Dom,
9 Event = YAHOO.util.Event,
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
18 * Provides a toolbar button based on the button and menu widgets.
20 * @class ToolbarButtonAdvanced
21 * @param {String/HTMLElement} el The element to turn into a button.
22 * @param {Object} attrs Object liternal containing configuration parameters.
24 if (YAHOO.widget.Button) {
25 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button;
27 * @property buttonType
29 * @description Tells if the Button is a Rich Button or a Simple Button
31 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich';
34 * @param {String} value The value of the option that we want to mark as selected
35 * @description Select an option by value
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();
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);
51 YAHOO.widget.ToolbarButtonAdvanced = function() {};
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, <select> 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
64 * @param {String/HTMLElement} el The element to turn into a button.
65 * @param {Object} attrs Object liternal containing configuration parameters.
68 YAHOO.widget.ToolbarButton = function(el, attrs) {
69 YAHOO.log('ToolbarButton Initalizing', 'info', 'ToolbarButton');
70 YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
72 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
75 var local_attrs = (attrs || {});
79 attributes: local_attrs
82 if (!oConfig.attributes.type) {
83 oConfig.attributes.type = 'push';
86 oConfig.element = document.createElement('span');
87 oConfig.element.setAttribute('unselectable', 'on');
88 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
89 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
90 oConfig.element.firstChild.firstChild.tabIndex = '-1';
91 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId());
92 oConfig.element.id = oConfig.attributes.id;
94 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
97 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
99 * @property buttonType
101 * @description Tells if the Button is a Rich Button or a Simple Button
103 buttonType: 'normal',
105 * @method _handleMouseOver
107 * @description Adds classes to the button elements on mouseover (hover)
109 _handleMouseOver: function() {
110 if (!this.get('disabled')) {
111 this.addClass('yui-button-hover');
112 this.addClass('yui-' + this.get('type') + '-button-hover');
116 * @method _handleMouseOut
118 * @description Removes classes from the button elements on mouseout (hover)
120 _handleMouseOut: function() {
121 this.removeClass('yui-button-hover');
122 this.removeClass('yui-' + this.get('type') + '-button-hover');
126 * @param {String} value The value of the option that we want to mark as selected
127 * @description Select an option by value
129 checkValue: function(value) {
130 if (this.get('type') == 'menu') {
131 var opts = this._button.options;
132 for (var i = 0; i < opts.length; i++) {
133 if (opts[i].value == value) {
134 opts.selectedIndex = i;
141 * @description The ToolbarButton class's initialization method
143 init: function(p_oElement, p_oAttributes) {
144 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
146 this.on('mouseover', this._handleMouseOver, this, true);
147 this.on('mouseout', this._handleMouseOut, this, true);
148 this.on('click', function(ev) {
154 * @method initAttributes
155 * @description Initializes all of the configuration attributes used to create
157 * @param {Object} attr Object literal specifying a set of
158 * configuration attributes used to create the toolbar.
160 initAttributes: function(attr) {
161 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
164 * @description The value of the button
167 this.setAttributeConfig('value', {
172 * @description The menu attribute, see YAHOO.widget.Button
175 this.setAttributeConfig('menu', {
176 value: attr.menu || false
180 * @description The type of button to create: push, menu, color, select, spin
183 this.setAttributeConfig('type', {
186 method: function(type) {
189 this._button = this.get('element').getElementsByTagName('a')[0];
194 el = document.createElement('select');
195 var menu = this.get('menu');
196 for (var i = 0; i < menu.length; i++) {
197 opt = document.createElement('option');
198 opt.innerHTML = menu[i].text;
199 opt.value = menu[i].value;
200 if (menu[i].checked) {
205 this._button.parentNode.replaceChild(el, this._button);
206 Event.on(el, 'change', this._handleSelect, this, true);
214 * @attribute disabled
215 * @description Set the button into a disabled state
218 this.setAttributeConfig('disabled', {
219 value: attr.disabled || false,
220 method: function(disabled) {
222 this.addClass('yui-button-disabled');
223 this.addClass('yui-' + this.get('type') + '-button-disabled');
225 this.removeClass('yui-button-disabled');
226 this.removeClass('yui-' + this.get('type') + '-button-disabled');
228 if (this.get('type') == 'menu') {
229 this._button.disabled = disabled;
236 * @description The text label for the button
239 this.setAttributeConfig('label', {
241 method: function(label) {
243 this._button = this.get('element').getElementsByTagName('a')[0];
245 if (this.get('type') == 'push') {
246 this._button.innerHTML = label;
253 * @description The title of the button
256 this.setAttributeConfig('title', {
262 * @description The container that the button is rendered to, handled by Toolbar
265 this.setAttributeConfig('container', {
268 method: function(cont) {
276 * @method _handleSelect
277 * @description The event fired when a change event gets fired on a select element
278 * @param {Event} ev The change event.
280 _handleSelect: function(ev) {
281 var tar = Event.getTarget(ev);
282 var value = tar.options[tar.selectedIndex].value;
283 this.fireEvent('change', {type: 'change', value: value });
287 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
289 getMenu: function() {
290 return this.get('menu');
294 * @description Destroy the button
296 destroy: function() {
297 Event.purgeElement(this.get('element'), true);
298 this.get('element').parentNode.removeChild(this.get('element'));
299 //Brutal Object Destroy
300 for (var i in this) {
301 if (Lang.hasOwnProperty(this, i)) {
308 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
310 fireEvent: function(p_sType, p_aArgs) {
311 // Disabled buttons should not respond to DOM events
312 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
313 Event.stopEvent(p_aArgs);
317 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
321 * @description Returns a string representing the toolbar.
324 toString: function() {
325 return 'ToolbarButton (' + this.get('id') + ')';
332 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
333 * @namespace YAHOO.widget
334 * @requires yahoo, dom, element, event, toolbarbutton
335 * @optional container_core, dragdrop
338 var Dom = YAHOO.util.Dom,
339 Event = YAHOO.util.Event,
342 var getButton = function(id) {
344 if (Lang.isString(id)) {
345 button = this.getButtonById(id);
347 if (Lang.isNumber(id)) {
348 button = this.getButtonByIndex(id);
350 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
351 button = this.getButtonByValue(id);
353 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
360 * Provides a rich toolbar widget based on the button and menu widgets
363 * @extends YAHOO.util.Element
364 * @param {String/HTMLElement} el The element to turn into a toolbar.
365 * @param {Object} attrs Object liternal containing configuration parameters.
367 YAHOO.widget.Toolbar = function(el, attrs) {
368 YAHOO.log('Toolbar Initalizing', 'info', 'Toolbar');
369 YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
371 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
374 var local_attrs = {};
376 Lang.augmentObject(local_attrs, attrs); //Break the config reference
382 attributes: local_attrs
386 if (Lang.isString(el) && Dom.get(el)) {
387 oConfig.element = Dom.get(el);
388 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
389 oConfig.element = Dom.get(el);
393 if (!oConfig.element) {
394 YAHOO.log('No element defined, creating toolbar container', 'warn', 'Toolbar');
395 oConfig.element = document.createElement('DIV');
396 oConfig.element.id = Dom.generateId();
398 if (local_attrs.container && Dom.get(local_attrs.container)) {
399 YAHOO.log('Container found in config appending to it (' + Dom.get(local_attrs.container).id + ')', 'info', 'Toolbar');
400 Dom.get(local_attrs.container).appendChild(oConfig.element);
405 if (!oConfig.element.id) {
406 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
407 YAHOO.log('No element ID defined for toolbar container, creating..', 'warn', 'Toolbar');
409 YAHOO.log('Initing toolbar with id: ' + oConfig.element.id, 'info', 'Toolbar');
411 var fs = document.createElement('fieldset');
412 var lg = document.createElement('legend');
413 lg.innerHTML = 'Toolbar';
416 var cont = document.createElement('DIV');
417 oConfig.attributes.cont = cont;
418 Dom.addClass(cont, 'yui-toolbar-subcont');
419 fs.appendChild(cont);
420 oConfig.element.appendChild(fs);
422 oConfig.element.tabIndex = -1;
425 oConfig.attributes.element = oConfig.element;
426 oConfig.attributes.id = oConfig.element.id;
428 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
432 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
434 * @method _addMenuClasses
436 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
437 * @param {String} ev The event that fired.
438 * @param {Array} na Array of event information.
439 * @param {Object} o Button config object.
441 _addMenuClasses: function(ev, na, o) {
442 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
443 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
444 Dom.addClass(this.element, 'yui-toolbar-select-menu');
446 var items = this.getItems();
447 for (var i = 0; i < items.length; i++) {
448 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()));
449 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
453 * @property buttonType
454 * @description The default button to use
457 buttonType: YAHOO.widget.ToolbarButton,
460 * @description The DragDrop instance associated with the Toolbar
465 * @property _colorData
466 * @description Object reference containing colors hex and text values.
471 '#111111': 'Obsidian',
472 '#2D2D2D': 'Dark Gray',
476 '#8B8B8B': 'Concrete',
478 '#B9B9B9': 'Titanium',
480 '#D0D0D0': 'Light Gray',
483 '#BFBF00': 'Pumpkin',
486 '#FFFF80': 'Pale Yellow',
488 '#525330': 'Raw Siena',
491 '#7F7F00': 'Paprika',
496 '#80FF00': 'Chartreuse',
498 '#C0FF80': 'Pale Lime',
499 '#DFFFBF': 'Light Mint',
501 '#668F5A': 'Lime Gray',
504 '#8A9B55': 'Pistachio',
505 '#B7C296': 'Light Jade',
506 '#E6EBD5': 'Breakwater',
507 '#00BF00': 'Spring Frost',
508 '#00FF80': 'Pastel Green',
509 '#40FFA0': 'Light Emerald',
510 '#80FFC0': 'Sea Foam',
511 '#BFFFDF': 'Sea Mist',
512 '#033D21': 'Dark Forrest',
514 '#7FA37C': 'Medium Green',
516 '#8DAE94': 'Yellow Gray Green',
517 '#ACC6B5': 'Aqua Lung',
518 '#DDEBE2': 'Sea Vapor',
521 '#40FFFF': 'Turquoise Blue',
522 '#80FFFF': 'Light Aqua',
523 '#BFFFFF': 'Pale Cyan',
524 '#033D3D': 'Dark Teal',
525 '#347D7E': 'Gray Turquoise',
526 '#609A9F': 'Green Blue',
527 '#007F7F': 'Seaweed',
528 '#96BDC4': 'Green Gray',
529 '#B5D1D7': 'Soapstone',
530 '#E2F1F4': 'Light Turquoise',
531 '#0060BF': 'Summer Sky',
532 '#0080FF': 'Sky Blue',
533 '#40A0FF': 'Electric Blue',
534 '#80C0FF': 'Light Azure',
535 '#BFDFFF': 'Ice Blue',
538 '#57708F': 'Dusty Blue',
539 '#00407F': 'Sea Blue',
540 '#7792AC': 'Sky Blue Gray',
541 '#A8BED1': 'Morning Sky',
543 '#0000BF': 'Deep Blue',
545 '#4040FF': 'Cerulean Blue',
546 '#8080FF': 'Evening Blue',
547 '#BFBFFF': 'Light Blue',
548 '#212143': 'Deep Indigo',
549 '#373E68': 'Sea Blue',
550 '#444F75': 'Night Blue',
551 '#00007F': 'Indigo Blue',
552 '#585E82': 'Dockside',
553 '#8687A4': 'Blue Gray',
554 '#D2D1E1': 'Light Blue Gray',
555 '#6000BF': 'Neon Violet',
556 '#8000FF': 'Blue Violet',
557 '#A040FF': 'Violet Purple',
558 '#C080FF': 'Violet Dusk',
559 '#DFBFFF': 'Pale Lavender',
560 '#302449': 'Cool Shale',
561 '#54466F': 'Dark Indigo',
562 '#655A7F': 'Dark Violet',
564 '#726284': 'Smoky Violet',
565 '#9E8FA9': 'Slate Gray',
566 '#DCD1DF': 'Violet White',
567 '#BF00BF': 'Royal Violet',
568 '#FF00FF': 'Fuchsia',
569 '#FF40FF': 'Magenta',
571 '#FFBFFF': 'Pale Magenta',
572 '#4A234A': 'Dark Purple',
573 '#794A72': 'Medium Purple',
574 '#936386': 'Cool Granite',
576 '#9D7292': 'Purple Moon',
577 '#C0A0B6': 'Pale Purple',
578 '#ECDAE5': 'Pink Cloud',
579 '#BF005F': 'Hot Pink',
580 '#FF007F': 'Deep Pink',
582 '#FF80BF': 'Electric Pink',
584 '#451528': 'Purple Red',
585 '#823857': 'Purple Dino',
586 '#A94A76': 'Purple Gray',
588 '#BC6F95': 'Antique Mauve',
589 '#D8A5BB': 'Cool Marble',
590 '#F7DDE9': 'Pink Granite',
592 '#FF0000': 'Fire Truck',
593 '#FF4040': 'Pale Red',
595 '#FFC0C0': 'Warm Pink',
599 '#800000': 'Brick Red',
601 '#D8A3A4': 'Shrimp Pink',
602 '#F8DDDD': 'Shell Pink',
603 '#BF5F00': 'Dark Orange',
605 '#FF9F40': 'Grapefruit',
606 '#FFBF80': 'Canteloupe',
608 '#482C1B': 'Dark Brick',
612 '#C49B71': 'Mustard',
613 '#E1C4A8': 'Pale Tan',
618 * @property _colorPicker
619 * @description The HTML Element containing the colorPicker
624 * @property STR_COLLAPSE
625 * @description String for Toolbar Collapse Button
628 STR_COLLAPSE: 'Collapse Toolbar',
630 * @property STR_SPIN_LABEL
631 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
634 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.',
636 * @property STR_SPIN_UP
637 * @description String for spinbutton up
640 STR_SPIN_UP: 'Click to increase the value of this input',
642 * @property STR_SPIN_DOWN
643 * @description String for spinbutton down
646 STR_SPIN_DOWN: 'Click to decrease the value of this input',
648 * @property _titlebar
649 * @description Object reference to the titlebar
655 * @description Standard browser detection
658 browser: YAHOO.env.ua,
661 * @property _buttonList
662 * @description Internal property list of current buttons in the toolbar
668 * @property _buttonGroupList
669 * @description Internal property list of current button groups in the toolbar
672 _buttonGroupList: null,
676 * @description Internal reference to the separator HTML Element for cloning
682 * @property _sepCount
683 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
689 * @property draghandle
695 * @property _toolbarConfigs
703 * @property CLASS_CONTAINER
704 * @description Default CSS class to apply to the toolbar container element
707 CLASS_CONTAINER: 'yui-toolbar-container',
710 * @property CLASS_DRAGHANDLE
711 * @description Default CSS class to apply to the toolbar's drag handle element
714 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
717 * @property CLASS_SEPARATOR
718 * @description Default CSS class to apply to all separators in the toolbar
721 CLASS_SEPARATOR: 'yui-toolbar-separator',
724 * @property CLASS_DISABLED
725 * @description Default CSS class to apply when the toolbar is disabled
728 CLASS_DISABLED: 'yui-toolbar-disabled',
731 * @property CLASS_PREFIX
732 * @description Default prefix for dynamically created class names
735 CLASS_PREFIX: 'yui-toolbar',
738 * @description The Toolbar class's initialization method
740 init: function(p_oElement, p_oAttributes) {
741 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
745 * @method initAttributes
746 * @description Initializes all of the configuration attributes used to create
748 * @param {Object} attr Object literal specifying a set of
749 * configuration attributes used to create the toolbar.
751 initAttributes: function(attr) {
752 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
753 this.addClass(this.CLASS_CONTAINER);
756 * @attribute buttonType
757 * @description The buttonType to use (advanced or basic)
760 this.setAttributeConfig('buttonType', {
761 value: attr.buttonType || 'basic',
763 validator: function(type) {
771 method: function(type) {
772 if (type == 'advanced') {
773 if (YAHOO.widget.Button) {
774 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
776 YAHOO.log('Can not find YAHOO.widget.Button', 'error', 'Toolbar');
777 this.buttonType = YAHOO.widget.ToolbarButton;
780 this.buttonType = YAHOO.widget.ToolbarButton;
788 * @description Object specifying the buttons to include in the toolbar
792 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
793 * { type: 'separator' },
794 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
796 * { text: "Left", value: 'alignleft' },
797 * { text: "Center", value: 'aligncenter' },
798 * { text: "Right", value: 'alignright' }
806 this.setAttributeConfig('buttons', {
809 method: function(data) {
810 for (var i in data) {
811 if (Lang.hasOwnProperty(data, i)) {
812 if (data[i].type == 'separator') {
814 } else if (data[i].group !== undefined) {
815 this.addButtonGroup(data[i]);
817 this.addButton(data[i]);
825 * @attribute disabled
826 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
830 this.setAttributeConfig('disabled', {
832 method: function(disabled) {
833 if (this.get('disabled') === disabled) {
837 this.addClass(this.CLASS_DISABLED);
838 this.set('draggable', false);
839 this.disableAllButtons();
841 this.removeClass(this.CLASS_DISABLED);
842 if (this._configs.draggable._initialConfig.value) {
843 //Draggable by default, set it back
844 this.set('draggable', true);
846 this.resetAllButtons();
853 * @description The container for the toolbar.
856 this.setAttributeConfig('cont', {
863 * @attribute grouplabels
864 * @description Boolean indicating if the toolbar should show the group label's text string.
868 this.setAttributeConfig('grouplabels', {
869 value: ((attr.grouplabels === false) ? false : true),
870 method: function(grouplabels) {
872 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
874 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
879 * @attribute titlebar
880 * @description Boolean indicating if the toolbar should have a titlebar. If
881 * passed a string, it will use that as the titlebar text
883 * @type Boolean or String
885 this.setAttributeConfig('titlebar', {
887 method: function(titlebar) {
889 if (this._titlebar && this._titlebar.parentNode) {
890 this._titlebar.parentNode.removeChild(this._titlebar);
892 this._titlebar = document.createElement('DIV');
893 this._titlebar.tabIndex = '-1';
894 Event.on(this._titlebar, 'focus', function() {
897 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
898 if (Lang.isString(titlebar)) {
899 var h2 = document.createElement('h2');
901 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
902 this._titlebar.appendChild(h2);
903 Event.on(h2.firstChild, 'click', function(ev) {
906 Event.on([h2, h2.firstChild], 'focus', function() {
910 if (this.get('firstChild')) {
911 this.insertBefore(this._titlebar, this.get('firstChild'));
913 this.appendChild(this._titlebar);
915 if (this.get('collapse')) {
916 this.set('collapse', true);
918 } else if (this._titlebar) {
919 if (this._titlebar && this._titlebar.parentNode) {
920 this._titlebar.parentNode.removeChild(this._titlebar);
928 * @attribute collapse
929 * @description Boolean indicating if the the titlebar should have a collapse button.
930 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
934 this.setAttributeConfig('collapse', {
936 method: function(collapse) {
937 if (this._titlebar) {
938 var collapseEl = null;
939 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
942 //There is already a collapse button
945 collapseEl = document.createElement('SPAN');
946 collapseEl.innerHTML = 'X';
947 collapseEl.title = this.STR_COLLAPSE;
949 Dom.addClass(collapseEl, 'collapse');
950 this._titlebar.appendChild(collapseEl);
951 Event.addListener(collapseEl, 'click', function() {
952 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
953 this.collapse(false); //Expand Toolbar
955 this.collapse(); //Collapse Toolbar
959 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
961 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
962 //We are closed, reopen the titlebar..
963 this.collapse(false); //Expand Toolbar
965 collapseEl[0].parentNode.removeChild(collapseEl[0]);
973 * @attribute draggable
974 * @description Boolean indicating if the toolbar should be draggable.
979 this.setAttributeConfig('draggable', {
980 value: (attr.draggable || false),
981 method: function(draggable) {
982 if (draggable && !this.get('titlebar')) {
983 YAHOO.log('Dragging enabled', 'info', 'Toolbar');
984 if (!this._dragHandle) {
985 this._dragHandle = document.createElement('SPAN');
986 this._dragHandle.innerHTML = '|';
987 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
988 this._dragHandle.id = this.get('id') + '_draghandle';
989 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
990 if (this.get('cont').hasChildNodes()) {
991 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
993 this.get('cont').appendChild(this._dragHandle);
995 this.dd = new YAHOO.util.DD(this.get('id'));
996 this.dd.setHandleElId(this._dragHandle.id);
1000 YAHOO.log('Dragging disabled', 'info', 'Toolbar');
1001 if (this._dragHandle) {
1002 this._dragHandle.parentNode.removeChild(this._dragHandle);
1003 this._dragHandle = null;
1007 if (this._titlebar) {
1009 this.dd = new YAHOO.util.DD(this.get('id'));
1010 this.dd.setHandleElId(this._titlebar);
1011 Dom.addClass(this._titlebar, 'draggable');
1013 Dom.removeClass(this._titlebar, 'draggable');
1021 validator: function(value) {
1023 if (!YAHOO.util.DD) {
1032 * @method addButtonGroup
1033 * @description Add a new button group to the toolbar. (uses addButton)
1034 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1036 addButtonGroup: function(oGroup) {
1037 if (!this.get('element')) {
1038 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1042 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1043 this.addClass(this.CLASS_PREFIX + '-grouped');
1045 var div = document.createElement('DIV');
1046 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1047 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1049 var label = document.createElement('h3');
1050 label.innerHTML = oGroup.label;
1051 div.appendChild(label);
1053 if (!this.get('grouplabels')) {
1054 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1057 this.get('cont').appendChild(div);
1059 //For accessibility, let's put all of the group buttons in an Unordered List
1060 var ul = document.createElement('ul');
1061 div.appendChild(ul);
1063 if (!this._buttonGroupList) {
1064 this._buttonGroupList = {};
1067 this._buttonGroupList[oGroup.group] = ul;
1069 for (var i = 0; i < oGroup.buttons.length; i++) {
1070 var li = document.createElement('li');
1071 li.className = this.CLASS_PREFIX + '-groupitem';
1073 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1074 this.addSeparator(li);
1076 oGroup.buttons[i].container = li;
1077 this.addButton(oGroup.buttons[i]);
1082 * @method addButtonToGroup
1083 * @description Add a new button to a toolbar group. Buttons supported:
1084 * push, split, menu, select, color, spin
1085 * @param {Object} oButton Object literal reference to the Button's Config
1086 * @param {String} group The Group identifier passed into the initial config
1087 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1089 addButtonToGroup: function(oButton, group, after) {
1090 var groupCont = this._buttonGroupList[group];
1091 var li = document.createElement('li');
1092 li.className = this.CLASS_PREFIX + '-groupitem';
1093 oButton.container = li;
1094 this.addButton(oButton, after);
1095 groupCont.appendChild(li);
1099 * @description Add a new button to the toolbar. Buttons supported:
1100 * push, split, menu, select, color, spin
1101 * @param {Object} oButton Object literal reference to the Button's Config
1102 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1104 addButton: function(oButton, after) {
1105 if (!this.get('element')) {
1106 this._queue[this._queue.length] = ['addButton', arguments];
1109 if (!this._buttonList) {
1110 this._buttonList = [];
1112 YAHOO.log('Adding button of type: ' + oButton.type, 'info', 'Toolbar');
1113 if (!oButton.container) {
1114 oButton.container = this.get('cont');
1117 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1118 if (Lang.isArray(oButton.menu)) {
1119 for (var i in oButton.menu) {
1120 if (Lang.hasOwnProperty(oButton.menu, i)) {
1122 fn: function(ev, x, oMenu) {
1123 if (!oButton.menucmd) {
1124 oButton.menucmd = oButton.value;
1126 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1130 oButton.menu[i].onclick = funcObject;
1135 var _oButton = {}, skip = false;
1136 for (var o in oButton) {
1137 if (Lang.hasOwnProperty(oButton, o)) {
1138 if (!this._toolbarConfigs[o]) {
1139 _oButton[o] = oButton[o];
1143 if (oButton.type == 'select') {
1144 _oButton.type = 'menu';
1146 if (oButton.type == 'spin') {
1147 _oButton.type = 'push';
1149 if (_oButton.type == 'color') {
1150 if (YAHOO.widget.Overlay) {
1151 _oButton = this._makeColorButton(_oButton);
1156 if (_oButton.menu) {
1157 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1158 oButton.menu.showEvent.subscribe(function() {
1159 this._button = _oButton;
1162 for (var m = 0; m < _oButton.menu.length; m++) {
1163 if (!_oButton.menu[m].value) {
1164 _oButton.menu[m].value = _oButton.menu[m].text;
1167 if (this.browser.webkit) {
1168 _oButton.focusmenu = false;
1175 //Add to .get('buttons') manually
1176 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1178 var tmp = new this.buttonType(_oButton);
1179 tmp.get('element').tabIndex = '-1';
1180 tmp.get('element').setAttribute('role', 'button');
1181 tmp._selected = true;
1183 if (this.get('disabled')) {
1184 //Toolbar is disabled, disable the new button too!
1185 tmp.set('disabled', true);
1188 oButton.id = tmp.get('id');
1190 YAHOO.log('Button created (' + oButton.type + ')', 'info', 'Toolbar');
1193 var el = tmp.get('element');
1196 nextSib = after.get('element').nextSibling;
1197 } else if (after.nextSibling) {
1198 nextSib = after.nextSibling;
1201 nextSib.parentNode.insertBefore(el, nextSib);
1204 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1206 var icon = document.createElement('span');
1207 icon.className = this.CLASS_PREFIX + '-icon';
1208 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1209 if (tmp._button.tagName.toLowerCase() == 'button') {
1210 tmp.get('element').setAttribute('unselectable', 'on');
1211 //Replace the Button HTML Element with an a href if it exists
1212 var a = document.createElement('a');
1213 a.innerHTML = tmp._button.innerHTML;
1216 Event.on(a, 'click', function(ev) {
1217 Event.stopEvent(ev);
1219 tmp._button.parentNode.replaceChild(a, tmp._button);
1223 if (oButton.type == 'select') {
1224 if (tmp._button.tagName.toLowerCase() == 'select') {
1225 icon.parentNode.removeChild(icon);
1226 var iel = tmp._button;
1227 var parEl = tmp.get('element');
1228 parEl.parentNode.replaceChild(iel, parEl);
1230 //Don't put a class on it if it's a real select element
1231 tmp.addClass(this.CLASS_PREFIX + '-select');
1234 if (oButton.type == 'spin') {
1235 if (!Lang.isArray(oButton.range)) {
1236 oButton.range = [ 10, 100 ];
1238 this._makeSpinButton(tmp, oButton);
1240 tmp.get('element').setAttribute('title', tmp.get('label'));
1241 if (oButton.type != 'spin') {
1242 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1243 var showPicker = function(ev) {
1245 if (ev.keyCode && (ev.keyCode == 9)) {
1249 if (this._colorPicker) {
1250 this._colorPicker._button = oButton.value;
1252 var menuEL = tmp.getMenu().element;
1253 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1254 tmp.getMenu().show();
1256 tmp.getMenu().hide();
1259 YAHOO.util.Event.stopEvent(ev);
1261 tmp.on('mousedown', showPicker, oButton, this);
1262 tmp.on('keydown', showPicker, oButton, this);
1264 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1265 tmp.on('keypress', this._buttonClick, oButton, this);
1266 tmp.on('mousedown', function(ev) {
1267 YAHOO.util.Event.stopEvent(ev);
1268 this._buttonClick(ev, oButton);
1270 tmp.on('click', function(ev) {
1271 YAHOO.util.Event.stopEvent(ev);
1274 //Stop the mousedown event so we can trap the selection in the editor!
1275 tmp.on('mousedown', function(ev) {
1276 YAHOO.util.Event.stopEvent(ev);
1278 tmp.on('click', function(ev) {
1279 YAHOO.util.Event.stopEvent(ev);
1281 tmp.on('change', function(ev) {
1282 if (!oButton.menucmd) {
1283 oButton.menucmd = oButton.value;
1285 oButton.value = ev.value;
1286 this._buttonClick(ev, oButton);
1290 //Hijack the mousedown event in the menu and make it fire a button click..
1291 tmp.on('appendTo', function() {
1293 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1294 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1295 YAHOO.log('mouseDownEvent', 'warn', 'Toolbar');
1296 var oMenu = args[1];
1297 YAHOO.util.Event.stopEvent(args[0]);
1298 tmp._onMenuClick(args[0], tmp);
1299 if (!oButton.menucmd) {
1300 oButton.menucmd = oButton.value;
1302 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1303 self._buttonClick.call(self, args[1], oButton);
1307 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1308 YAHOO.log('clickEvent', 'warn', 'Toolbar');
1309 YAHOO.util.Event.stopEvent(args[0]);
1311 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1312 YAHOO.log('mouseUpEvent', 'warn', 'Toolbar');
1313 YAHOO.util.Event.stopEvent(args[0]);
1320 //Stop the mousedown event so we can trap the selection in the editor!
1321 tmp.on('mousedown', function(ev) {
1322 YAHOO.util.Event.stopEvent(ev);
1324 tmp.on('click', function(ev) {
1325 YAHOO.util.Event.stopEvent(ev);
1328 if (this.browser.ie) {
1330 //Add a couple of new events for IE
1331 tmp.DOM_EVENTS.focusin = true;
1332 tmp.DOM_EVENTS.focusout = true;
1334 //Stop them so we don't loose focus in the Editor
1335 tmp.on('focusin', function(ev) {
1336 YAHOO.util.Event.stopEvent(ev);
1339 tmp.on('focusout', function(ev) {
1340 YAHOO.util.Event.stopEvent(ev);
1342 tmp.on('click', function(ev) {
1343 YAHOO.util.Event.stopEvent(ev);
1347 if (this.browser.webkit) {
1348 //This will keep the document from gaining focus and the editor from loosing it..
1349 //Forcefully remove the focus calls in button!
1350 tmp.hasFocus = function() {
1354 this._buttonList[this._buttonList.length] = tmp;
1355 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1356 if (Lang.isArray(oButton.menu)) {
1357 YAHOO.log('Button type is (' + oButton.type + '), doing extra renderer work.', 'info', 'Toolbar');
1358 var menu = tmp.getMenu();
1359 if (menu && menu.renderEvent) {
1360 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1361 if (oButton.renderer) {
1362 menu.renderEvent.subscribe(oButton.renderer, tmp);
1371 * @method addSeparator
1372 * @description Add a new button separator to the toolbar.
1373 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1374 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1376 addSeparator: function(cont, after) {
1377 if (!this.get('element')) {
1378 this._queue[this._queue.length] = ['addSeparator', arguments];
1381 var sepCont = ((cont) ? cont : this.get('cont'));
1382 if (!this.get('element')) {
1383 this._queue[this._queue.length] = ['addSeparator', arguments];
1386 if (this._sepCount === null) {
1390 YAHOO.log('Separator does not yet exist, creating', 'info', 'Toolbar');
1391 this._sep = document.createElement('SPAN');
1392 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1393 this._sep.innerHTML = '|';
1395 YAHOO.log('Separator does exist, cloning', 'info', 'Toolbar');
1396 var _sep = this._sep.cloneNode(true);
1398 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1402 nextSib = after.get('element').nextSibling;
1403 } else if (after.nextSibling) {
1404 nextSib = after.nextSibling;
1409 if (nextSib == after) {
1410 nextSib.parentNode.appendChild(_sep);
1412 nextSib.parentNode.insertBefore(_sep, nextSib);
1416 sepCont.appendChild(_sep);
1421 * @method _createColorPicker
1423 * @description Creates the core DOM reference to the color picker menu item.
1424 * @param {String} id the id of the toolbar to prefix this DOM container with.
1426 _createColorPicker: function(id) {
1427 if (Dom.get(id + '_colors')) {
1428 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1430 var picker = document.createElement('div');
1431 picker.className = 'yui-toolbar-colors';
1432 picker.id = id + '_colors';
1433 picker.style.display = 'none';
1434 Event.on(window, 'load', function() {
1435 document.body.appendChild(picker);
1438 this._colorPicker = picker;
1441 for (var i in this._colorData) {
1442 if (Lang.hasOwnProperty(this._colorData, i)) {
1443 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1446 html += '<span><em>X</em><strong></strong></span>';
1447 window.setTimeout(function() {
1448 picker.innerHTML = html;
1451 Event.on(picker, 'mouseover', function(ev) {
1452 var picker = this._colorPicker;
1453 var em = picker.getElementsByTagName('em')[0];
1454 var strong = picker.getElementsByTagName('strong')[0];
1455 var tar = Event.getTarget(ev);
1456 if (tar.tagName.toLowerCase() == 'a') {
1457 em.style.backgroundColor = tar.style.backgroundColor;
1458 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1461 Event.on(picker, 'focus', function(ev) {
1462 Event.stopEvent(ev);
1464 Event.on(picker, 'click', function(ev) {
1465 Event.stopEvent(ev);
1467 Event.on(picker, 'mousedown', function(ev) {
1468 Event.stopEvent(ev);
1469 var tar = Event.getTarget(ev);
1470 if (tar.tagName.toLowerCase() == 'a') {
1471 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1472 if (retVal !== false) {
1474 color: tar.innerHTML,
1475 colorName: this._colorData['#' + tar.innerHTML],
1476 value: this._colorPicker._button
1479 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1481 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1486 * @method _resetColorPicker
1488 * @description Clears the currently selected color or mouseover color in the color picker.
1490 _resetColorPicker: function() {
1491 var em = this._colorPicker.getElementsByTagName('em')[0];
1492 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1493 em.style.backgroundColor = 'transparent';
1494 strong.innerHTML = '';
1497 * @method _makeColorButton
1499 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1500 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1502 _makeColorButton: function(_oButton) {
1503 if (!this._colorPicker) {
1504 this._createColorPicker(this.get('id'));
1506 _oButton.type = 'color';
1507 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1508 _oButton.menu.setBody('');
1509 _oButton.menu.render(this.get('cont'));
1510 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1511 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1512 _oButton.menu.beforeShowEvent.subscribe(function() {
1513 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1514 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1515 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1516 this._resetColorPicker();
1517 var _p = this._colorPicker;
1518 if (_p.parentNode) {
1519 _p.parentNode.removeChild(_p);
1521 _oButton.menu.setBody('');
1522 _oButton.menu.appendToBody(_p);
1523 this._colorPicker.style.display = 'block';
1529 * @method _makeSpinButton
1530 * @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.
1531 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1532 * @param {Object} oButton Object literal containing the buttons initial config
1534 _makeSpinButton: function(_button, oButton) {
1535 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1537 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1538 range = oButton.range,
1539 _b1 = document.createElement('a'),
1540 _b2 = document.createElement('a');
1543 _b1.tabIndex = '-1';
1544 _b2.tabIndex = '-1';
1546 //Setup the up and down arrows
1547 _b1.className = 'up';
1548 _b1.title = this.STR_SPIN_UP;
1549 _b1.innerHTML = this.STR_SPIN_UP;
1550 _b2.className = 'down';
1551 _b2.title = this.STR_SPIN_DOWN;
1552 _b2.innerHTML = this.STR_SPIN_DOWN;
1554 //Append them to the container
1555 _par.appendChild(_b1);
1556 _par.appendChild(_b2);
1558 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1559 _button.set('title', label);
1561 var cleanVal = function(value) {
1562 value = ((value < range[0]) ? range[0] : value);
1563 value = ((value > range[1]) ? range[1] : value);
1567 var br = this.browser;
1569 var strLabel = this.STR_SPIN_LABEL;
1570 if (this._titlebar && this._titlebar.firstChild) {
1571 tbar = this._titlebar.firstChild;
1574 var _intUp = function(ev) {
1575 YAHOO.util.Event.stopEvent(ev);
1576 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1577 var value = parseInt(_button.get('label'), 10);
1579 value = cleanVal(value);
1580 _button.set('label', ''+value);
1581 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1582 _button.set('title', label);
1583 if (!br.webkit && tbar) {
1584 //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
1587 self._buttonClick(ev, oButton);
1591 var _intDown = function(ev) {
1592 YAHOO.util.Event.stopEvent(ev);
1593 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1594 var value = parseInt(_button.get('label'), 10);
1596 value = cleanVal(value);
1598 _button.set('label', ''+value);
1599 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1600 _button.set('title', label);
1601 if (!br.webkit && tbar) {
1602 //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
1605 self._buttonClick(ev, oButton);
1609 var _intKeyUp = function(ev) {
1610 if (ev.keyCode == 38) {
1612 } else if (ev.keyCode == 40) {
1614 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1616 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1621 //Handle arrow keys..
1622 _button.on('keydown', _intKeyUp, this, true);
1624 //Listen for the click on the up button and act on it
1625 //Listen for the click on the down button and act on it
1626 Event.on(_b1, 'mousedown',function(ev) {
1627 Event.stopEvent(ev);
1629 Event.on(_b2, 'mousedown', function(ev) {
1630 Event.stopEvent(ev);
1632 Event.on(_b1, 'click', _intUp, this, true);
1633 Event.on(_b2, 'click', _intDown, this, true);
1637 * @method _buttonClick
1638 * @description Click handler for all buttons in the toolbar.
1639 * @param {String} ev The event that was passed in.
1640 * @param {Object} info Object literal of information about the button that was clicked.
1642 _buttonClick: function(ev, info) {
1645 if (ev && ev.type == 'keypress') {
1646 if (ev.keyCode == 9) {
1648 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1655 var fireNextEvent = true,
1658 info.isSelected = this.isSelected(info.id);
1661 YAHOO.log('fireEvent::' + info.value + 'Click', 'info', 'Toolbar');
1662 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1663 if (retValue === false) {
1664 fireNextEvent = false;
1668 if (info.menucmd && fireNextEvent) {
1669 YAHOO.log('fireEvent::' + info.menucmd + 'Click', 'info', 'Toolbar');
1670 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1671 if (retValue === false) {
1672 fireNextEvent = false;
1675 if (fireNextEvent) {
1676 YAHOO.log('fireEvent::buttonClick', 'info', 'Toolbar');
1677 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1680 if (info.type == 'select') {
1681 var button = this.getButtonById(info.id);
1682 if (button.buttonType == 'rich') {
1683 var txt = info.value;
1684 for (var i = 0; i < info.menu.length; i++) {
1685 if (info.menu[i].value == info.value) {
1686 txt = info.menu[i].text;
1690 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1691 var _items = button.getMenu().getItems();
1692 for (var m = 0; m < _items.length; m++) {
1693 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1694 _items[m].cfg.setProperty('checked', true);
1696 _items[m].cfg.setProperty('checked', false);
1702 Event.stopEvent(ev);
1709 * @description Flag to determine if the arrow nav listeners have been attached
1715 * @property _navCounter
1716 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1722 * @method _navigateButtons
1723 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1724 * @param {Event} ev The Key Event
1726 _navigateButtons: function(ev) {
1727 switch (ev.keyCode) {
1730 if (ev.keyCode == 37) {
1735 if (this._navCounter > (this._buttonList.length - 1)) {
1736 this._navCounter = 0;
1738 if (this._navCounter < 0) {
1739 this._navCounter = (this._buttonList.length - 1);
1741 if (this._buttonList[this._navCounter]) {
1742 var el = this._buttonList[this._navCounter].get('element');
1743 if (this.browser.ie) {
1744 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1746 if (this._buttonList[this._navCounter].get('disabled')) {
1747 this._navigateButtons(ev);
1757 * @method _handleFocus
1758 * @description Sets up the listeners for the arrow key navigation
1760 _handleFocus: function() {
1761 if (!this._keyNav) {
1762 var ev = 'keypress';
1763 if (this.browser.ie) {
1766 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1767 this._keyNav = true;
1768 this._navCounter = -1;
1772 * @method getButtonById
1773 * @description Gets a button instance from the toolbar by is Dom id.
1774 * @param {String} id The Dom id to query for.
1775 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1777 getButtonById: function(id) {
1778 var len = this._buttonList.length;
1779 for (var i = 0; i < len; i++) {
1780 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1781 return this._buttonList[i];
1787 * @method getButtonByValue
1788 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1789 * @param {String} value The button value to query for.
1790 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1792 getButtonByValue: function(value) {
1793 var _buttons = this.get('buttons');
1794 var len = _buttons.length;
1795 for (var i = 0; i < len; i++) {
1796 if (_buttons[i].group !== undefined) {
1797 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1798 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1799 return this.getButtonById(_buttons[i].buttons[m].id);
1801 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1802 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1803 if (_buttons[i].buttons[m].menu[s].value == value) {
1804 return this.getButtonById(_buttons[i].buttons[m].id);
1810 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1811 return this.getButtonById(_buttons[i].id);
1813 if (_buttons[i].menu) { //Menu Button, loop through the values
1814 for (var j = 0; j < _buttons[i].menu.length; j++) {
1815 if (_buttons[i].menu[j].value == value) {
1816 return this.getButtonById(_buttons[i].id);
1825 * @method getButtonByIndex
1826 * @description Gets a button instance from the toolbar by is index in _buttonList.
1827 * @param {Number} index The index of the button in _buttonList.
1828 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1830 getButtonByIndex: function(index) {
1831 if (this._buttonList[index]) {
1832 return this._buttonList[index];
1838 * @method getButtons
1839 * @description Returns an array of buttons in the current toolbar
1842 getButtons: function() {
1843 return this._buttonList;
1846 * @method disableButton
1847 * @description Disables a button in the toolbar.
1848 * @param {String/Number} id Disable a button by it's id, index or value.
1851 disableButton: function(id) {
1852 var button = getButton.call(this, id);
1854 button.set('disabled', true);
1860 * @method enableButton
1861 * @description Enables a button in the toolbar.
1862 * @param {String/Number} id Enable a button by it's id, index or value.
1865 enableButton: function(id) {
1866 if (this.get('disabled')) {
1869 var button = getButton.call(this, id);
1871 if (button.get('disabled')) {
1872 button.set('disabled', false);
1879 * @method isSelected
1880 * @description Tells if a button is selected or not.
1881 * @param {String/Number} id A button by it's id, index or value.
1884 isSelected: function(id) {
1885 var button = getButton.call(this, id);
1887 return button._selected;
1892 * @method selectButton
1893 * @description Selects a button in the toolbar.
1894 * @param {String/Number} id Select a button by it's id, index or value.
1895 * @param {String} value If this is a Menu Button, check this item in the menu
1898 selectButton: function(id, value) {
1899 var button = getButton.call(this, id);
1901 button.addClass('yui-button-selected');
1902 button.addClass('yui-button-' + button.get('value') + '-selected');
1903 button._selected = true;
1905 if (button.buttonType == 'rich') {
1906 var _items = button.getMenu().getItems();
1907 for (var m = 0; m < _items.length; m++) {
1908 if (_items[m].value == value) {
1909 _items[m].cfg.setProperty('checked', true);
1910 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1912 _items[m].cfg.setProperty('checked', false);
1922 * @method deselectButton
1923 * @description Deselects a button in the toolbar.
1924 * @param {String/Number} id Deselect a button by it's id, index or value.
1927 deselectButton: function(id) {
1928 var button = getButton.call(this, id);
1930 button.removeClass('yui-button-selected');
1931 button.removeClass('yui-button-' + button.get('value') + '-selected');
1932 button.removeClass('yui-button-hover');
1933 button._selected = false;
1939 * @method deselectAllButtons
1940 * @description Deselects all buttons in the toolbar.
1943 deselectAllButtons: function() {
1944 var len = this._buttonList.length;
1945 for (var i = 0; i < len; i++) {
1946 this.deselectButton(this._buttonList[i]);
1950 * @method disableAllButtons
1951 * @description Disables all buttons in the toolbar.
1954 disableAllButtons: function() {
1955 if (this.get('disabled')) {
1958 var len = this._buttonList.length;
1959 for (var i = 0; i < len; i++) {
1960 this.disableButton(this._buttonList[i]);
1964 * @method enableAllButtons
1965 * @description Enables all buttons in the toolbar.
1968 enableAllButtons: function() {
1969 if (this.get('disabled')) {
1972 var len = this._buttonList.length;
1973 for (var i = 0; i < len; i++) {
1974 this.enableButton(this._buttonList[i]);
1978 * @method resetAllButtons
1979 * @description Resets all buttons to their initial state.
1980 * @param {Object} _ex Except these buttons
1983 resetAllButtons: function(_ex) {
1984 if (!Lang.isObject(_ex)) {
1987 if (this.get('disabled')) {
1990 var len = this._buttonList.length;
1991 for (var i = 0; i < len; i++) {
1992 var _button = this._buttonList[i];
1994 var disabled = _button._configs.disabled._initialConfig.value;
1995 if (_ex[_button.get('id')]) {
1996 this.enableButton(_button);
1997 this.selectButton(_button);
2000 this.disableButton(_button);
2002 this.enableButton(_button);
2004 this.deselectButton(_button);
2010 * @method destroyButton
2011 * @description Destroy a button in the toolbar.
2012 * @param {String/Number} id Destroy a button by it's id or index.
2015 destroyButton: function(id) {
2016 var button = getButton.call(this, id);
2018 var thisID = button.get('id');
2021 var len = this._buttonList.length;
2022 for (var i = 0; i < len; i++) {
2023 if (this._buttonList[i] && this._buttonList[i].get('id') == thisID) {
2024 this._buttonList[i] = null;
2033 * @description Destroys the toolbar, all of it's elements and objects.
2036 destroy: function() {
2037 this.get('element').innerHTML = '';
2038 this.get('element').className = '';
2039 //Brutal Object Destroy
2040 for (var i in this) {
2041 if (Lang.hasOwnProperty(this, i)) {
2049 * @description Programatically collapse the toolbar.
2050 * @param {Boolean} collapse True to collapse, false to expand.
2052 collapse: function(collapse) {
2053 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2054 if (collapse === false) {
2055 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2057 Dom.removeClass(el[0], 'collapsed');
2059 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2062 Dom.addClass(el[0], 'collapsed');
2064 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2065 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2070 * @description Returns a string representing the toolbar.
2073 toString: function() {
2074 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2078 * @event buttonClick
2079 * @param {Object} o The object passed to this handler is the button config used to create the button.
2080 * @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.
2081 * @type YAHOO.util.CustomEvent
2085 * @param {Object} o The object passed to this handler is the button config used to create the button.
2086 * @description This is a special dynamic event that is created and dispatched based on the value property
2087 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2091 * { type: 'button', value: 'test', value: 'testButton' }
2094 * With the valueClick event you could subscribe to this buttons click event with this:
2095 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2096 * @type YAHOO.util.CustomEvent
2099 * @event toolbarExpanded
2100 * @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.
2101 * @type YAHOO.util.CustomEvent
2104 * @event toolbarCollapsed
2105 * @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.
2106 * @type YAHOO.util.CustomEvent
2111 * @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>
2112 * @namespace YAHOO.widget
2113 * @requires yahoo, dom, element, event, toolbar
2114 * @optional animation, container_core, resize, dragdrop
2118 var Dom = YAHOO.util.Dom,
2119 Event = YAHOO.util.Event,
2121 Toolbar = YAHOO.widget.Toolbar;
2124 * 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.
2126 * @class SimpleEditor
2127 * @extends YAHOO.util.Element
2128 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2129 * @param {Object} attrs Object liternal containing configuration parameters.
2132 YAHOO.widget.SimpleEditor = function(el, attrs) {
2133 YAHOO.log('SimpleEditor Initalizing', 'info', 'SimpleEditor');
2136 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2137 Lang.augmentObject(o, el); //Break the config reference
2138 el = document.createElement('textarea');
2139 this.DOMReady = true;
2141 var c = Dom.get(o.container);
2144 document.body.appendChild(el);
2148 Lang.augmentObject(o, attrs); //Break the config reference
2157 if (Lang.isString(el)) {
2160 if (oConfig.attributes.id) {
2161 id = oConfig.attributes.id;
2163 this.DOMReady = true;
2164 id = Dom.generateId(el);
2167 oConfig.element = el;
2169 var element_cont = document.createElement('DIV');
2170 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2171 id: id + '_container'
2173 var div = document.createElement('div');
2174 Dom.addClass(div, 'first-child');
2175 oConfig.attributes.element_cont.appendChild(div);
2177 if (!oConfig.attributes.toolbar_cont) {
2178 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2179 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2180 div.appendChild(oConfig.attributes.toolbar_cont);
2182 var editorWrapper = document.createElement('DIV');
2183 div.appendChild(editorWrapper);
2184 oConfig.attributes.editor_wrapper = editorWrapper;
2186 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2190 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2193 * @property _resizeConfig
2194 * @description The default config for the Resize Utility
2206 * @method _setupResize
2207 * @description Creates the Resize instance and binds its events.
2209 _setupResize: function() {
2210 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2211 if (this.get('resize')) {
2213 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2214 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2215 this.resize.on('resize', function(args) {
2216 var anim = this.get('animate');
2217 this.set('animate', false);
2218 this.set('width', args.width + 'px');
2219 var h = args.height,
2220 th = (this.toolbar.get('element').clientHeight + 2),
2223 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2225 var newH = (h - th - dh);
2226 this.set('height', newH + 'px');
2227 this.get('element_cont').setStyle('height', '');
2228 this.set('animate', anim);
2234 * @description A reference to the Resize object
2235 * @type YAHOO.util.Resize
2241 * @description Sets up the DD instance used from the 'drag' config option.
2243 _setupDD: function() {
2244 if (!YAHOO.util.DD) { return false; }
2245 if (this.get('drag')) {
2246 YAHOO.log('Attaching DD instance to Editor', 'info', 'SimpleEditor');
2247 var d = this.get('drag'),
2249 if (d === 'proxy') {
2250 dd = YAHOO.util.DDProxy;
2253 this.dd = new dd(this.get('element_cont').get('element'));
2254 this.toolbar.addClass('draggable');
2255 this.dd.setHandleElId(this.toolbar._titlebar);
2260 * @description A reference to the DragDrop object.
2261 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2266 * @property _lastCommand
2267 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2271 _undoNodeChange: function() {},
2272 _storeUndo: function() {},
2276 * @description Checks a keyMap entry against a key event
2277 * @param {Object} k The _keyMap object
2278 * @param {Event} e The Mouse Event
2281 _checkKey: function(k, e) {
2283 if ((e.keyCode === k.key)) {
2284 if (k.mods && (k.mods.length > 0)) {
2286 for (var i = 0; i < k.mods.length; i++) {
2287 if (this.browser.mac) {
2288 if (k.mods[i] == 'ctrl') {
2292 if (e[k.mods[i] + 'Key'] === true) {
2296 if (val === k.mods.length) {
2303 //YAHOO.log('Shortcut Key Check: (' + k.key + ') return: ' + ret, 'info', 'SimpleEditor');
2309 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2310 * 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.
2311 * @type {Object/Mixed}
2320 mods: ['shift', 'ctrl']
2331 mods: ['shift', 'ctrl']
2335 mods: ['shift', 'ctrl']
2339 mods: ['shift', 'ctrl']
2343 mods: ['shift', 'ctrl']
2347 mods: ['shift', 'ctrl']
2351 mods: ['shift', 'ctrl']
2359 mods: ['shift', 'ctrl']
2363 mods: ['shift', 'ctrl']
2367 mods: ['shift', 'ctrl']
2371 mods: ['shift', 'ctrl']
2376 * @method _cleanClassName
2377 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2378 * @param {String} str The classname to clean up
2381 _cleanClassName: function(str) {
2382 return str.replace(/ /g, '-').toLowerCase();
2385 * @property _textarea
2386 * @description Flag to determine if we are using a textarea or an HTML Node.
2391 * @property _docType
2392 * @description The DOCTYPE to use in the editable container.
2395 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2397 * @property editorDirty
2398 * @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.
2403 * @property _defaultCSS
2404 * @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' }
2407 _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; }',
2409 * @property _defaultToolbar
2411 * @description Default toolbar config.
2414 _defaultToolbar: null,
2416 * @property _lastButton
2418 * @description The last button pressed, so we don't disable it.
2423 * @property _baseHREF
2425 * @description The base location of the editable page (this page) so that relative paths for image work.
2428 _baseHREF: function() {
2429 var href = document.location.href;
2430 if (href.indexOf('?') !== -1) { //Remove the query string
2431 href = href.substring(0, href.indexOf('?'));
2433 href = href.substring(0, href.lastIndexOf('/')) + '/';
2437 * @property _lastImage
2439 * @description Safari reference for the last image selected (for styling as selected).
2444 * @property _blankImageLoaded
2446 * @description Don't load the blank image more than once..
2449 _blankImageLoaded: null,
2451 * @property _fixNodesTimer
2453 * @description Holder for the fixNodes timer
2456 _fixNodesTimer: null,
2458 * @property _nodeChangeTimer
2460 * @description Holds a reference to the nodeChange setTimeout call
2463 _nodeChangeTimer: null,
2465 * @property _lastNodeChangeEvent
2467 * @description Flag to determine the last event that fired a node change
2470 _lastNodeChangeEvent: null,
2472 * @property _lastNodeChange
2474 * @description Flag to determine when the last node change was fired
2479 * @property _rendered
2481 * @description Flag to determine if editor has been rendered or not
2486 * @property DOMReady
2488 * @description Flag to determine if DOM is ready or not
2493 * @property _selection
2495 * @description Holder for caching iframe selections
2502 * @description DOM Element holder for the editor Mask when disabled
2507 * @property _showingHiddenElements
2509 * @description Status of the hidden elements button
2512 _showingHiddenElements: null,
2514 * @property currentWindow
2515 * @description A reference to the currently open EditorWindow
2518 currentWindow: null,
2520 * @property currentEvent
2521 * @description A reference to the current editor event
2526 * @property operaEvent
2528 * @description setTimeout holder for Opera and Image DoubleClick event..
2533 * @property currentFont
2534 * @description A reference to the last font selected from the Toolbar
2539 * @property currentElement
2540 * @description A reference to the current working element in the editor
2543 currentElement: null,
2546 * @description A reference to the dompath container for writing the current working dom path to.
2551 * @property beforeElement
2552 * @description A reference to the H2 placed before the editor for Accessibilty.
2555 beforeElement: null,
2557 * @property afterElement
2558 * @description A reference to the H2 placed after the editor for Accessibilty.
2563 * @property invalidHTML
2564 * @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.
2582 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2583 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2588 * @property _contentTimer
2589 * @description setTimeout holder for documentReady check
2591 _contentTimer: null,
2594 * @property _contentTimerCounter
2595 * @description Counter to check the number of times the body is polled for before giving up
2598 _contentTimerCounter: 0,
2601 * @property _disabled
2602 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2605 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2608 * @property _alwaysDisabled
2609 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2612 _alwaysDisabled: { undo: true, redo: true },
2615 * @property _alwaysEnabled
2616 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2619 _alwaysEnabled: { },
2622 * @property _semantic
2623 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2626 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2629 * @property _tag2cmd
2630 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2639 'sup': 'superscript',
2641 'img': 'insertimage',
2643 'ul' : 'insertunorderedlist',
2644 'ol' : 'insertorderedlist'
2648 * @private _createIframe
2649 * @description Creates the DOM and YUI Element for the iFrame editor area.
2650 * @param {String} id The string ID to prefix the iframe with
2651 * @return {Object} iFrame object
2653 _createIframe: function() {
2654 var ifrmDom = document.createElement('iframe');
2655 ifrmDom.id = this.get('id') + '_editor';
2663 allowTransparency: 'true',
2666 if (this.get('autoHeight')) {
2667 config.scrolling = 'no';
2669 for (var i in config) {
2670 if (Lang.hasOwnProperty(config, i)) {
2671 ifrmDom.setAttribute(i, config[i]);
2674 var isrc = 'javascript:;';
2675 if (this.browser.ie) {
2676 //isrc = 'about:blank';
2677 //TODO - Check this, I have changed it before..
2678 isrc = 'javascript:false;';
2680 ifrmDom.setAttribute('src', isrc);
2681 var ifrm = new YAHOO.util.Element(ifrmDom);
2682 ifrm.setStyle('visibility', 'hidden');
2686 * @private _isElement
2687 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2688 * @param {HTMLElement} el The element to check
2689 * @param {String} tag The tag that the element needs to be
2692 _isElement: function(el, tag) {
2693 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2696 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2702 * @private _hasParent
2703 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2704 * @param {HTMLElement} el The element to check
2705 * @param {String} tag The tag that the element needs to be
2706 * @return HTMLElement
2708 _hasParent: function(el, tag) {
2709 if (!el || !el.parentNode) {
2713 while (el.parentNode) {
2714 if (this._isElement(el, tag)) {
2717 if (el.parentNode) {
2728 * @description Get the Document of the IFRAME
2731 _getDoc: function() {
2734 if (this.get('iframe')) {
2735 if (this.get('iframe').get) {
2736 if (this.get('iframe').get('element')) {
2738 if (this.get('iframe').get('element').contentWindow) {
2739 if (this.get('iframe').get('element').contentWindow.document) {
2740 value = this.get('iframe').get('element').contentWindow.document;
2753 * @method _getWindow
2754 * @description Get the Window of the IFRAME
2757 _getWindow: function() {
2758 return this.get('iframe').get('element').contentWindow;
2762 * @description Attempt to set the focus of the iframes window.
2765 this._getWindow().focus();
2769 * @depreciated - This should not be used, moved to this.focus();
2770 * @method _focusWindow
2771 * @description Attempt to set the focus of the iframes window.
2773 _focusWindow: function() {
2774 YAHOO.log('_focusWindow: depreciated in favor of this.focus()', 'warn', 'Editor');
2779 * @method _hasSelection
2780 * @description Determines if there is a selection in the editor document.
2783 _hasSelection: function() {
2784 var sel = this._getSelection();
2785 var range = this._getRange();
2788 if (!sel || !range) {
2793 if (this.browser.ie || this.browser.opera) {
2801 if (this.browser.webkit) {
2802 if (sel+'' !== '') {
2806 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2815 * @method _getSelection
2816 * @description Handles the different selection objects across the A-Grade list.
2817 * @return {Object} Selection Object
2819 _getSelection: function() {
2821 if (this._getDoc() && this._getWindow()) {
2822 if (this._getDoc().selection) {
2823 _sel = this._getDoc().selection;
2825 _sel = this._getWindow().getSelection();
2827 //Handle Safari's lack of Selection Object
2828 if (this.browser.webkit) {
2829 if (_sel.baseNode) {
2830 this._selection = {};
2831 this._selection.baseNode = _sel.baseNode;
2832 this._selection.baseOffset = _sel.baseOffset;
2833 this._selection.extentNode = _sel.extentNode;
2834 this._selection.extentOffset = _sel.extentOffset;
2835 } else if (this._selection !== null) {
2836 _sel = this._getWindow().getSelection();
2837 _sel.setBaseAndExtent(
2838 this._selection.baseNode,
2839 this._selection.baseOffset,
2840 this._selection.extentNode,
2841 this._selection.extentOffset);
2842 this._selection = null;
2850 * @method _selectNode
2851 * @description Places the highlight around a given node
2852 * @param {HTMLElement} node The node to select
2854 _selectNode: function(node, collapse) {
2858 var sel = this._getSelection(),
2861 if (this.browser.ie) {
2862 try { //IE freaks out here sometimes..
2863 range = this._getDoc().body.createTextRange();
2864 range.moveToElementText(node);
2867 YAHOO.log('IE failed to select element: ' + node.tagName, 'warn', 'SimpleEditor');
2869 } else if (this.browser.webkit) {
2871 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2873 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2875 } else if (this.browser.opera) {
2876 sel = this._getWindow().getSelection();
2877 range = this._getDoc().createRange();
2878 range.selectNode(node);
2879 sel.removeAllRanges();
2880 sel.addRange(range);
2882 range = this._getDoc().createRange();
2883 range.selectNodeContents(node);
2884 sel.removeAllRanges();
2885 sel.addRange(range);
2887 //TODO - Check Performance
2893 * @description Handles the different range objects across the A-Grade list.
2894 * @return {Object} Range Object
2896 _getRange: function() {
2897 var sel = this._getSelection();
2903 if (this.browser.webkit && !sel.getRangeAt) {
2904 var _range = this._getDoc().createRange();
2906 _range.setStart(sel.anchorNode, sel.anchorOffset);
2907 _range.setEnd(sel.focusNode, sel.focusOffset);
2909 _range = this._getWindow().getSelection()+'';
2914 if (this.browser.ie || this.browser.opera) {
2916 return sel.createRange();
2922 if (sel.rangeCount > 0) {
2923 return sel.getRangeAt(0);
2929 * @method _setDesignMode
2930 * @description Sets the designMode of the iFrame document.
2931 * @param {String} state This should be either on or off
2933 _setDesignMode: function(state) {
2936 //SourceForge Bug #1807057
2937 if (this.browser.ie && (state.toLowerCase() == 'off')) {
2941 this._getDoc().designMode = state;
2947 * @method _toggleDesignMode
2948 * @description Toggles the designMode of the iFrame document on and off.
2949 * @return {String} The state that it was set to.
2951 _toggleDesignMode: function() {
2952 var _dMode = this._getDoc().designMode.toLowerCase(),
2954 if (_dMode == 'on') {
2957 this._setDesignMode(_state);
2962 * @property _focused
2963 * @description Holder for trapping focus/blur state and prevent double events
2969 * @method _handleFocus
2970 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
2971 * @param {Event} e The DOM Event
2973 _handleFocus: function(e) {
2974 if (!this._focused) {
2975 YAHOO.log('Editor Window Focused', 'info', 'SimpleEditor');
2976 this._focused = true;
2977 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
2982 * @method _handleBlur
2983 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
2984 * @param {Event} e The DOM Event
2986 _handleBlur: function(e) {
2987 if (this._focused) {
2988 YAHOO.log('Editor Window Blurred', 'info', 'SimpleEditor');
2989 this._focused = false;
2990 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
2995 * @method _initEditorEvents
2996 * @description This method sets up the listeners on the Editors document.
2998 _initEditorEvents: function() {
2999 //Setup Listeners on iFrame
3000 var doc = this._getDoc(),
3001 win = this._getWindow();
3003 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3004 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3005 Event.on(doc, 'click', this._handleClick, this, true);
3006 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3007 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3008 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3009 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3010 /* TODO -- Everyone but Opera works here..
3011 Event.on(doc, 'paste', function() {
3012 YAHOO.log('PASTE', 'info', 'SimpleEditor');
3017 Event.on(win, 'focus', this._handleFocus, this, true);
3018 Event.on(win, 'blur', this._handleBlur, this, true);
3022 * @method _removeEditorEvents
3023 * @description This method removes the listeners on the Editors document (for disabling).
3025 _removeEditorEvents: function() {
3026 //Remove Listeners on iFrame
3027 var doc = this._getDoc(),
3028 win = this._getWindow();
3030 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3031 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3032 Event.removeListener(doc, 'click', this._handleClick, this, true);
3033 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3034 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3035 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3036 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3039 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3040 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3042 _fixWebkitDivs: function() {
3043 if (this.browser.webkit) {
3044 var divs = this._getDoc().body.getElementsByTagName('div');
3045 Dom.addClass(divs, 'yui-wk-div');
3050 * @method _initEditor
3051 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3053 _initEditor: function() {
3054 if (this.browser.ie) {
3055 this._getDoc().body.style.margin = '0';
3057 if (!this.get('disabled')) {
3058 if (this._getDoc().designMode.toLowerCase() != 'on') {
3059 this._setDesignMode('on');
3060 this._contentTimerCounter = 0;
3063 if (!this._getDoc().body) {
3064 YAHOO.log('Body is null, check again', 'error', 'SimpleEditor');
3065 this._contentTimerCounter = 0;
3066 this._checkLoaded();
3070 YAHOO.log('editorLoaded', 'info', 'SimpleEditor');
3071 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3072 if (!this.get('disabled')) {
3073 this._initEditorEvents();
3074 this.toolbar.set('disabled', false);
3077 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3078 this._fixWebkitDivs();
3079 if (this.get('dompath')) {
3080 YAHOO.log('Delayed DomPath write', 'info', 'SimpleEditor');
3082 setTimeout(function() {
3083 self._writeDomPath.call(self);
3084 self._setupResize.call(self);
3088 for (var i in this.browser) {
3089 if (this.browser[i]) {
3093 if (this.get('ptags')) {
3096 Dom.addClass(this._getDoc().body, br.join(' '));
3097 this.nodeChange(true);
3101 * @method _checkLoaded
3102 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3104 _checkLoaded: function() {
3105 this._contentTimerCounter++;
3106 if (this._contentTimer) {
3107 clearTimeout(this._contentTimer);
3109 if (this._contentTimerCounter > 500) {
3110 YAHOO.log('ERROR: Body Did Not load', 'error', 'SimpleEditor');
3115 if (this._getDoc() && this._getDoc().body) {
3116 if (this.browser.ie) {
3117 if (this._getDoc().body.readyState == 'complete') {
3121 if (this._getDoc().body._rteLoaded === true) {
3128 YAHOO.log('checking body (e)' + e, 'error', 'SimpleEditor');
3131 if (init === true) {
3132 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3133 YAHOO.log('Firing _initEditor', 'info', 'SimpleEditor');
3137 this._contentTimer = setTimeout(function() {
3138 self._checkLoaded.call(self);
3144 * @method _setInitialContent
3145 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3147 _setInitialContent: function() {
3148 YAHOO.log('Populating editor body with contents of the text area', 'info', 'SimpleEditor');
3150 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3153 if ((value === '') && this.browser.gecko) {
3154 //It seems that Gecko doesn't like an empty body so we have to give it something..
3158 var html = Lang.substitute(this.get('html'), {
3159 TITLE: this.STR_TITLE,
3160 CONTENT: this._cleanIncomingHTML(value),
3161 CSS: this.get('css'),
3162 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3163 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3166 if (document.compatMode != 'BackCompat') {
3167 YAHOO.log('Adding Doctype to editable area', 'info', 'SimpleEditor');
3168 html = this._docType + "\n" + html;
3170 YAHOO.log('DocType skipped because we are in BackCompat Mode.', 'warn', 'SimpleEditor');
3173 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3174 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3177 if (this.browser.air) {
3178 doc = this._getDoc().implementation.createHTMLDocument();
3179 var origDoc = this._getDoc();
3185 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3186 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3187 origDoc.body._rteLoaded = true;
3189 doc = this._getDoc();
3195 YAHOO.log('Setting doc failed.. (_setInitialContent)', 'error', 'SimpleEditor');
3196 //Safari will only be here if we are hidden
3200 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3201 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3203 this.get('iframe').setStyle('visibility', '');
3205 this._checkLoaded();
3210 * @method _setMarkupType
3211 * @param {String} action The action to take. Possible values are: css, default or semantic
3212 * @description This method will turn on/off the useCSS execCommand.
3214 _setMarkupType: function(action) {
3215 switch (this.get('markup')) {
3217 this._setEditorStyle(true);
3220 this._setEditorStyle(false);
3224 if (this._semantic[action]) {
3225 this._setEditorStyle(false);
3227 this._setEditorStyle(true);
3233 * Set the editor to use CSS instead of HTML
3234 * @param {Booleen} stat True/False
3236 _setEditorStyle: function(stat) {
3238 this._getDoc().execCommand('useCSS', false, !stat);
3244 * @method _getSelectedElement
3245 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3246 * @return {HTMLElement} The currently selected element.
3248 _getSelectedElement: function() {
3249 var doc = this._getDoc(),
3255 if (this.browser.ie) {
3256 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3257 range = this._getRange();
3259 elm = range.item ? range.item(0) : range.parentElement();
3260 if (this._hasSelection()) {
3262 //WTF.. Why can't I get an element reference here?!??!
3264 if (elm === doc.body) {
3268 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3269 elm = Event.getTarget(this.currentEvent);
3272 sel = this._getSelection();
3273 range = this._getRange();
3275 if (!sel || !range) {
3279 if (!this._hasSelection() && this.browser.webkit3) {
3282 if (this.browser.gecko) {
3284 if (range.startContainer) {
3285 if (range.startContainer.nodeType === 3) {
3286 elm = range.startContainer.parentNode;
3287 } else if (range.startContainer.nodeType === 1) {
3288 elm = range.startContainer;
3291 if (this.currentEvent) {
3292 var tar = Event.getTarget(this.currentEvent);
3293 if (!this._isElement(tar, 'html')) {
3303 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3304 if (sel.anchorNode.parentNode) { //next check parentNode
3305 elm = sel.anchorNode.parentNode;
3307 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3308 elm = sel.anchorNode.nextSibling;
3311 if (this._isElement(elm, 'br')) {
3315 elm = range.commonAncestorContainer;
3316 if (!range.collapsed) {
3317 if (range.startContainer == range.endContainer) {
3318 if (range.startOffset - range.endOffset < 2) {
3319 if (range.startContainer.hasChildNodes()) {
3320 elm = range.startContainer.childNodes[range.startOffset];
3329 if (this.currentEvent !== null) {
3331 switch (this.currentEvent.type) {
3335 if (this.browser.webkit) {
3336 elm = Event.getTarget(this.currentEvent);
3344 YAHOO.log('Firefox 1.5 errors here: ' + e, 'error', 'SimpleEditor');
3346 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3347 //TODO is this still needed?
3348 //elm = this.currentElement[0];
3352 if (this.browser.opera || this.browser.webkit) {
3353 if (this.currentEvent && !elm) {
3354 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3357 if (!elm || !elm.tagName) {
3360 if (this._isElement(elm, 'html')) {
3361 //Safari sometimes gives us the HTML node back..
3364 if (this._isElement(elm, 'body')) {
3365 //make sure that body means this body not the parent..
3368 if (elm && !elm.parentNode) { //Not in document
3371 if (elm === undefined) {
3378 * @method _getDomPath
3379 * @description This method will attempt to build the DOM path from the currently selected element.
3380 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3381 * @return {Array} An array of node references that will create the DOM Path.
3383 _getDomPath: function(el) {
3385 el = this._getSelectedElement();
3388 while (el !== null) {
3389 if (el.ownerDocument != this._getDoc()) {
3393 //Check to see if we get el.nodeName and nodeType
3394 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3395 domPath[domPath.length] = el;
3398 if (this._isElement(el, 'body')) {
3404 if (domPath.length === 0) {
3405 if (this._getDoc() && this._getDoc().body) {
3406 domPath[0] = this._getDoc().body;
3409 return domPath.reverse();
3413 * @method _writeDomPath
3414 * @description Write the current DOM path out to the dompath container below the editor.
3416 _writeDomPath: function() {
3417 var path = this._getDomPath(),
3422 for (var i = 0; i < path.length; i++) {
3423 var tag = path[i].tagName.toLowerCase();
3424 if ((tag == 'ol') && (path[i].type)) {
3425 tag += ':' + path[i].type;
3427 if (Dom.hasClass(path[i], 'yui-tag')) {
3428 tag = path[i].getAttribute('tag');
3430 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3432 case 'b': tag = 'strong'; break;
3433 case 'i': tag = 'em'; break;
3436 if (!Dom.hasClass(path[i], 'yui-non')) {
3437 if (Dom.hasClass(path[i], 'yui-tag')) {
3440 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3441 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3444 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3451 if (path[i].getAttribute('href', 2)) {
3452 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3456 var h = path[i].height;
3457 var w = path[i].width;
3458 if (path[i].style.height) {
3459 h = parseInt(path[i].style.height, 10);
3461 if (path[i].style.width) {
3462 w = parseInt(path[i].style.width, 10);
3464 pathStr += '(' + w + 'x' + h + ')';
3468 if (pathStr.length > 10) {
3469 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3471 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3473 pathArr[pathArr.length] = pathStr;
3476 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3477 //Prevent flickering
3478 if (this.dompath.innerHTML != str) {
3479 this.dompath.innerHTML = str;
3485 * @description Fix href and imgs as well as remove invalid HTML.
3487 _fixNodes: function() {
3488 var doc = this._getDoc(),
3491 for (var v in this.invalidHTML) {
3492 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3493 if (v.toLowerCase() != 'span') {
3494 var tags = doc.body.getElementsByTagName(v);
3496 for (var i = 0; i < tags.length; i++) {
3503 for (var h = 0; h < els.length; h++) {
3504 if (els[h].parentNode) {
3505 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3506 this._swapEl(els[h], 'span', function(el) {
3507 el.className = 'yui-non';
3510 els[h].parentNode.removeChild(els[h]);
3514 var imgs = this._getDoc().getElementsByTagName('img');
3515 Dom.addClass(imgs, 'yui-img');
3519 * @method _isNonEditable
3520 * @param Event ev The Dom event being checked
3521 * @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.
3522 * 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
3523 * disable and enable the Editor's toolbar based on the noedit state.
3526 _isNonEditable: function(ev) {
3527 if (this.get('allowNoEdit')) {
3528 var el = Event.getTarget(ev);
3529 if (this._isElement(el, 'html')) {
3532 var path = this._getDomPath(el);
3533 for (var i = (path.length - 1); i > -1; i--) {
3534 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3535 //if (this.toolbar.get('disabled') === false) {
3536 // this.toolbar.set('disabled', true);
3539 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3542 Event.stopEvent(ev);
3543 YAHOO.log('CLASS_NOEDIT found in DOM Path, stopping event', 'info', 'SimpleEditor');
3547 //if (this.toolbar.get('disabled') === true) {
3548 //Should only happen once..
3549 //this.toolbar.set('disabled', false);
3551 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3559 * @method _setCurrentEvent
3560 * @param {Event} ev The event to cache
3561 * @description Sets the current event property
3563 _setCurrentEvent: function(ev) {
3564 this.currentEvent = ev;
3568 * @method _handleClick
3569 * @param {Event} ev The event we are working on.
3570 * @description Handles all click events inside the iFrame document.
3572 _handleClick: function(ev) {
3573 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3574 if (ret === false) {
3577 if (this._isNonEditable(ev)) {
3580 this._setCurrentEvent(ev);
3581 if (this.currentWindow) {
3584 if (this.currentWindow) {
3587 if (this.browser.webkit) {
3588 var tar =Event.getTarget(ev);
3589 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3590 Event.stopEvent(ev);
3596 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3600 * @method _handleMouseUp
3601 * @param {Event} ev The event we are working on.
3602 * @description Handles all mouseup events inside the iFrame document.
3604 _handleMouseUp: function(ev) {
3605 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3606 if (ret === false) {
3609 if (this._isNonEditable(ev)) {
3612 //Don't set current event for mouseup.
3613 //It get's fired after a menu is closed and gives up a bogus event to work with
3614 //this._setCurrentEvent(ev);
3616 if (this.browser.opera) {
3618 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3620 * @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.
3622 var sel = Event.getTarget(ev);
3623 if (this._isElement(sel, 'img')) {
3625 if (this.operaEvent) {
3626 clearTimeout(this.operaEvent);
3627 this.operaEvent = null;
3628 this._handleDoubleClick(ev);
3630 this.operaEvent = window.setTimeout(function() {
3631 self.operaEvent = false;
3636 //This will stop Safari from selecting the entire document if you select all the text in the editor
3637 if (this.browser.webkit || this.browser.opera) {
3638 if (this.browser.webkit) {
3639 Event.stopEvent(ev);
3643 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3647 * @method _handleMouseDown
3648 * @param {Event} ev The event we are working on.
3649 * @description Handles all mousedown events inside the iFrame document.
3651 _handleMouseDown: function(ev) {
3652 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3653 if (ret === false) {
3656 if (this._isNonEditable(ev)) {
3659 this._setCurrentEvent(ev);
3660 var sel = Event.getTarget(ev);
3661 if (this.browser.webkit && this._hasSelection()) {
3662 var _sel = this._getSelection();
3663 if (!this.browser.webkit3) {
3664 _sel.collapse(true);
3666 _sel.collapseToStart();
3669 if (this.browser.webkit && this._lastImage) {
3670 Dom.removeClass(this._lastImage, 'selected');
3671 this._lastImage = null;
3673 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3674 if (this.browser.webkit) {
3675 Event.stopEvent(ev);
3676 if (this._isElement(sel, 'img')) {
3677 Dom.addClass(sel, 'selected');
3678 this._lastImage = sel;
3681 if (this.currentWindow) {
3686 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3690 * @method _handleDoubleClick
3691 * @param {Event} ev The event we are working on.
3692 * @description Handles all doubleclick events inside the iFrame document.
3694 _handleDoubleClick: function(ev) {
3695 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3696 if (ret === false) {
3699 if (this._isNonEditable(ev)) {
3702 this._setCurrentEvent(ev);
3703 var sel = Event.getTarget(ev);
3704 if (this._isElement(sel, 'img')) {
3705 this.currentElement[0] = sel;
3706 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3707 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3708 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3709 this.currentElement[0] = this._hasParent(sel, 'a');
3710 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3711 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3714 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3718 * @method _handleKeyUp
3719 * @param {Event} ev The event we are working on.
3720 * @description Handles all keyup events inside the iFrame document.
3722 _handleKeyUp: function(ev) {
3723 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3724 if (ret === false) {
3727 if (this._isNonEditable(ev)) {
3730 this._setCurrentEvent(ev);
3731 switch (ev.keyCode) {
3732 case this._keyMap.SELECT_ALL.key:
3733 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3737 case 32: //Space Bar
3740 case 37: //Left Arrow
3742 case 39: //Right Arrow
3743 case 40: //Down Arrow
3744 case 46: //Forward Delete
3746 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3747 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3748 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3752 if (!this.browser.ie) {
3753 if (this._nodeChangeTimer) {
3754 clearTimeout(this._nodeChangeTimer);
3757 this._nodeChangeTimer = setTimeout(function() {
3758 self._nodeChangeTimer = null;
3759 self.nodeChange.call(self);
3764 this.editorDirty = true;
3768 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3773 * @method _handleKeyPress
3774 * @param {Event} ev The event we are working on.
3775 * @description Handles all keypress events inside the iFrame document.
3777 _handleKeyPress: function(ev) {
3778 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3779 if (ret === false) {
3783 if (this.get('allowNoEdit')) {
3784 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3785 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3786 //Forward delete key
3787 YAHOO.log('allowNoEdit is set, forward delete key has been disabled', 'warn', 'SimpleEditor');
3788 Event.stopEvent(ev);
3791 if (this._isNonEditable(ev)) {
3794 this._setCurrentEvent(ev);
3795 if (this.browser.opera) {
3796 if (ev.keyCode === 13) {
3797 var tar = this._getSelectedElement();
3798 if (!this._isElement(tar, 'li')) {
3799 this.execCommand('inserthtml', '<br>');
3800 Event.stopEvent(ev);
3804 if (this.browser.webkit) {
3805 if (!this.browser.webkit3) {
3806 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3807 //This is CMD + z (for undo)
3808 if (this._hasParent(this._getSelectedElement(), 'li')) {
3809 YAHOO.log('We are in an LI and we found CMD + z, stopping the event', 'warn', 'SimpleEditor');
3810 Event.stopEvent(ev);
3816 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3820 * @method _handleKeyDown
3821 * @param {Event} ev The event we are working on.
3822 * @description Handles all keydown events inside the iFrame document.
3824 _handleKeyDown: function(ev) {
3825 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3826 if (ret === false) {
3829 var tar = null, _range = null;
3830 if (this._isNonEditable(ev)) {
3833 this._setCurrentEvent(ev);
3834 if (this.currentWindow) {
3837 if (this.currentWindow) {
3845 //YAHOO.log('keyCode: ' + ev.keyCode, 'info', 'SimpleEditor');
3847 switch (ev.keyCode) {
3848 case this._keyMap.FOCUS_TOOLBAR.key:
3849 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3850 var h = this.toolbar.getElementsByTagName('h2')[0];
3851 if (h && h.firstChild) {
3852 h.firstChild.focus();
3854 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3855 //Focus After Element - Esc
3856 this.afterElement.focus();
3858 Event.stopEvent(ev);
3862 case this._keyMap.CREATE_LINK.key: //L
3863 if (this._hasSelection()) {
3864 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3865 var makeLink = true;
3866 if (this.get('limitCommands')) {
3867 if (!this.toolbar.getButtonByValue('createlink')) {
3868 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
3873 this.execCommand('createlink', '');
3874 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3875 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3882 case this._keyMap.UNDO.key:
3883 case this._keyMap.REDO.key:
3884 if (this._checkKey(this._keyMap.REDO, ev)) {
3887 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3893 case this._keyMap.BOLD.key:
3894 if (this._checkKey(this._keyMap.BOLD, ev)) {
3899 case this._keyMap.FONT_SIZE_UP.key:
3900 case this._keyMap.FONT_SIZE_DOWN.key:
3901 var uk = false, dk = false;
3902 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3905 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3909 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3910 label = parseInt(fs_button.get('label'), 10),
3911 newValue = (label + 1);
3914 newValue = (label - 1);
3917 action = 'fontsize';
3918 value = newValue + 'px';
3923 case this._keyMap.ITALIC.key:
3924 if (this._checkKey(this._keyMap.ITALIC, ev)) {
3930 case this._keyMap.UNDERLINE.key:
3931 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3932 action = 'underline';
3937 if (this.browser.ie) {
3938 //Insert a tab in Internet Explorer
3939 _range = this._getRange();
3940 tar = this._getSelectedElement();
3941 if (!this._isElement(tar, 'li')) {
3943 _range.pasteHTML(' ');
3944 _range.collapse(false);
3947 Event.stopEvent(ev);
3951 if (this.browser.gecko > 1.8) {
3952 tar = this._getSelectedElement();
3953 if (this._isElement(tar, 'li')) {
3955 this._getDoc().execCommand('outdent', null, '');
3957 this._getDoc().execCommand('indent', null, '');
3960 } else if (!this._hasSelection()) {
3961 this.execCommand('inserthtml', ' ');
3963 Event.stopEvent(ev);
3967 var p = null, i = 0;
3968 if (this.get('ptags') && !ev.shiftKey) {
3969 if (this.browser.gecko) {
3970 tar = this._getSelectedElement();
3971 if (!this._hasParent(tar, 'li')) {
3972 if (this._hasParent(tar, 'p')) {
3973 p = this._getDoc().createElement('p');
3974 p.innerHTML = ' ';
3975 Dom.insertAfter(p, tar);
3976 this._selectNode(p.firstChild);
3977 } else if (this._isElement(tar, 'body')) {
3978 this.execCommand('insertparagraph', null);
3979 var ps = this._getDoc().body.getElementsByTagName('p');
3980 for (i = 0; i < ps.length; i++) {
3981 if (ps[i].getAttribute('_moz_dirty') !== null) {
3982 p = this._getDoc().createElement('p');
3983 p.innerHTML = ' ';
3984 Dom.insertAfter(p, ps[i]);
3985 this._selectNode(p.firstChild);
3986 ps[i].removeAttribute('_moz_dirty');
3990 YAHOO.log('Something went wrong with paragraphs, please file a bug!!', 'error', 'SimpleEditor');
3992 action = 'insertparagraph';
3994 Event.stopEvent(ev);
3997 if (this.browser.webkit) {
3998 tar = this._getSelectedElement();
3999 if (!this._hasParent(tar, 'li')) {
4000 this.execCommand('insertparagraph', null);
4001 var divs = this._getDoc().body.getElementsByTagName('div');
4002 for (i = 0; i < divs.length; i++) {
4003 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4004 Dom.addClass(divs[i], 'yui-wk-p');
4007 Event.stopEvent(ev);
4011 if (this.browser.webkit) {
4012 tar = this._getSelectedElement();
4013 if (!this._hasParent(tar, 'li')) {
4014 this.execCommand('inserthtml', '<var id="yui-br"></var>');
4015 var holder = this._getDoc().getElementById('yui-br'),
4016 br = this._getDoc().createElement('br'),
4017 caret = this._getDoc().createElement('span');
4019 holder.parentNode.replaceChild(br, holder);
4020 caret.className = 'yui-non';
4021 caret.innerHTML = ' ';
4022 Dom.insertAfter(caret, br);
4023 this._selectNode(caret);
4024 Event.stopEvent(ev);
4027 if (this.browser.ie) {
4028 YAHOO.log('Stopping P tags', 'info', 'SimpleEditor');
4029 //Insert a <br> instead of a <p></p> in Internet Explorer
4030 _range = this._getRange();
4031 tar = this._getSelectedElement();
4032 if (!this._isElement(tar, 'li')) {
4034 _range.pasteHTML('<br>');
4035 _range.collapse(false);
4038 Event.stopEvent(ev);
4044 if (this.browser.ie) {
4047 if (doExec && action) {
4048 this.execCommand(action, value);
4049 Event.stopEvent(ev);
4052 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4057 * @param {Event} ev The event we are working on.
4058 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4060 _listFix: function(ev) {
4061 //YAHOO.log('Lists Fix (' + ev.keyCode + ')', 'info', 'SimpleEditor');
4062 var testLi = null, par = null, preContent = false, range = null;
4064 if (this.browser.webkit) {
4065 if (ev.keyCode && (ev.keyCode == 13)) {
4066 if (this._hasParent(this._getSelectedElement(), 'li')) {
4067 var tar = this._hasParent(this._getSelectedElement(), 'li');
4068 if (tar.previousSibling) {
4069 if (tar.firstChild && (tar.firstChild.length == 1)) {
4070 this._selectNode(tar);
4077 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4078 testLi = this._getSelectedElement();
4079 if (this._hasParent(testLi, 'li')) {
4080 testLi = this._hasParent(testLi, 'li');
4081 YAHOO.log('We have a SHIFT tab in an LI, reverse it..', 'info', 'SimpleEditor');
4082 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4083 YAHOO.log('We have a double parent, move up a level', 'info', 'SimpleEditor');
4084 par = this._hasParent(testLi, 'ul');
4086 par = this._hasParent(testLi, 'ol');
4088 //YAHOO.log(par.previousSibling + ' :: ' + par.previousSibling.innerHTML);
4089 if (this._isElement(par.previousSibling, 'li')) {
4090 par.removeChild(testLi);
4091 par.parentNode.insertBefore(testLi, par.nextSibling);
4092 if (this.browser.ie) {
4093 range = this._getDoc().body.createTextRange();
4094 range.moveToElementText(testLi);
4095 range.collapse(false);
4098 if (this.browser.webkit) {
4099 this._selectNode(testLi.firstChild);
4101 Event.stopEvent(ev);
4107 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4108 YAHOO.log('List Fix - Tab', 'info', 'SimpleEditor');
4109 var preLi = this._getSelectedElement();
4110 if (this._hasParent(preLi, 'li')) {
4111 preContent = this._hasParent(preLi, 'li').innerHTML;
4113 //YAHOO.log('preLI: ' + preLi.tagName + ' :: ' + preLi.innerHTML);
4114 if (this.browser.webkit) {
4115 this._getDoc().execCommand('inserttext', false, '\t');
4117 testLi = this._getSelectedElement();
4118 if (this._hasParent(testLi, 'li')) {
4119 YAHOO.log('We have a tab in an LI', 'info', 'SimpleEditor');
4120 par = this._hasParent(testLi, 'li');
4121 YAHOO.log('parLI: ' + par.tagName + ' :: ' + par.innerHTML);
4122 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4123 if (this.browser.webkit) {
4124 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4125 //Remove the span element that Safari puts in
4127 par.removeChild(span[0]);
4128 par.innerHTML = Lang.trim(par.innerHTML);
4129 //Put the HTML from the LI into this new LI
4131 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4133 par.innerHTML = '<span class="yui-non"> </span> ';
4138 par.innerHTML = preContent + ' ';
4140 par.innerHTML = ' ';
4144 par.parentNode.replaceChild(newUl, par);
4145 newUl.appendChild(par);
4146 if (this.browser.webkit) {
4147 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4148 if (!this.browser.webkit3) {
4149 par.parentNode.parentNode.style.display = 'list-item';
4150 setTimeout(function() {
4151 par.parentNode.parentNode.style.display = 'block';
4154 } else if (this.browser.ie) {
4155 range = this._getDoc().body.createTextRange();
4156 range.moveToElementText(par);
4157 range.collapse(false);
4160 this._selectNode(par);
4162 Event.stopEvent(ev);
4164 if (this.browser.webkit) {
4165 Event.stopEvent(ev);
4171 * @method nodeChange
4172 * @param {Boolean} force Optional paramenter to skip the threshold counter
4173 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4175 nodeChange: function(force) {
4178 if (this.get('nodeChangeDelay')) {
4179 window.setTimeout(function() {
4180 NCself._nodeChange.apply(NCself, arguments);
4188 * @method _nodeChange
4189 * @param {Boolean} force Optional paramenter to skip the threshold counter
4190 * @description Fired from nodeChange in a setTimeout.
4192 _nodeChange: function(force) {
4193 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4194 thisNodeChange = Math.round(new Date().getTime() / 1000),
4197 if (force === true) {
4198 this._lastNodeChange = 0;
4201 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4202 if (this._fixNodesTimer === null) {
4203 this._fixNodesTimer = window.setTimeout(function() {
4204 self._fixNodes.call(self);
4205 self._fixNodesTimer = null;
4209 this._lastNodeChange = thisNodeChange;
4210 if (this.currentEvent) {
4212 this._lastNodeChangeEvent = this.currentEvent.type;
4216 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4217 if (beforeNodeChange === false) {
4220 if (this.get('dompath')) {
4221 window.setTimeout(function() {
4222 self._writeDomPath.call(self);
4225 //Check to see if we are disabled before continuing
4226 if (!this.get('disabled')) {
4227 if (this.STOP_NODE_CHANGE) {
4228 //Reset this var for next action
4229 this.STOP_NODE_CHANGE = false;
4232 var sel = this._getSelection(),
4233 range = this._getRange(),
4234 el = this._getSelectedElement(),
4235 fn_button = this.toolbar.getButtonByValue('fontname'),
4236 fs_button = this.toolbar.getButtonByValue('fontsize'),
4237 undo_button = this.toolbar.getButtonByValue('undo'),
4238 redo_button = this.toolbar.getButtonByValue('redo');
4240 //Handle updating the toolbar with active buttons
4242 if (this._lastButton) {
4243 _ex[this._lastButton.id] = true;
4244 //this._lastButton = null;
4246 if (!this._isElement(el, 'body')) {
4248 _ex[fn_button.get('id')] = true;
4251 _ex[fs_button.get('id')] = true;
4255 delete _ex[redo_button.get('id')];
4257 this.toolbar.resetAllButtons(_ex);
4259 //Handle disabled buttons
4260 for (var d = 0; d < this._disabled.length; d++) {
4261 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4262 if (_button && _button.get) {
4263 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4266 if (!this._hasSelection() && !this.get('insert')) {
4267 switch (this._disabled[d]) {
4272 //No Selection - disable
4273 this.toolbar.disableButton(_button);
4276 if (!this._alwaysDisabled[this._disabled[d]]) {
4277 this.toolbar.enableButton(_button);
4280 if (!this._alwaysEnabled[this._disabled[d]]) {
4281 this.toolbar.deselectButton(_button);
4286 var path = this._getDomPath();
4287 var tag = null, cmd = null;
4288 for (var i = 0; i < path.length; i++) {
4289 tag = path[i].tagName.toLowerCase();
4290 if (path[i].getAttribute('tag')) {
4291 tag = path[i].getAttribute('tag').toLowerCase();
4293 cmd = this._tag2cmd[tag];
4294 if (cmd === undefined) {
4297 if (!Lang.isArray(cmd)) {
4301 //Bold and Italic styles
4302 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4303 cmd[cmd.length] = 'bold';
4305 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4306 cmd[cmd.length] = 'italic';
4308 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4309 cmd[cmd.length] = 'underline';
4311 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4312 cmd[cmd.length] = 'strikethrough';
4314 if (cmd.length > 0) {
4315 for (var j = 0; j < cmd.length; j++) {
4316 this.toolbar.selectButton(cmd[j]);
4317 this.toolbar.enableButton(cmd[j]);
4321 switch (path[i].style.textAlign.toLowerCase()) {
4326 var alignType = path[i].style.textAlign.toLowerCase();
4327 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4330 this.toolbar.selectButton('justify' + alignType);
4331 this.toolbar.enableButton('justify' + alignType);
4337 //Reset Font Family and Size to the inital configs
4339 var family = fn_button._configs.label._initialConfig.value;
4340 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4341 this._updateMenuChecked('fontname', family);
4345 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4348 var hd_button = this.toolbar.getButtonByValue('heading');
4350 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4351 this._updateMenuChecked('heading', 'none');
4353 var img_button = this.toolbar.getButtonByValue('insertimage');
4354 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4355 this.toolbar.disableButton(img_button);
4357 if (this._lastButton && this._lastButton.isSelected) {
4358 this.toolbar.deselectButton(this._lastButton.id);
4360 this._undoNodeChange();
4364 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4368 * @method _updateMenuChecked
4369 * @param {Object} button The command identifier of the button you want to check
4370 * @param {String} value The value of the menu item you want to check
4371 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4372 * @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.
4374 _updateMenuChecked: function(button, value, tbar) {
4376 tbar = this.toolbar;
4378 var _button = tbar.getButtonByValue(button);
4379 _button.checkValue(value);
4383 * @method _handleToolbarClick
4384 * @param {Event} ev The event that triggered the button click
4385 * @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.
4387 _handleToolbarClick: function(ev) {
4390 var cmd = ev.button.value;
4391 if (ev.button.menucmd) {
4393 cmd = ev.button.menucmd;
4395 this._lastButton = ev.button;
4396 if (this.STOP_EXEC_COMMAND) {
4397 YAHOO.log('execCommand skipped because we found the STOP_EXEC_COMMAND flag set to true', 'warn', 'SimpleEditor');
4398 YAHOO.log('NOEXEC::execCommand::(' + cmd + '), (' + value + ')', 'warn', 'SimpleEditor');
4399 this.STOP_EXEC_COMMAND = false;
4402 this.execCommand(cmd, value);
4403 if (!this.browser.webkit) {
4405 setTimeout(function() {
4406 Fself.focus.call(Fself);
4410 Event.stopEvent(ev);
4414 * @method _setupAfterElement
4415 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4417 _setupAfterElement: function() {
4418 if (!this.beforeElement) {
4419 this.beforeElement = document.createElement('h2');
4420 this.beforeElement.className = 'yui-editor-skipheader';
4421 this.beforeElement.tabIndex = '-1';
4422 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4423 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4425 if (!this.afterElement) {
4426 this.afterElement = document.createElement('h2');
4427 this.afterElement.className = 'yui-editor-skipheader';
4428 this.afterElement.tabIndex = '-1';
4429 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4430 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4435 * @method _disableEditor
4436 * @param {Boolean} disabled Pass true to disable, false to enable
4437 * @description Creates a mask to place over the Editor.
4439 _disableEditor: function(disabled) {
4441 this._removeEditorEvents();
4443 if (!!this.browser.ie) {
4444 this._setDesignMode('off');
4447 this.toolbar.set('disabled', true);
4449 this._mask = document.createElement('DIV');
4450 Dom.addClass(this._mask, 'yui-editor-masked');
4451 this.get('iframe').get('parentNode').appendChild(this._mask);
4454 this._initEditorEvents();
4456 this._mask.parentNode.removeChild(this._mask);
4459 this.toolbar.set('disabled', false);
4461 this._setDesignMode('on');
4464 window.setTimeout(function() {
4465 self.nodeChange.call(self);
4471 * @property SEP_DOMPATH
4472 * @description The value to place in between the Dom path items
4477 * @property STR_LEAVE_EDITOR
4478 * @description The accessibility string for the element after the iFrame
4481 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4483 * @property STR_BEFORE_EDITOR
4484 * @description The accessibility string for the element before the iFrame
4487 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>',
4489 * @property STR_TITLE
4490 * @description The Title of the HTML document that is created in the iFrame
4493 STR_TITLE: 'Rich Text Area.',
4495 * @property STR_IMAGE_HERE
4496 * @description The text to place in the URL textbox when using the blankimage.
4499 STR_IMAGE_HERE: 'Image URL Here',
4501 * @property STR_IMAGE_URL
4502 * @description The label string for Image URL
4505 STR_IMAGE_URL: 'Image URL',
4507 * @property STR_LINK_URL
4508 * @description The label string for the Link URL.
4511 STR_LINK_URL: 'Link URL',
4514 * @property STOP_EXEC_COMMAND
4515 * @description Set to true when you want the default execCommand function to not process anything
4518 STOP_EXEC_COMMAND: false,
4521 * @property STOP_NODE_CHANGE
4522 * @description Set to true when you want the default nodeChange function to not process anything
4525 STOP_NODE_CHANGE: false,
4528 * @property CLASS_NOEDIT
4529 * @description CSS class applied to elements that are not editable.
4532 CLASS_NOEDIT: 'yui-noedit',
4535 * @property CLASS_CONTAINER
4536 * @description Default CSS class to apply to the editors container element
4539 CLASS_CONTAINER: 'yui-editor-container',
4542 * @property CLASS_EDITABLE
4543 * @description Default CSS class to apply to the editors iframe element
4546 CLASS_EDITABLE: 'yui-editor-editable',
4549 * @property CLASS_EDITABLE_CONT
4550 * @description Default CSS class to apply to the editors iframe's parent element
4553 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4556 * @property CLASS_PREFIX
4557 * @description Default prefix for dynamically created class names
4560 CLASS_PREFIX: 'yui-editor',
4563 * @description Standard browser detection
4566 browser: function() {
4567 var br = YAHOO.env.ua;
4569 if (br.webkit >= 420) {
4570 br.webkit3 = br.webkit;
4576 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4584 * @description The Editor class' initialization method
4586 init: function(p_oElement, p_oAttributes) {
4587 YAHOO.log('init', 'info', 'SimpleEditor');
4589 if (!this._defaultToolbar) {
4590 this._defaultToolbar = {
4592 titlebar: 'Text Editing Tools',
4595 { group: 'fontstyle', label: 'Font Name and Size',
4597 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4599 { text: 'Arial', checked: true },
4600 { text: 'Arial Black' },
4601 { text: 'Comic Sans MS' },
4602 { text: 'Courier New' },
4603 { text: 'Lucida Console' },
4605 { text: 'Times New Roman' },
4606 { text: 'Trebuchet MS' },
4610 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4613 { type: 'separator' },
4614 { group: 'textstyle', label: 'Font Style',
4616 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4617 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4618 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4619 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4620 { type: 'separator' },
4621 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4622 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4626 { type: 'separator' },
4627 { group: 'indentlist', label: 'Lists',
4629 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4630 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4633 { type: 'separator' },
4634 { group: 'insertitem', label: 'Insert Item',
4636 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4637 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4644 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4645 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4648 this.currentElement = [];
4649 this.on('contentReady', function() {
4650 this.DOMReady = true;
4656 * @method initAttributes
4657 * @description Initializes all of the configuration attributes used to create
4659 * @param {Object} attr Object literal specifying a set of
4660 * configuration attributes used to create the editor.
4662 initAttributes: function(attr) {
4663 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4667 * @config nodeChangeDelay
4668 * @description Do we wrap the nodeChange method in a timeout for performance, default: true.
4672 this.setAttributeConfig('nodeChangeDelay', {
4673 value: ((attr.nodeChangeDelay === false) ? false : true)
4677 * @description The max number of undo levels to store.
4681 this.setAttributeConfig('maxUndo', {
4683 value: attr.maxUndo || 30
4688 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4692 this.setAttributeConfig('ptags', {
4694 value: attr.ptags || false
4698 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4702 this.setAttributeConfig('insert', {
4704 value: attr.insert || false,
4705 method: function(insert) {
4713 var tmp = this._defaultToolbar.buttons;
4714 for (var i = 0; i < tmp.length; i++) {
4715 if (tmp[i].buttons) {
4716 for (var a = 0; a < tmp[i].buttons.length; a++) {
4717 if (tmp[i].buttons[a].value) {
4718 if (buttons[tmp[i].buttons[a].value]) {
4719 delete tmp[i].buttons[a].disabled;
4730 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4731 * We will create one and place it in this container. If no container is passed we will append to document.body.
4735 this.setAttributeConfig('container', {
4737 value: attr.container || false
4741 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4745 this.setAttributeConfig('plainText', {
4747 value: attr.plainText || false
4752 * @description Internal config for holding the iframe element.
4756 this.setAttributeConfig('iframe', {
4761 * @depreciated - No longer used, should use this.get('element')
4763 * @description Internal config for holding the textarea element (replaced with element).
4767 this.setAttributeConfig('textarea', {
4772 * @config nodeChangeThreshold
4773 * @description The number of seconds that need to be in between nodeChange processing
4777 this.setAttributeConfig('nodeChangeThreshold', {
4778 value: attr.nodeChangeThreshold || 3,
4779 validator: YAHOO.lang.isNumber
4782 * @config allowNoEdit
4783 * @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.
4784 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4788 this.setAttributeConfig('allowNoEdit', {
4789 value: attr.allowNoEdit || false,
4790 validator: YAHOO.lang.isBoolean
4793 * @config limitCommands
4794 * @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.
4798 this.setAttributeConfig('limitCommands', {
4799 value: attr.limitCommands || false,
4800 validator: YAHOO.lang.isBoolean
4803 * @config element_cont
4804 * @description Internal config for the editors container
4808 this.setAttributeConfig('element_cont', {
4809 value: attr.element_cont
4813 * @config editor_wrapper
4814 * @description The outter wrapper for the entire editor.
4818 this.setAttributeConfig('editor_wrapper', {
4819 value: attr.editor_wrapper || null,
4824 * @description The height of the editor iframe container, not including the toolbar..
4825 * @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
4828 this.setAttributeConfig('height', {
4829 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4830 method: function(height) {
4831 if (this._rendered) {
4832 //We have been rendered, change the height
4833 if (this.get('animate')) {
4834 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4836 to: parseInt(height, 10)
4841 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4847 * @config autoHeight
4848 * @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.
4850 * @type Boolean || Number
4852 this.setAttributeConfig('autoHeight', {
4853 value: attr.autoHeight || false,
4854 method: function(a) {
4856 if (this.get('iframe')) {
4857 this.get('iframe').get('element').setAttribute('scrolling', 'no');
4859 this.on('afterNodeChange', this._handleAutoHeight, this, true);
4860 this.on('editorKeyDown', this._handleAutoHeight, this, true);
4861 this.on('editorKeyPress', this._handleAutoHeight, this, true);
4863 if (this.get('iframe')) {
4864 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4866 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4867 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4868 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4874 * @description The width of the editor container.
4875 * @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
4878 this.setAttributeConfig('width', {
4879 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
4880 method: function(width) {
4881 if (this._rendered) {
4882 //We have been rendered, change the width
4883 if (this.get('animate')) {
4884 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
4886 to: parseInt(width, 10)
4891 this.get('element_cont').setStyle('width', width);
4898 * @attribute blankimage
4899 * @description The URL for the image placeholder to put in when inserting an image.
4900 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
4903 this.setAttributeConfig('blankimage', {
4904 value: attr.blankimage || this._getBlankImage()
4908 * @description The Base CSS used to format the content of the editor
4909 * @default <code><pre>html {
4914 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
4918 text-decoration: underline;
4921 .warning-localfile {
4922 border-bottom: 1px dashed red !important;
4925 cursor: wait !important;
4927 img.selected { //Safari image selection
4928 border: 2px dotted #808080;
4931 cursor: pointer !important;
4937 this.setAttributeConfig('css', {
4938 value: attr.css || this._defaultCSS,
4943 * @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)
4944 * @default This HTML requires a few things if you are to override:
4945 <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>
4946 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
4951 <title>{TITLE}</title>
4952 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4963 <body onload="document.body._rteLoaded = true;">
4971 this.setAttributeConfig('html', {
4972 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>',
4977 * @attribute extracss
4978 * @description Extra user defined css to load after the default SimpleEditor CSS
4982 this.setAttributeConfig('extracss', {
4983 value: attr.extracss || '',
4988 * @attribute handleSubmit
4989 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
4990 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
4991 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
4995 this.setAttributeConfig('handleSubmit', {
4996 value: attr.handleSubmit || false,
4997 method: function(exec) {
4998 if (this.get('element').form) {
4999 if (!this._formButtons) {
5000 this._formButtons = [];
5003 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5004 var i = this.get('element').form.getElementsByTagName('input');
5005 for (var s = 0; s < i.length; s++) {
5006 var type = i[s].getAttribute('type');
5007 if (type && (type.toLowerCase() == 'submit')) {
5008 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5009 this._formButtons[this._formButtons.length] = i[s];
5013 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5014 if (this._formButtons) {
5015 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5022 * @attribute disabled
5023 * @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.
5024 All Toolbar buttons are also disabled so they cannot be used.
5029 this.setAttributeConfig('disabled', {
5031 method: function(disabled) {
5032 if (this._rendered) {
5033 this._disableEditor(disabled);
5039 * @description When save HTML is called, this element will be updated as well as the source of data.
5043 this.setAttributeConfig('saveEl', {
5044 value: this.get('element')
5047 * @config toolbar_cont
5048 * @description Internal config for the toolbars container
5052 this.setAttributeConfig('toolbar_cont', {
5057 * @attribute toolbar
5058 * @description The default toolbar config.
5061 this.setAttributeConfig('toolbar', {
5062 value: attr.toolbar || this._defaultToolbar,
5064 method: function(toolbar) {
5065 if (!toolbar.buttonType) {
5066 toolbar.buttonType = this._defaultToolbar.buttonType;
5068 this._defaultToolbar = toolbar;
5072 * @attribute animate
5073 * @description Should the editor animate window movements
5074 * @default false unless Animation is found, then true
5077 this.setAttributeConfig('animate', {
5078 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5079 validator: function(value) {
5081 if (!YAHOO.util.Anim) {
5089 * @description A reference to the panel we are using for windows.
5093 this.setAttributeConfig('panel', {
5096 validator: function(value) {
5098 if (!YAHOO.widget.Overlay) {
5105 * @attribute focusAtStart
5106 * @description Should we focus the window when the content is ready?
5110 this.setAttributeConfig('focusAtStart', {
5111 value: attr.focusAtStart || false,
5113 method: function(fs) {
5115 this.on('editorContentLoaded', function() {
5117 setTimeout(function() {
5118 self.focus.call(self);
5119 self.editorDirty = false;
5126 * @attribute dompath
5127 * @description Toggle the display of the current Dom path below the editor
5131 this.setAttributeConfig('dompath', {
5132 value: attr.dompath || false,
5133 method: function(dompath) {
5134 if (dompath && !this.dompath) {
5135 this.dompath = document.createElement('DIV');
5136 this.dompath.id = this.get('id') + '_dompath';
5137 Dom.addClass(this.dompath, 'dompath');
5138 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5139 if (this.get('iframe')) {
5140 this._writeDomPath();
5142 } else if (!dompath && this.dompath) {
5143 this.dompath.parentNode.removeChild(this.dompath);
5144 this.dompath = null;
5150 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5151 * @default "semantic"
5154 this.setAttributeConfig('markup', {
5155 value: attr.markup || 'semantic',
5156 validator: function(markup) {
5157 switch (markup.toLowerCase()) {
5168 * @attribute removeLineBreaks
5169 * @description Should we remove linebreaks and extra spaces on cleanup
5173 this.setAttributeConfig('removeLineBreaks', {
5174 value: attr.removeLineBreaks || false,
5175 validator: YAHOO.lang.isBoolean
5180 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5181 * @type {Boolean/String}
5183 this.setAttributeConfig('drag', {
5185 value: attr.drag || false
5190 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5191 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5194 this.setAttributeConfig('resize', {
5196 value: attr.resize || false
5200 * @config filterWord
5201 * @description Attempt to filter out MS Word HTML from the Editor's output.
5204 this.setAttributeConfig('filterWord', {
5205 value: attr.filterWord || false,
5206 validator: YAHOO.lang.isBoolean
5212 * @method _getBlankImage
5213 * @description Retrieves the full url of the image to use as the blank image.
5214 * @return {String} The URL to the blank image
5216 _getBlankImage: function() {
5217 if (!this.DOMReady) {
5218 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5222 if (!this._blankImageLoaded) {
5223 if (YAHOO.widget.EditorInfo.blankImage) {
5224 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5225 this._blankImageLoaded = true;
5227 var div = document.createElement('div');
5228 div.style.position = 'absolute';
5229 div.style.top = '-9999px';
5230 div.style.left = '-9999px';
5231 div.className = this.CLASS_PREFIX + '-blankimage';
5232 document.body.appendChild(div);
5233 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5234 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5236 img = img.replace('app:/', '');
5237 this.set('blankimage', img);
5238 this._blankImageLoaded = true;
5239 div.parentNode.removeChild(div);
5240 YAHOO.widget.EditorInfo.blankImage = img;
5243 img = this.get('blankimage');
5249 * @method _handleAutoHeight
5250 * @description Handles resizing the editor's height based on the content
5252 _handleAutoHeight: function() {
5253 var doc = this._getDoc(),
5255 docEl = doc.documentElement;
5257 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5258 var newHeight = body.scrollHeight;
5259 if (this.browser.webkit) {
5260 newHeight = docEl.scrollHeight;
5262 if (newHeight < parseInt(this.get('height'), 10)) {
5263 newHeight = parseInt(this.get('height'), 10);
5265 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5266 var anim = this.get('animate');
5267 this.set('animate', false);
5268 this.set('height', newHeight + 'px');
5269 this.set('animate', anim);
5270 if (this.browser.ie) {
5271 //Internet Explorer needs this
5272 this.get('iframe').setStyle('height', '99%');
5273 this.get('iframe').setStyle('zoom', '1');
5275 window.setTimeout(function() {
5276 self.get('iframe').setStyle('height', '100%');
5283 * @property _formButtons
5284 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5290 * @property _formButtonClicked
5291 * @description The form button that was clicked to submit the form.
5294 _formButtonClicked: null,
5297 * @method _handleFormButtonClick
5298 * @description The click listener assigned to each submit button in the Editor's parent form.
5299 * @param {Event} ev The click event
5301 _handleFormButtonClick: function(ev) {
5302 var tar = Event.getTarget(ev);
5303 this._formButtonClicked = tar;
5307 * @method _handleFormSubmit
5308 * @description Handles the form submission.
5309 * @param {Object} ev The Form Submit Event
5311 _handleFormSubmit: function(ev) {
5314 var form = this.get('element').form,
5315 tar = this._formButtonClicked || false;
5317 Event.removeListener(form, 'submit', this._handleFormSubmit);
5318 if (YAHOO.env.ua.ie) {
5319 //form.fireEvent("onsubmit");
5320 if (tar && !tar.disabled) {
5323 } else { // Gecko, Opera, and Safari
5324 if (tar && !tar.disabled) {
5327 var oEvent = document.createEvent("HTMLEvents");
5328 oEvent.initEvent("submit", true, true);
5329 form.dispatchEvent(oEvent);
5330 if (YAHOO.env.ua.webkit) {
5331 if (YAHOO.lang.isFunction(form.submit)) {
5337 //Removed this, not need since removing Safari 2.x
5338 //Event.stopEvent(ev);
5342 * @method _handleFontSize
5343 * @description Handles the font size button in the toolbar.
5344 * @param {Object} o Object returned from Toolbar's buttonClick Event
5346 _handleFontSize: function(o) {
5347 var button = this.toolbar.getButtonById(o.button.id);
5348 var value = button.get('label') + 'px';
5349 this.execCommand('fontsize', value);
5354 * @description Handles the colorpicker buttons in the toolbar.
5355 * @param {Object} o Object returned from Toolbar's buttonClick Event
5357 _handleColorPicker: function(o) {
5359 var value = '#' + o.color;
5360 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5361 this.execCommand(cmd, value);
5366 * @method _handleAlign
5367 * @description Handles the alignment buttons in the toolbar.
5368 * @param {Object} o Object returned from Toolbar's buttonClick Event
5370 _handleAlign: function(o) {
5372 for (var i = 0; i < o.button.menu.length; i++) {
5373 if (o.button.menu[i].value == o.button.value) {
5374 cmd = o.button.menu[i].value;
5377 var value = this._getSelection();
5379 this.execCommand(cmd, value);
5384 * @method _handleAfterNodeChange
5385 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5387 _handleAfterNodeChange: function() {
5388 var path = this._getDomPath(),
5393 fn_button = this.toolbar.getButtonByValue('fontname'),
5394 fs_button = this.toolbar.getButtonByValue('fontsize'),
5395 hd_button = this.toolbar.getButtonByValue('heading');
5397 for (var i = 0; i < path.length; i++) {
5400 var tag = elm.tagName.toLowerCase();
5403 if (elm.getAttribute('tag')) {
5404 tag = elm.getAttribute('tag');
5407 family = elm.getAttribute('face');
5408 if (Dom.getStyle(elm, 'font-family')) {
5409 family = Dom.getStyle(elm, 'font-family');
5411 family = family.replace(/'/g, '');
5414 if (tag.substring(0, 1) == 'h') {
5416 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5417 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5418 hd_button.set('label', hd_button._configs.menu.value[h].text);
5421 this._updateMenuChecked('heading', tag);
5427 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5428 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5430 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5434 family = fn_button._configs.label._initialConfig.value;
5436 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5437 if (fn_button.get('label') != familyLabel) {
5438 fn_button.set('label', familyLabel);
5439 this._updateMenuChecked('fontname', family);
5444 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5445 if ((fontsize === null) || isNaN(fontsize)) {
5446 fontsize = fs_button._configs.label._initialConfig.value;
5448 fs_button.set('label', ''+fontsize);
5451 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5452 this.toolbar.enableButton(fn_button);
5453 this.toolbar.enableButton(fs_button);
5454 this.toolbar.enableButton('forecolor');
5455 this.toolbar.enableButton('backcolor');
5457 if (this._isElement(elm, 'img')) {
5458 if (YAHOO.widget.Overlay) {
5459 this.toolbar.enableButton('createlink');
5462 if (this._hasParent(elm, 'blockquote')) {
5463 this.toolbar.selectButton('indent');
5464 this.toolbar.disableButton('indent');
5465 this.toolbar.enableButton('outdent');
5467 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5468 this.toolbar.disableButton('indent');
5470 this._lastButton = null;
5475 * @method _handleInsertImageClick
5476 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5478 _handleInsertImageClick: function() {
5479 if (this.get('limitCommands')) {
5480 if (!this.toolbar.getButtonByValue('insertimage')) {
5481 YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'SimpleEditor');
5486 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5487 var _handleAEC = function() {
5488 var el = this.currentElement[0],
5491 el = this._getSelectedElement();
5494 if (el.getAttribute('src')) {
5495 src = el.getAttribute('src', 2);
5496 if (src.indexOf(this.get('blankimage')) != -1) {
5497 src = this.STR_IMAGE_HERE;
5501 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5502 if ((str !== '') && (str !== null)) {
5503 el.setAttribute('src', str);
5504 } else if (str === '') {
5505 el.parentNode.removeChild(el);
5506 this.currentElement = [];
5508 } else if ((str === null)) {
5509 src = el.getAttribute('src', 2);
5510 if (src.indexOf(this.get('blankimage')) != -1) {
5511 el.parentNode.removeChild(el);
5512 this.currentElement = [];
5517 this.toolbar.set('disabled', false);
5518 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5520 this.on('afterExecCommand', _handleAEC, this, true);
5524 * @method _handleInsertImageWindowClose
5525 * @description Handles the closing of the Image Properties Window.
5527 _handleInsertImageWindowClose: function() {
5532 * @method _isLocalFile
5533 * @param {String} url THe url/string to check
5534 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5536 _isLocalFile: function(url) {
5537 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5544 * @method _handleCreateLinkClick
5545 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5547 _handleCreateLinkClick: function() {
5548 if (this.get('limitCommands')) {
5549 if (!this.toolbar.getButtonByValue('createlink')) {
5550 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
5555 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5557 var _handleAEC = function() {
5558 var el = this.currentElement[0],
5562 if (el.getAttribute('href', 2) !== null) {
5563 url = el.getAttribute('href', 2);
5566 var str = prompt(this.STR_LINK_URL + ': ', url);
5567 if ((str !== '') && (str !== null)) {
5569 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5570 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5571 //Found an @ sign, prefix with mailto:
5572 urlValue = 'mailto:' + urlValue;
5574 /* :// not found adding */
5575 if (urlValue.substring(0, 1) != '#') {
5576 //urlValue = 'http:/'+'/' + urlValue;
5580 el.setAttribute('href', urlValue);
5581 } else if (str !== null) {
5582 var _span = this._getDoc().createElement('span');
5583 _span.innerHTML = el.innerHTML;
5584 Dom.addClass(_span, 'yui-non');
5585 el.parentNode.replaceChild(_span, el);
5588 this.toolbar.set('disabled', false);
5589 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5591 this.on('afterExecCommand', _handleAEC, this);
5596 * @method _handleCreateLinkWindowClose
5597 * @description Handles the closing of the Link Properties Window.
5599 _handleCreateLinkWindowClose: function() {
5601 this.currentElement = [];
5605 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5607 render: function() {
5608 if (this._rendered) {
5611 YAHOO.log('Render', 'info', 'SimpleEditor');
5612 if (!this.DOMReady) {
5613 YAHOO.log('!DOMReady', 'info', 'SimpleEditor');
5614 this._queue[this._queue.length] = ['render', arguments];
5617 if (this.get('element')) {
5618 if (this.get('element').tagName) {
5619 this._textarea = true;
5620 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5621 this._textarea = false;
5624 YAHOO.log('No Valid Element', 'error', 'SimpleEditor');
5628 YAHOO.log('No Element', 'error', 'SimpleEditor');
5631 this._rendered = true;
5633 window.setTimeout(function() {
5634 self._render.call(self);
5640 * @description Causes the toolbar and the editor to render and replace the textarea.
5642 _render: function() {
5644 this.set('textarea', this.get('element'));
5646 this.get('element_cont').setStyle('display', 'none');
5647 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5649 this.set('iframe', this._createIframe());
5651 window.setTimeout(function() {
5652 self._setInitialContent.call(self);
5655 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5657 if (this.get('disabled')) {
5658 this._disableEditor(true);
5661 var tbarConf = this.get('toolbar');
5662 //Create Toolbar instance
5663 if (tbarConf instanceof Toolbar) {
5664 this.toolbar = tbarConf;
5665 //Set the toolbar to disabled until content is loaded
5666 this.toolbar.set('disabled', true);
5668 //Set the toolbar to disabled until content is loaded
5669 tbarConf.disabled = true;
5670 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5673 YAHOO.log('fireEvent::toolbarLoaded', 'info', 'SimpleEditor');
5674 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5677 this.toolbar.on('toolbarCollapsed', function() {
5678 if (this.currentWindow) {
5682 this.toolbar.on('toolbarExpanded', function() {
5683 if (this.currentWindow) {
5687 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5689 this.toolbar.on('colorPickerClicked', function(o) {
5690 this._handleColorPicker(o);
5691 return false; //Stop the buttonClick event
5694 this.toolbar.on('alignClick', this._handleAlign, this, true);
5695 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5696 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5697 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5698 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5699 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5702 //Replace Textarea with editable area
5703 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5706 this.setStyle('visibility', 'hidden');
5707 this.setStyle('position', 'absolute');
5708 this.setStyle('top', '-9999px');
5709 this.setStyle('left', '-9999px');
5710 this.get('element_cont').appendChild(this.get('element'));
5711 this.get('element_cont').setStyle('display', 'block');
5714 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5715 this.get('iframe').addClass(this.CLASS_EDITABLE);
5717 //Set height and width of editor container
5718 this.get('element_cont').setStyle('width', this.get('width'));
5719 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5721 this.get('iframe').setStyle('width', '100%'); //WIDTH
5722 this.get('iframe').setStyle('height', '100%');
5726 window.setTimeout(function() {
5727 self._setupAfterElement.call(self);
5729 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5732 * @method execCommand
5733 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5734 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5735 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5737 execCommand: function(action, value) {
5738 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5739 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5740 this.STOP_EXEC_COMMAND = false;
5743 this._lastCommand = action;
5744 this._setMarkupType(action);
5745 if (this.browser.ie) {
5746 this._getWindow().focus();
5750 if (this.get('limitCommands')) {
5751 if (!this.toolbar.getButtonByValue(action)) {
5752 YAHOO.log('Toolbar Button for (' + action + ') was not found, skipping exec.', 'info', 'SimpleEditor');
5757 this.editorDirty = true;
5759 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5760 YAHOO.log('Found execCommand override method: (cmd_' + action.toLowerCase() + ')', 'info', 'SimpleEditor');
5761 var retValue = this['cmd_' + action.toLowerCase()](value);
5764 action = retValue[1];
5767 value = retValue[2];
5771 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'SimpleEditor');
5773 this._getDoc().execCommand(action, false, value);
5775 YAHOO.log('execCommand Failed', 'error', 'SimpleEditor');
5778 YAHOO.log('OVERRIDE::execCommand::(' + action + '),(' + value + ') skipped', 'warn', 'SimpleEditor');
5780 this.on('afterExecCommand', function() {
5781 this.unsubscribeAll('afterExecCommand');
5784 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5787 /* {{{ Command Overrides */
5791 * @param value Value passed from the execCommand method
5792 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5794 cmd_bold: function(value) {
5795 if (!this.browser.webkit) {
5796 var el = this._getSelectedElement();
5797 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5798 if (el.style.fontWeight == 'bold') {
5799 el.style.fontWeight = '';
5800 var b = this._getDoc().createElement('b'),
5801 par = el.parentNode;
5802 par.replaceChild(b, el);
5810 * @method cmd_italic
5811 * @param value Value passed from the execCommand method
5812 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5815 cmd_italic: function(value) {
5816 if (!this.browser.webkit) {
5817 var el = this._getSelectedElement();
5818 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5819 if (el.style.fontStyle == 'italic') {
5820 el.style.fontStyle = '';
5821 var i = this._getDoc().createElement('i'),
5822 par = el.parentNode;
5823 par.replaceChild(i, el);
5833 * @method cmd_underline
5834 * @param value Value passed from the execCommand method
5835 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5837 cmd_underline: function(value) {
5838 if (!this.browser.webkit) {
5839 var el = this._getSelectedElement();
5840 if (el && this._isElement(el, 'span')) {
5841 if (el.style.textDecoration == 'underline') {
5842 el.style.textDecoration = 'none';
5844 el.style.textDecoration = 'underline';
5852 * @method cmd_backcolor
5853 * @param value Value passed from the execCommand method
5854 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5856 cmd_backcolor: function(value) {
5858 el = this._getSelectedElement(),
5859 action = 'backcolor';
5861 if (this.browser.gecko || this.browser.opera) {
5862 this._setEditorStyle(true);
5863 action = 'hilitecolor';
5866 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5867 el.style.backgroundColor = value;
5868 this._selectNode(el);
5871 if (this.get('insert')) {
5872 el = this._createInsertElement({ backgroundColor: value });
5874 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
5875 this._selectNode(this.currentElement[0]);
5880 return [exec, action];
5883 * @method cmd_forecolor
5884 * @param value Value passed from the execCommand method
5885 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
5887 cmd_forecolor: function(value) {
5889 el = this._getSelectedElement();
5891 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5892 Dom.setStyle(el, 'color', value);
5893 this._selectNode(el);
5896 if (this.get('insert')) {
5897 el = this._createInsertElement({ color: value });
5899 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
5900 this._selectNode(this.currentElement[0]);
5907 * @method cmd_unlink
5908 * @param value Value passed from the execCommand method
5909 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
5911 cmd_unlink: function(value) {
5912 this._swapEl(this.currentElement[0], 'span', function(el) {
5913 el.className = 'yui-non';
5918 * @method cmd_createlink
5919 * @param value Value passed from the execCommand method
5920 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
5922 cmd_createlink: function(value) {
5923 var el = this._getSelectedElement(), _a = null;
5924 if (this._hasParent(el, 'a')) {
5925 this.currentElement[0] = this._hasParent(el, 'a');
5926 } else if (this._isElement(el, 'li')) {
5927 _a = this._getDoc().createElement('a');
5928 _a.innerHTML = el.innerHTML;
5931 this.currentElement[0] = _a;
5932 } else if (!this._isElement(el, 'a')) {
5933 this._createCurrentElement('a');
5934 _a = this._swapEl(this.currentElement[0], 'a');
5935 this.currentElement[0] = _a;
5937 this.currentElement[0] = el;
5942 * @method cmd_insertimage
5943 * @param value Value passed from the execCommand method
5944 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
5946 cmd_insertimage: function(value) {
5947 var exec = true, _img = null, action = 'insertimage',
5948 el = this._getSelectedElement();
5951 value = this.get('blankimage');
5955 * @knownissue Safari Cursor Position
5956 * @browser Safari 2.x
5957 * @description The issue here is that we have no way of knowing where the cursor position is
5958 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5961 YAHOO.log('InsertImage: ' + el.tagName, 'info', 'SimpleEditor');
5962 if (this._isElement(el, 'img')) {
5963 this.currentElement[0] = el;
5966 if (this._getDoc().queryCommandEnabled(action)) {
5967 this._getDoc().execCommand('insertimage', false, value);
5968 var imgs = this._getDoc().getElementsByTagName('img');
5969 for (var i = 0; i < imgs.length; i++) {
5970 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
5971 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
5972 this.currentElement[0] = imgs[i];
5977 if (el == this._getDoc().body) {
5978 _img = this._getDoc().createElement('img');
5979 _img.setAttribute('src', value);
5980 YAHOO.util.Dom.addClass(_img, 'yui-img');
5981 this._getDoc().body.appendChild(_img);
5983 this._createCurrentElement('img');
5984 _img = this._getDoc().createElement('img');
5985 _img.setAttribute('src', value);
5986 YAHOO.util.Dom.addClass(_img, 'yui-img');
5987 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
5989 this.currentElement[0] = _img;
5996 * @method cmd_inserthtml
5997 * @param value Value passed from the execCommand method
5998 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6000 cmd_inserthtml: function(value) {
6001 var exec = true, action = 'inserthtml', _span = null, _range = null;
6003 * @knownissue Safari cursor position
6004 * @browser Safari 2.x
6005 * @description The issue here is that we have no way of knowing where the cursor position is
6006 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6008 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6009 YAHOO.log('More Safari DOM tricks (inserthtml)', 'info', 'EditorSafari');
6010 this._createCurrentElement('img');
6011 _span = this._getDoc().createElement('span');
6012 _span.innerHTML = value;
6013 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6015 } else if (this.browser.ie) {
6016 _range = this._getRange();
6018 _range.item(0).outerHTML = value;
6020 _range.pasteHTML(value);
6028 * @param tag The tag of the list you want to create (eg, ul or ol)
6029 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6031 cmd_list: function(tag) {
6032 var exec = true, list = null, li = 0, el = null, str = '',
6033 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6035 action = 'insertunorderedlist';
6038 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6039 * @browser Safari 2.x
6040 * The issue with this workaround is that when applied to a set of text
6041 * that has BR's in it, Safari may or may not pick up the individual items as
6042 * list items. This is fixed in WebKit (Safari 3)
6043 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6045 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6046 if (this.browser.webkit) {
6047 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6048 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6049 el = selEl.parentNode;
6050 list = this._getDoc().createElement('span');
6051 YAHOO.util.Dom.addClass(list, 'yui-non');
6053 var lis = el.getElementsByTagName('li');
6054 for (li = 0; li < lis.length; li++) {
6055 str += '<div>' + lis[li].innerHTML + '</div>';
6057 list.innerHTML = str;
6058 this.currentElement[0] = el;
6059 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6061 YAHOO.log('Create list item', 'info', 'SimpleEditor');
6062 this._createCurrentElement(tag.toLowerCase());
6063 list = this._getDoc().createElement(tag);
6064 for (li = 0; li < this.currentElement.length; li++) {
6065 var newli = this._getDoc().createElement('li');
6066 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
6067 list.appendChild(newli);
6069 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6073 var items = list.firstChild.innerHTML.split('<br>');
6074 if (items.length > 0) {
6075 list.innerHTML = '';
6076 for (var i = 0; i < items.length; i++) {
6077 var item = this._getDoc().createElement('li');
6078 item.innerHTML = items[i];
6079 list.appendChild(item);
6083 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6084 this.currentElement[0] = list;
6085 var _h = this.currentElement[0].firstChild;
6086 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6087 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6091 el = this._getSelectedElement();
6092 YAHOO.log(el.tagName);
6093 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..
6094 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6095 if (this.browser.ie) {
6096 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6097 el = el.getElementsByTagName('li')[0];
6099 YAHOO.log('Undo IE', 'info', 'SimpleEditor');
6101 var lis2 = el.parentNode.getElementsByTagName('li');
6102 for (var j = 0; j < lis2.length; j++) {
6103 str += lis2[j].innerHTML + '<br>';
6105 var newEl = this._getDoc().createElement('span');
6106 newEl.innerHTML = str;
6107 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6110 this._getDoc().execCommand(action, '', el.parentNode);
6115 if (this.browser.opera) {
6117 window.setTimeout(function() {
6118 var liso = self._getDoc().getElementsByTagName('li');
6119 for (var i = 0; i < liso.length; i++) {
6120 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6121 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6126 if (this.browser.ie && exec) {
6128 if (this._getRange().html) {
6129 html = '<li>' + this._getRange().html+ '</li>';
6131 var t = this._getRange().text.split('\n');
6134 for (var ie = 0; ie < t.length; ie++) {
6135 html += '<li>' + t[ie] + '</li>';
6138 var txt = this._getRange().text;
6140 html = '<li id="new_list_item">' + txt + '</li>';
6142 html = '<li>' + txt + '</li>';
6146 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6147 var new_item = this._getDoc().getElementById('new_list_item');
6149 var range = this._getDoc().body.createTextRange();
6150 range.moveToElementText(new_item);
6151 range.collapse(false);
6161 * @method cmd_insertorderedlist
6162 * @param value Value passed from the execCommand method
6163 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6165 cmd_insertorderedlist: function(value) {
6166 return [this.cmd_list('ol')];
6169 * @method cmd_insertunorderedlist
6170 * @param value Value passed from the execCommand method
6171 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6173 cmd_insertunorderedlist: function(value) {
6174 return [this.cmd_list('ul')];
6177 * @method cmd_fontname
6178 * @param value Value passed from the execCommand method
6179 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6181 cmd_fontname: function(value) {
6183 selEl = this._getSelectedElement();
6185 this.currentFont = value;
6186 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6187 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6189 } else if (this.get('insert') && !this._hasSelection()) {
6190 YAHOO.log('No selection and no selected element and we are in insert mode', 'info', 'SimpleEditor');
6191 var el = this._createInsertElement({ fontFamily: value });
6197 * @method cmd_fontsize
6198 * @param value Value passed from the execCommand method
6199 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6201 cmd_fontsize: function(value) {
6202 var el = null, go = true;
6203 el = this._getSelectedElement();
6204 if (this.browser.webkit) {
6205 if (this.currentElement[0]) {
6206 if (el == this.currentElement[0]) {
6208 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6209 this._selectNode(el);
6210 this.currentElement[0] = el;
6215 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6216 el = this._getSelectedElement();
6217 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6218 if (this.get('insert') && this.browser.ie) {
6219 var r = this._getRange();
6223 this._selectNode(el);
6225 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6226 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6228 if (this.get('insert') && !this._hasSelection()) {
6229 el = this._createInsertElement({ fontSize: value });
6230 this.currentElement[0] = el;
6231 this._selectNode(this.currentElement[0]);
6233 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6234 this._selectNode(this.currentElement[0]);
6244 * @param {HTMLElement} el The element to swap with
6245 * @param {String} tagName The tagname of the element that you wish to create
6246 * @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.
6247 * @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.
6249 _swapEl: function(el, tagName, callback) {
6250 var _el = this._getDoc().createElement(tagName);
6252 _el.innerHTML = el.innerHTML;
6254 if (typeof callback == 'function') {
6255 callback.call(this, _el);
6258 el.parentNode.replaceChild(_el, el);
6264 * @method _createInsertElement
6265 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6266 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6267 * @return {HTMLElement}
6269 _createInsertElement: function(css) {
6270 this._createCurrentElement('span', css);
6271 var el = this.currentElement[0];
6272 if (this.browser.webkit) {
6273 //Little Safari Hackery here..
6274 el.innerHTML = '<span class="yui-non"> </span>';
6276 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6277 } else if (this.browser.ie || this.browser.opera) {
6278 el.innerHTML = ' ';
6281 this._selectNode(el, true);
6286 * @method _createCurrentElement
6287 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6288 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6289 * @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.
6290 * 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
6291 * <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.
6293 _createCurrentElement: function(tagName, tagStyle) {
6294 tagName = ((tagName) ? tagName : 'a');
6297 _doc = this._getDoc();
6299 if (this.currentFont) {
6303 tagStyle.fontFamily = this.currentFont;
6304 this.currentFont = null;
6306 this.currentElement = [];
6308 var _elCreate = function(tagName, tagStyle) {
6310 tagName = ((tagName) ? tagName : 'span');
6311 tagName = tagName.toLowerCase();
6319 el = _doc.createElement(tagName);
6322 el = _doc.createElement(tagName);
6323 if (tagName === 'span') {
6324 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6325 YAHOO.util.Dom.addClass(el, 'yui-tag');
6326 el.setAttribute('tag', tagName);
6329 for (var k in tagStyle) {
6330 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6331 el.style[k] = tagStyle[k];
6339 if (!this._hasSelection()) {
6340 if (this._getDoc().queryCommandEnabled('insertimage')) {
6341 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6342 var imgs = this._getDoc().getElementsByTagName('img');
6343 for (var j = 0; j < imgs.length; j++) {
6344 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6345 el = _elCreate(tagName, tagStyle);
6346 imgs[j].parentNode.replaceChild(el, imgs[j]);
6347 this.currentElement[this.currentElement.length] = el;
6351 if (this.currentEvent) {
6352 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6355 tar = this._getDoc().body;
6360 * @knownissue Safari Cursor Position
6361 * @browser Safari 2.x
6362 * @description The issue here is that we have no way of knowing where the cursor position is
6363 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6365 el = _elCreate(tagName, tagStyle);
6366 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6367 if (this._isElement(tar, 'html')) {
6368 tar = this._getDoc().body;
6370 tar.appendChild(el);
6371 } else if (tar.nextSibling) {
6372 tar.parentNode.insertBefore(el, tar.nextSibling);
6374 tar.parentNode.appendChild(el);
6376 //this.currentElement = el;
6377 this.currentElement[this.currentElement.length] = el;
6378 this.currentEvent = null;
6379 if (this.browser.webkit) {
6380 //Force Safari to focus the new element
6381 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6382 if (this.browser.webkit3) {
6383 this._getSelection().collapseToStart();
6385 this._getSelection().collapse(true);
6390 //Force CSS Styling for this action...
6391 this._setEditorStyle(true);
6392 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6393 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6395 if (!this._isElement(this._getSelectedElement(), 'body')) {
6396 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6397 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6399 for (var _els = 0; _els < __els.length; _els++) {
6400 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6401 for (var e = 0; e < _tmp1.length; e++) {
6402 _tmp[_tmp.length] = _tmp1[e];
6407 for (var i = 0; i < _tmp.length; i++) {
6408 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6409 if (tagName !== 'span') {
6410 el = _elCreate(tagName, tagStyle);
6412 el = _elCreate(_tmp[i].tagName, tagStyle);
6414 el.innerHTML = _tmp[i].innerHTML;
6415 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6416 var fc = _tmp[i].getElementsByTagName('li')[0];
6417 _tmp[i].style.fontFamily = 'inherit';
6418 fc.style.fontFamily = 'inherit';
6419 el.innerHTML = fc.innerHTML;
6422 this.currentElement[this.currentElement.length] = el;
6423 } else if (this._isElement(_tmp[i], 'li')) {
6424 _tmp[i].innerHTML = '';
6425 _tmp[i].appendChild(el);
6426 _tmp[i].style.fontFamily = 'inherit';
6427 this.currentElement[this.currentElement.length] = el;
6429 if (_tmp[i].parentNode) {
6430 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6431 this.currentElement[this.currentElement.length] = el;
6432 this.currentEvent = null;
6433 if (this.browser.webkit) {
6434 //Force Safari to focus the new element
6435 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6436 if (this.browser.webkit3) {
6437 this._getSelection().collapseToStart();
6439 this._getSelection().collapse(true);
6442 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6443 this._getSelection().empty();
6445 if (this.browser.gecko) {
6446 this._getSelection().collapseToStart();
6452 var len = this.currentElement.length;
6453 for (var o = 0; o < len; o++) {
6454 if ((o + 1) != len) { //Skip the last one in the list
6455 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6456 if (this._isElement(this.currentElement[o], 'br')) {
6457 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6466 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6469 saveHTML: function() {
6470 var html = this.cleanHTML();
6471 if (this._textarea) {
6472 this.get('element').value = html;
6474 this.get('element').innerHTML = html;
6476 if (this.get('saveEl') !== this.get('element')) {
6477 var out = this.get('saveEl');
6478 if (Lang.isString(out)) {
6482 if (out.tagName.toLowerCase() === 'textarea') {
6485 out.innerHTML = html;
6492 * @method setEditorHTML
6493 * @param {String} incomingHTML The html content to load into the editor
6494 * @description Loads HTML into the editors body
6496 setEditorHTML: function(incomingHTML) {
6497 var html = this._cleanIncomingHTML(incomingHTML);
6498 this._getDoc().body.innerHTML = html;
6502 * @method getEditorHTML
6503 * @description Gets the unprocessed/unfiltered HTML from the editor
6505 getEditorHTML: function() {
6506 var b = this._getDoc().body;
6508 YAHOO.log('Body is null, returning null.', 'error', 'SimpleEditor');
6511 return this._getDoc().body.innerHTML;
6515 * @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.
6518 if (this.browser.gecko) {
6519 this._setDesignMode('on');
6522 if (this.browser.webkit) {
6524 window.setTimeout(function() {
6525 self._setInitialContent.call(self);
6528 //Adding this will close all other Editor window's when showing this one.
6529 if (this.currentWindow) {
6532 //Put the iframe back in place
6533 this.get('iframe').setStyle('position', 'static');
6534 this.get('iframe').setStyle('left', '');
6538 * @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.
6541 //Adding this will close all other Editor window's.
6542 if (this.currentWindow) {
6545 if (this._fixNodesTimer) {
6546 clearTimeout(this._fixNodesTimer);
6547 this._fixNodesTimer = null;
6549 if (this._nodeChangeTimer) {
6550 clearTimeout(this._nodeChangeTimer);
6551 this._nodeChangeTimer = null;
6553 this._lastNodeChange = 0;
6554 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6555 this.get('iframe').setStyle('position', 'absolute');
6556 this.get('iframe').setStyle('left', '-9999px');
6559 * @method _cleanIncomingHTML
6560 * @param {String} html The unfiltered HTML
6561 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6562 * @return {String} The filtered HTML
6564 _cleanIncomingHTML: function(html) {
6565 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6566 html = html.replace(/<\/strong>/gi, '</b>');
6568 //replace embed before em check
6569 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6570 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6572 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6573 html = html.replace(/<\/em>/gi, '</i>');
6574 html = html.replace(/_moz_dirty=""/gi, '');
6576 //Put embed tags back in..
6577 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6578 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6579 if (this.get('plainText')) {
6580 YAHOO.log('Filtering as plain text', 'info', 'SimpleEditor');
6581 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6582 html = html.replace(/ /gi, ' '); //Replace all double spaces
6583 html = html.replace(/\t/gi, ' '); //Replace all tabs
6585 //Removing Script Tags from the Editor
6586 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6587 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6588 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6589 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6590 //Replace the line feeds
6591 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6593 //Remove Bad HTML elements (used to be script nodes)
6594 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6595 //Replace the lines feeds
6596 html = html.replace(/<YUI_LF>/g, '\n');
6601 * @param {String} html The unfiltered HTML
6602 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6603 * @return {String} The filtered HTML
6605 cleanHTML: function(html) {
6606 //Start Filtering Output
6609 html = this.getEditorHTML();
6611 var markup = this.get('markup');
6612 //Make some backups...
6613 html = this.pre_filter_linebreaks(html, markup);
6616 html = this.filter_msword(html);
6618 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6619 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6621 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6622 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6624 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6625 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6626 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6627 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6629 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6630 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6632 //Convert b and i tags to strong and em tags
6633 if ((markup == 'semantic') || (markup == 'xhtml')) {
6634 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6635 html = html.replace(/<\/i>/gi, '</em>');
6636 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6637 html = html.replace(/<\/b>/gi, '</strong>');
6640 html = html.replace(/_moz_dirty=""/gi, '');
6642 //normalize strikethrough
6643 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6644 html = html.replace(/\/strike>/gi, '/span>');
6648 if (this.browser.ie) {
6649 html = html.replace(/text-decoration/gi, 'text-decoration');
6650 html = html.replace(/font-weight/gi, 'font-weight');
6651 html = html.replace(/_width="([^>]*)"/gi, '');
6652 html = html.replace(/_height="([^>]*)"/gi, '');
6653 //Cleanup Image URL's
6654 var url = this._baseHREF.replace(/\//gi, '\\/'),
6655 re = new RegExp('src="' + url, 'gi');
6656 html = html.replace(re, 'src="');
6658 html = html.replace(/<font/gi, '<font');
6659 html = html.replace(/<\/font>/gi, '</font>');
6660 html = html.replace(/<span/gi, '<span');
6661 html = html.replace(/<\/span>/gi, '</span>');
6662 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6663 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6664 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6665 if (this.browser.webkit) {
6666 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6667 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6669 html = html.replace(/\/u>/gi, '/span>');
6670 if (markup == 'css') {
6671 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6672 html = html.replace(/<\/em>/gi, '</i>');
6673 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6674 html = html.replace(/<\/strong>/gi, '</b>');
6675 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6676 html = html.replace(/\/b>/gi, '/span>');
6677 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6678 html = html.replace(/\/i>/gi, '/span>');
6680 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6682 html = html.replace(/<u/gi, '<u');
6683 html = html.replace(/\/u>/gi, '/u>');
6685 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6686 html = html.replace(/\/ol>/gi, '/ol>');
6687 html = html.replace(/<li/gi, '<li');
6688 html = html.replace(/\/li>/gi, '/li>');
6689 html = this.filter_safari(html);
6691 html = this.filter_internals(html);
6693 html = this.filter_all_rgb(html);
6695 //Replace our backups with the real thing
6696 html = this.post_filter_linebreaks(html, markup);
6698 if (markup == 'xhtml') {
6699 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6700 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6702 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6703 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6705 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6706 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6708 html = this.filter_invalid_lists(html);
6710 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6711 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6713 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6714 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6716 //This should fix &s in URL's
6717 html = html.replace(/ & /gi, 'YUI_AMP');
6718 html = html.replace(/&/gi, '&');
6719 html = html.replace(/YUI_AMP/gi, ' & ');
6721 //Trim the output, removing whitespace from the beginning and end
6722 html = YAHOO.lang.trim(html);
6724 if (this.get('removeLineBreaks')) {
6725 html = html.replace(/\n/g, '').replace(/\r/g, '');
6726 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6730 if (html.substring(0, 6).toLowerCase() == '<span>') {
6731 html = html.substring(6);
6733 if (html.substring(html.length - 7, html.length).toLowerCase() == '</span>') {
6734 html = html.substring(0, html.length - 7);
6738 for (var v in this.invalidHTML) {
6739 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6740 if (Lang.isObject(v) && v.keepContents) {
6741 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6743 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6748 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6753 * @method filter_msword
6754 * @param String html The HTML string to filter
6755 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6757 filter_msword: function(html) {
6758 if (!this.get('filterWord')) {
6761 //Remove the ms o: tags
6762 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6763 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6765 //Remove the ms w: tags
6766 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6768 //Remove mso-? styles.
6769 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6771 //Remove more bogus MS styles.
6772 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6773 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6774 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6775 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6776 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6777 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6778 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6779 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6781 //Remove XML declarations
6782 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6785 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6787 //Remove language tags
6788 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6790 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6791 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6792 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
6797 * @method filter_invalid_lists
6798 * @param String html The HTML string to filter
6799 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6801 filter_invalid_lists: function(html) {
6802 html = html.replace(/<\/li>\n/gi, '</li>');
6804 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6805 html = html.replace(/<\/ol>/gi, '</ol></li>');
6806 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
6808 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6809 html = html.replace(/<\/ul>/gi, '</ul></li>');
6810 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
6812 html = html.replace(/<\/li>/gi, "</li>");
6813 html = html.replace(/<\/ol>/gi, "</ol>");
6814 html = html.replace(/<ol>/gi, "<ol>");
6815 html = html.replace(/<ul>/gi, "<ul>");
6819 * @method filter_safari
6820 * @param String html The HTML string to filter
6821 * @description Filters strings specific to Safari
6824 filter_safari: function(html) {
6825 if (this.browser.webkit) {
6826 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6827 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
6828 html = html.replace(/Apple-style-span/gi, '');
6829 html = html.replace(/style="line-height: normal;"/gi, '');
6830 html = html.replace(/yui-wk-div/gi, '');
6831 html = html.replace(/yui-wk-p/gi, '');
6835 html = html.replace(/<li><\/li>/gi, '');
6836 html = html.replace(/<li> <\/li>/gi, '');
6837 html = html.replace(/<li> <\/li>/gi, '');
6838 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6839 if (this.get('ptags')) {
6840 html = html.replace(/<div([^>]*)>/g, '<p$1>');
6841 html = html.replace(/<\/div>/gi, '</p>');
6843 html = html.replace(/<div>/gi, '<br>');
6844 html = html.replace(/<\/div>/gi, '');
6850 * @method filter_internals
6851 * @param String html The HTML string to filter
6852 * @description Filters internal RTE strings and bogus attrs we don't want
6855 filter_internals: function(html) {
6856 html = html.replace(/\r/g, '');
6857 //Fix stuff we don't want
6858 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6860 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6862 html = html.replace(/yui-tag-span/gi, '');
6863 html = html.replace(/yui-tag/gi, '');
6864 html = html.replace(/yui-non/gi, '');
6865 html = html.replace(/yui-img/gi, '');
6866 html = html.replace(/ tag="span"/gi, '');
6867 html = html.replace(/ class=""/gi, '');
6868 html = html.replace(/ style=""/gi, '');
6869 html = html.replace(/ class=" "/gi, '');
6870 html = html.replace(/ class=" "/gi, '');
6871 html = html.replace(/ target=""/gi, '');
6872 html = html.replace(/ title=""/gi, '');
6874 if (this.browser.ie) {
6875 html = html.replace(/ class= /gi, '');
6876 html = html.replace(/ class= >/gi, '');
6882 * @method filter_all_rgb
6883 * @param String str The HTML string to filter
6884 * @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"
6887 filter_all_rgb: function(str) {
6888 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
6889 var arr = str.match(exp);
6890 if (Lang.isArray(arr)) {
6891 for (var i = 0; i < arr.length; i++) {
6892 var color = this.filter_rgb(arr[i]);
6893 str = str.replace(arr[i].toString(), color);
6900 * @method filter_rgb
6901 * @param String css The CSS string containing rgb(#,#,#);
6902 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
6905 filter_rgb: function(css) {
6906 if (css.toLowerCase().indexOf('rgb') != -1) {
6907 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
6908 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
6910 if (rgb.length == 5) {
6911 var r = parseInt(rgb[1], 10).toString(16);
6912 var g = parseInt(rgb[2], 10).toString(16);
6913 var b = parseInt(rgb[3], 10).toString(16);
6915 r = r.length == 1 ? '0' + r : r;
6916 g = g.length == 1 ? '0' + g : g;
6917 b = b.length == 1 ? '0' + b : b;
6919 css = "#" + r + g + b;
6925 * @method pre_filter_linebreaks
6926 * @param String html The HTML to filter
6927 * @param String markup The markup type to filter to
6928 * @description HTML Pre Filter
6931 pre_filter_linebreaks: function(html, markup) {
6932 if (this.browser.webkit) {
6933 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
6934 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
6936 html = html.replace(/<br>/gi, '<YUI_BR>');
6937 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
6938 html = html.replace(/<br\/>/gi, '<YUI_BR>');
6939 html = html.replace(/<br \/>/gi, '<YUI_BR>');
6940 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
6941 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
6942 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
6943 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
6945 html = html.replace(/<YUI_BR>$/, '');
6947 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
6948 if (this.browser.ie) {
6949 html = html.replace(/ /g, '\t');
6954 * @method post_filter_linebreaks
6955 * @param String html The HTML to filter
6956 * @param String markup The markup type to filter to
6957 * @description HTML Pre Filter
6960 post_filter_linebreaks: function(html, markup) {
6961 if (markup == 'xhtml') {
6962 html = html.replace(/<YUI_BR>/g, '<br />');
6964 html = html.replace(/<YUI_BR>/g, '<br>');
6969 * @method clearEditorDoc
6970 * @description Clear the doc of the Editor
6972 clearEditorDoc: function() {
6973 this._getDoc().body.innerHTML = ' ';
6976 * @method openWindow
6977 * @description Override Method for Advanced Editor
6979 openWindow: function(win) {
6982 * @method moveWindow
6983 * @description Override Method for Advanced Editor
6985 moveWindow: function() {
6989 * @method _closeWindow
6990 * @description Override Method for Advanced Editor
6992 _closeWindow: function() {
6995 * @method closeWindow
6996 * @description Override Method for Advanced Editor
6998 closeWindow: function() {
6999 //this.unsubscribeAll('afterExecCommand');
7000 this.toolbar.resetAllButtons();
7005 * @description Destroys the editor, all of it's elements and objects.
7008 destroy: function() {
7009 YAHOO.log('Destroying Editor', 'warn', 'SimpleEditor');
7011 YAHOO.log('Destroying Resize', 'warn', 'SimpleEditor');
7012 this.resize.destroy();
7015 YAHOO.log('Unreg DragDrop Instance', 'warn', 'SimpleEditor');
7018 if (this.get('panel')) {
7019 YAHOO.log('Destroying Editor Panel', 'warn', 'SimpleEditor');
7020 this.get('panel').destroy();
7023 this.toolbar.destroy();
7024 YAHOO.log('Restoring TextArea', 'info', 'SimpleEditor');
7025 this.setStyle('visibility', 'visible');
7026 this.setStyle('position', 'static');
7027 this.setStyle('top', '');
7028 this.setStyle('left', '');
7029 var textArea = this.get('element');
7030 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7031 this.get('element_cont').get('element').innerHTML = '';
7032 this.set('handleSubmit', false); //Remove the submit handler
7037 * @description Returns a string representing the editor.
7040 toString: function() {
7041 var str = 'SimpleEditor';
7042 if (this.get && this.get('element_cont')) {
7043 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7050 * @event toolbarLoaded
7051 * @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.
7052 * @type YAHOO.util.CustomEvent
7056 * @description Event is fired after the cleanHTML method is called.
7057 * @type YAHOO.util.CustomEvent
7060 * @event afterRender
7061 * @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.
7062 * @type YAHOO.util.CustomEvent
7065 * @event editorContentLoaded
7066 * @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.
7067 * @type YAHOO.util.CustomEvent
7070 * @event beforeNodeChange
7071 * @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.
7072 * @type YAHOO.util.CustomEvent
7075 * @event afterNodeChange
7076 * @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.
7077 * @type YAHOO.util.CustomEvent
7080 * @event beforeExecCommand
7081 * @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.
7082 * @type YAHOO.util.CustomEvent
7085 * @event afterExecCommand
7086 * @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.
7087 * @type YAHOO.util.CustomEvent
7090 * @event editorMouseUp
7091 * @param {Event} ev The DOM Event that occured
7092 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7093 * @type YAHOO.util.CustomEvent
7096 * @event editorMouseDown
7097 * @param {Event} ev The DOM Event that occured
7098 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7099 * @type YAHOO.util.CustomEvent
7102 * @event editorDoubleClick
7103 * @param {Event} ev The DOM Event that occured
7104 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7105 * @type YAHOO.util.CustomEvent
7108 * @event editorClick
7109 * @param {Event} ev The DOM Event that occured
7110 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7111 * @type YAHOO.util.CustomEvent
7114 * @event editorKeyUp
7115 * @param {Event} ev The DOM Event that occured
7116 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7117 * @type YAHOO.util.CustomEvent
7120 * @event editorKeyPress
7121 * @param {Event} ev The DOM Event that occured
7122 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7123 * @type YAHOO.util.CustomEvent
7126 * @event editorKeyDown
7127 * @param {Event} ev The DOM Event that occured
7128 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7129 * @type YAHOO.util.CustomEvent
7132 * @event beforeEditorMouseUp
7133 * @param {Event} ev The DOM Event that occured
7134 * @description Fires before editor event, returning false will stop the internal processing.
7135 * @type YAHOO.util.CustomEvent
7138 * @event beforeEditorMouseDown
7139 * @param {Event} ev The DOM Event that occured
7140 * @description Fires before editor event, returning false will stop the internal processing.
7141 * @type YAHOO.util.CustomEvent
7144 * @event beforeEditorDoubleClick
7145 * @param {Event} ev The DOM Event that occured
7146 * @description Fires before editor event, returning false will stop the internal processing.
7147 * @type YAHOO.util.CustomEvent
7150 * @event beforeEditorClick
7151 * @param {Event} ev The DOM Event that occured
7152 * @description Fires before editor event, returning false will stop the internal processing.
7153 * @type YAHOO.util.CustomEvent
7156 * @event beforeEditorKeyUp
7157 * @param {Event} ev The DOM Event that occured
7158 * @description Fires before editor event, returning false will stop the internal processing.
7159 * @type YAHOO.util.CustomEvent
7162 * @event beforeEditorKeyPress
7163 * @param {Event} ev The DOM Event that occured
7164 * @description Fires before editor event, returning false will stop the internal processing.
7165 * @type YAHOO.util.CustomEvent
7168 * @event beforeEditorKeyDown
7169 * @param {Event} ev The DOM Event that occured
7170 * @description Fires before editor event, returning false will stop the internal processing.
7171 * @type YAHOO.util.CustomEvent
7175 * @event editorWindowFocus
7176 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7177 * @type YAHOO.util.CustomEvent
7180 * @event editorWindowBlur
7181 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7182 * @type YAHOO.util.CustomEvent
7187 * @description Singleton object used to track the open window objects and panels across the various open editors
7191 YAHOO.widget.EditorInfo = {
7194 * @property _instances
7195 * @description A reference to all editors on the page.
7201 * @property blankImage
7202 * @description A reference to the blankImage url
7209 * @description A reference to the currently open window object in any editor on the page.
7210 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7216 * @description A reference to the currently open panel in any editor on the page.
7217 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7221 * @method getEditorById
7222 * @description Returns a reference to the Editor object associated with the given textarea
7223 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7224 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7226 getEditorById: function(id) {
7227 if (!YAHOO.lang.isString(id)) {
7228 //Not a string, assume a node Reference
7231 if (this._instances[id]) {
7232 return this._instances[id];
7238 * @description Returns a string representing the EditorInfo.
7241 toString: function() {
7243 for (var i in this._instances) {
7244 if (Lang.hasOwnProperty(this._instances, i)) {
7248 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7256 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.7.0", build: "1799"});