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 el.id = this.get('id');
196 var menu = this.get('menu');
197 for (var i = 0; i < menu.length; i++) {
198 opt = document.createElement('option');
199 opt.innerHTML = menu[i].text;
200 opt.value = menu[i].value;
201 if (menu[i].checked) {
206 this._button.parentNode.replaceChild(el, this._button);
207 Event.on(el, 'change', this._handleSelect, this, true);
215 * @attribute disabled
216 * @description Set the button into a disabled state
219 this.setAttributeConfig('disabled', {
220 value: attr.disabled || false,
221 method: function(disabled) {
223 this.addClass('yui-button-disabled');
224 this.addClass('yui-' + this.get('type') + '-button-disabled');
226 this.removeClass('yui-button-disabled');
227 this.removeClass('yui-' + this.get('type') + '-button-disabled');
229 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) {
230 this._button.disabled = disabled;
237 * @description The text label for the button
240 this.setAttributeConfig('label', {
242 method: function(label) {
244 this._button = this.get('element').getElementsByTagName('a')[0];
246 if (this.get('type') == 'push') {
247 this._button.innerHTML = label;
254 * @description The title of the button
257 this.setAttributeConfig('title', {
263 * @description The container that the button is rendered to, handled by Toolbar
266 this.setAttributeConfig('container', {
269 method: function(cont) {
277 * @method _handleSelect
278 * @description The event fired when a change event gets fired on a select element
279 * @param {Event} ev The change event.
281 _handleSelect: function(ev) {
282 var tar = Event.getTarget(ev);
283 var value = tar.options[tar.selectedIndex].value;
284 this.fireEvent('change', {type: 'change', value: value });
288 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
290 getMenu: function() {
291 return this.get('menu');
295 * @description Destroy the button
297 destroy: function() {
298 Event.purgeElement(this.get('element'), true);
299 this.get('element').parentNode.removeChild(this.get('element'));
300 //Brutal Object Destroy
301 for (var i in this) {
302 if (Lang.hasOwnProperty(this, i)) {
309 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
311 fireEvent: function(p_sType, p_aArgs) {
312 // Disabled buttons should not respond to DOM events
313 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
314 Event.stopEvent(p_aArgs);
318 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
322 * @description Returns a string representing the toolbar.
325 toString: function() {
326 return 'ToolbarButton (' + this.get('id') + ')';
333 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
334 * @namespace YAHOO.widget
335 * @requires yahoo, dom, element, event, toolbarbutton
336 * @optional container_core, dragdrop
339 var Dom = YAHOO.util.Dom,
340 Event = YAHOO.util.Event,
343 var getButton = function(id) {
345 if (Lang.isString(id)) {
346 button = this.getButtonById(id);
348 if (Lang.isNumber(id)) {
349 button = this.getButtonByIndex(id);
351 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
352 button = this.getButtonByValue(id);
354 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
361 * Provides a rich toolbar widget based on the button and menu widgets
364 * @extends YAHOO.util.Element
365 * @param {String/HTMLElement} el The element to turn into a toolbar.
366 * @param {Object} attrs Object liternal containing configuration parameters.
368 YAHOO.widget.Toolbar = function(el, attrs) {
369 YAHOO.log('Toolbar Initalizing', 'info', 'Toolbar');
370 YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
372 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
375 var local_attrs = {};
377 Lang.augmentObject(local_attrs, attrs); //Break the config reference
383 attributes: local_attrs
387 if (Lang.isString(el) && Dom.get(el)) {
388 oConfig.element = Dom.get(el);
389 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
390 oConfig.element = Dom.get(el);
394 if (!oConfig.element) {
395 YAHOO.log('No element defined, creating toolbar container', 'warn', 'Toolbar');
396 oConfig.element = document.createElement('DIV');
397 oConfig.element.id = Dom.generateId();
399 if (local_attrs.container && Dom.get(local_attrs.container)) {
400 YAHOO.log('Container found in config appending to it (' + Dom.get(local_attrs.container).id + ')', 'info', 'Toolbar');
401 Dom.get(local_attrs.container).appendChild(oConfig.element);
406 if (!oConfig.element.id) {
407 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
408 YAHOO.log('No element ID defined for toolbar container, creating..', 'warn', 'Toolbar');
410 YAHOO.log('Initing toolbar with id: ' + oConfig.element.id, 'info', 'Toolbar');
412 var fs = document.createElement('fieldset');
413 var lg = document.createElement('legend');
414 lg.innerHTML = 'Toolbar';
417 var cont = document.createElement('DIV');
418 oConfig.attributes.cont = cont;
419 Dom.addClass(cont, 'yui-toolbar-subcont');
420 fs.appendChild(cont);
421 oConfig.element.appendChild(fs);
423 oConfig.element.tabIndex = -1;
426 oConfig.attributes.element = oConfig.element;
427 oConfig.attributes.id = oConfig.element.id;
429 this._configuredButtons = [];
431 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
435 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
438 * @property _configuredButtons
441 _configuredButtons: null,
443 * @method _addMenuClasses
445 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
446 * @param {String} ev The event that fired.
447 * @param {Array} na Array of event information.
448 * @param {Object} o Button config object.
450 _addMenuClasses: function(ev, na, o) {
451 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
452 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
453 Dom.addClass(this.element, 'yui-toolbar-select-menu');
455 var items = this.getItems();
456 for (var i = 0; i < items.length; i++) {
457 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-').toLowerCase() : items[i]._oText.nodeValue.replace(/ /g, '-').toLowerCase()));
458 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
462 * @property buttonType
463 * @description The default button to use
466 buttonType: YAHOO.widget.ToolbarButton,
469 * @description The DragDrop instance associated with the Toolbar
474 * @property _colorData
475 * @description Object reference containing colors hex and text values.
480 '#111111': 'Obsidian',
481 '#2D2D2D': 'Dark Gray',
485 '#8B8B8B': 'Concrete',
487 '#B9B9B9': 'Titanium',
489 '#D0D0D0': 'Light Gray',
492 '#BFBF00': 'Pumpkin',
495 '#FFFF80': 'Pale Yellow',
497 '#525330': 'Raw Siena',
500 '#7F7F00': 'Paprika',
505 '#80FF00': 'Chartreuse',
507 '#C0FF80': 'Pale Lime',
508 '#DFFFBF': 'Light Mint',
510 '#668F5A': 'Lime Gray',
513 '#8A9B55': 'Pistachio',
514 '#B7C296': 'Light Jade',
515 '#E6EBD5': 'Breakwater',
516 '#00BF00': 'Spring Frost',
517 '#00FF80': 'Pastel Green',
518 '#40FFA0': 'Light Emerald',
519 '#80FFC0': 'Sea Foam',
520 '#BFFFDF': 'Sea Mist',
521 '#033D21': 'Dark Forrest',
523 '#7FA37C': 'Medium Green',
525 '#8DAE94': 'Yellow Gray Green',
526 '#ACC6B5': 'Aqua Lung',
527 '#DDEBE2': 'Sea Vapor',
530 '#40FFFF': 'Turquoise Blue',
531 '#80FFFF': 'Light Aqua',
532 '#BFFFFF': 'Pale Cyan',
533 '#033D3D': 'Dark Teal',
534 '#347D7E': 'Gray Turquoise',
535 '#609A9F': 'Green Blue',
536 '#007F7F': 'Seaweed',
537 '#96BDC4': 'Green Gray',
538 '#B5D1D7': 'Soapstone',
539 '#E2F1F4': 'Light Turquoise',
540 '#0060BF': 'Summer Sky',
541 '#0080FF': 'Sky Blue',
542 '#40A0FF': 'Electric Blue',
543 '#80C0FF': 'Light Azure',
544 '#BFDFFF': 'Ice Blue',
547 '#57708F': 'Dusty Blue',
548 '#00407F': 'Sea Blue',
549 '#7792AC': 'Sky Blue Gray',
550 '#A8BED1': 'Morning Sky',
552 '#0000BF': 'Deep Blue',
554 '#4040FF': 'Cerulean Blue',
555 '#8080FF': 'Evening Blue',
556 '#BFBFFF': 'Light Blue',
557 '#212143': 'Deep Indigo',
558 '#373E68': 'Sea Blue',
559 '#444F75': 'Night Blue',
560 '#00007F': 'Indigo Blue',
561 '#585E82': 'Dockside',
562 '#8687A4': 'Blue Gray',
563 '#D2D1E1': 'Light Blue Gray',
564 '#6000BF': 'Neon Violet',
565 '#8000FF': 'Blue Violet',
566 '#A040FF': 'Violet Purple',
567 '#C080FF': 'Violet Dusk',
568 '#DFBFFF': 'Pale Lavender',
569 '#302449': 'Cool Shale',
570 '#54466F': 'Dark Indigo',
571 '#655A7F': 'Dark Violet',
573 '#726284': 'Smoky Violet',
574 '#9E8FA9': 'Slate Gray',
575 '#DCD1DF': 'Violet White',
576 '#BF00BF': 'Royal Violet',
577 '#FF00FF': 'Fuchsia',
578 '#FF40FF': 'Magenta',
580 '#FFBFFF': 'Pale Magenta',
581 '#4A234A': 'Dark Purple',
582 '#794A72': 'Medium Purple',
583 '#936386': 'Cool Granite',
585 '#9D7292': 'Purple Moon',
586 '#C0A0B6': 'Pale Purple',
587 '#ECDAE5': 'Pink Cloud',
588 '#BF005F': 'Hot Pink',
589 '#FF007F': 'Deep Pink',
591 '#FF80BF': 'Electric Pink',
593 '#451528': 'Purple Red',
594 '#823857': 'Purple Dino',
595 '#A94A76': 'Purple Gray',
597 '#BC6F95': 'Antique Mauve',
598 '#D8A5BB': 'Cool Marble',
599 '#F7DDE9': 'Pink Granite',
601 '#FF0000': 'Fire Truck',
602 '#FF4040': 'Pale Red',
604 '#FFC0C0': 'Warm Pink',
608 '#800000': 'Brick Red',
610 '#D8A3A4': 'Shrimp Pink',
611 '#F8DDDD': 'Shell Pink',
612 '#BF5F00': 'Dark Orange',
614 '#FF9F40': 'Grapefruit',
615 '#FFBF80': 'Canteloupe',
617 '#482C1B': 'Dark Brick',
621 '#C49B71': 'Mustard',
622 '#E1C4A8': 'Pale Tan',
627 * @property _colorPicker
628 * @description The HTML Element containing the colorPicker
633 * @property STR_COLLAPSE
634 * @description String for Toolbar Collapse Button
637 STR_COLLAPSE: 'Collapse Toolbar',
639 * @property STR_EXPAND
640 * @description String for Toolbar Collapse Button - Expand
643 STR_EXPAND: 'Expand Toolbar',
645 * @property STR_SPIN_LABEL
646 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
649 STR_SPIN_LABEL: 'Spin Button with value {VALUE}. Use Control Shift Up Arrow and Control Shift Down arrow keys to increase or decrease the value.',
651 * @property STR_SPIN_UP
652 * @description String for spinbutton up
655 STR_SPIN_UP: 'Click to increase the value of this input',
657 * @property STR_SPIN_DOWN
658 * @description String for spinbutton down
661 STR_SPIN_DOWN: 'Click to decrease the value of this input',
663 * @property _titlebar
664 * @description Object reference to the titlebar
670 * @description Standard browser detection
673 browser: YAHOO.env.ua,
676 * @property _buttonList
677 * @description Internal property list of current buttons in the toolbar
683 * @property _buttonGroupList
684 * @description Internal property list of current button groups in the toolbar
687 _buttonGroupList: null,
691 * @description Internal reference to the separator HTML Element for cloning
697 * @property _sepCount
698 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
704 * @property draghandle
710 * @property _toolbarConfigs
718 * @property CLASS_CONTAINER
719 * @description Default CSS class to apply to the toolbar container element
722 CLASS_CONTAINER: 'yui-toolbar-container',
725 * @property CLASS_DRAGHANDLE
726 * @description Default CSS class to apply to the toolbar's drag handle element
729 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
732 * @property CLASS_SEPARATOR
733 * @description Default CSS class to apply to all separators in the toolbar
736 CLASS_SEPARATOR: 'yui-toolbar-separator',
739 * @property CLASS_DISABLED
740 * @description Default CSS class to apply when the toolbar is disabled
743 CLASS_DISABLED: 'yui-toolbar-disabled',
746 * @property CLASS_PREFIX
747 * @description Default prefix for dynamically created class names
750 CLASS_PREFIX: 'yui-toolbar',
753 * @description The Toolbar class's initialization method
755 init: function(p_oElement, p_oAttributes) {
756 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
759 * @method initAttributes
760 * @description Initializes all of the configuration attributes used to create
762 * @param {Object} attr Object literal specifying a set of
763 * configuration attributes used to create the toolbar.
765 initAttributes: function(attr) {
766 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
767 this.addClass(this.CLASS_CONTAINER);
770 * @attribute buttonType
771 * @description The buttonType to use (advanced or basic)
774 this.setAttributeConfig('buttonType', {
775 value: attr.buttonType || 'basic',
777 validator: function(type) {
785 method: function(type) {
786 if (type == 'advanced') {
787 if (YAHOO.widget.Button) {
788 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
790 YAHOO.log('Can not find YAHOO.widget.Button', 'error', 'Toolbar');
791 this.buttonType = YAHOO.widget.ToolbarButton;
794 this.buttonType = YAHOO.widget.ToolbarButton;
802 * @description Object specifying the buttons to include in the toolbar
806 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
807 * { type: 'separator' },
808 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
810 * { text: "Left", value: 'alignleft' },
811 * { text: "Center", value: 'aligncenter' },
812 * { text: "Right", value: 'alignright' }
820 this.setAttributeConfig('buttons', {
823 method: function(data) {
824 var i, button, buttons, len, b;
826 if (Lang.hasOwnProperty(data, i)) {
827 if (data[i].type == 'separator') {
829 } else if (data[i].group !== undefined) {
830 buttons = this.addButtonGroup(data[i]);
832 len = buttons.length;
833 for(b = 0; b < len; b++) {
835 this._configuredButtons[this._configuredButtons.length] = buttons[b].id;
841 button = this.addButton(data[i]);
843 this._configuredButtons[this._configuredButtons.length] = button.id;
852 * @attribute disabled
853 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
857 this.setAttributeConfig('disabled', {
859 method: function(disabled) {
860 if (this.get('disabled') === disabled) {
864 this.addClass(this.CLASS_DISABLED);
865 this.set('draggable', false);
866 this.disableAllButtons();
868 this.removeClass(this.CLASS_DISABLED);
869 if (this._configs.draggable._initialConfig.value) {
870 //Draggable by default, set it back
871 this.set('draggable', true);
873 this.resetAllButtons();
880 * @description The container for the toolbar.
883 this.setAttributeConfig('cont', {
890 * @attribute grouplabels
891 * @description Boolean indicating if the toolbar should show the group label's text string.
895 this.setAttributeConfig('grouplabels', {
896 value: ((attr.grouplabels === false) ? false : true),
897 method: function(grouplabels) {
899 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
901 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
906 * @attribute titlebar
907 * @description Boolean indicating if the toolbar should have a titlebar. If
908 * passed a string, it will use that as the titlebar text
910 * @type Boolean or String
912 this.setAttributeConfig('titlebar', {
914 method: function(titlebar) {
916 if (this._titlebar && this._titlebar.parentNode) {
917 this._titlebar.parentNode.removeChild(this._titlebar);
919 this._titlebar = document.createElement('DIV');
920 this._titlebar.tabIndex = '-1';
921 Event.on(this._titlebar, 'focus', function() {
924 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
925 if (Lang.isString(titlebar)) {
926 var h2 = document.createElement('h2');
928 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
929 this._titlebar.appendChild(h2);
930 Event.on(h2.firstChild, 'click', function(ev) {
933 Event.on([h2, h2.firstChild], 'focus', function() {
937 if (this.get('firstChild')) {
938 this.insertBefore(this._titlebar, this.get('firstChild'));
940 this.appendChild(this._titlebar);
942 if (this.get('collapse')) {
943 this.set('collapse', true);
945 } else if (this._titlebar) {
946 if (this._titlebar && this._titlebar.parentNode) {
947 this._titlebar.parentNode.removeChild(this._titlebar);
955 * @attribute collapse
956 * @description Boolean indicating if the titlebar should have a collapse button.
957 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
961 this.setAttributeConfig('collapse', {
963 method: function(collapse) {
964 if (this._titlebar) {
965 var collapseEl = null;
966 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
969 //There is already a collapse button
972 collapseEl = document.createElement('SPAN');
973 collapseEl.innerHTML = 'X';
974 collapseEl.title = this.STR_COLLAPSE;
976 Dom.addClass(collapseEl, 'collapse');
977 this._titlebar.appendChild(collapseEl);
978 Event.addListener(collapseEl, 'click', function() {
979 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
980 this.collapse(false); //Expand Toolbar
982 this.collapse(); //Collapse Toolbar
986 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
988 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
989 //We are closed, reopen the titlebar..
990 this.collapse(false); //Expand Toolbar
992 collapseEl[0].parentNode.removeChild(collapseEl[0]);
1000 * @attribute draggable
1001 * @description Boolean indicating if the toolbar should be draggable.
1006 this.setAttributeConfig('draggable', {
1007 value: (attr.draggable || false),
1008 method: function(draggable) {
1009 if (draggable && !this.get('titlebar')) {
1010 YAHOO.log('Dragging enabled', 'info', 'Toolbar');
1011 if (!this._dragHandle) {
1012 this._dragHandle = document.createElement('SPAN');
1013 this._dragHandle.innerHTML = '|';
1014 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
1015 this._dragHandle.id = this.get('id') + '_draghandle';
1016 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
1017 if (this.get('cont').hasChildNodes()) {
1018 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
1020 this.get('cont').appendChild(this._dragHandle);
1022 this.dd = new YAHOO.util.DD(this.get('id'));
1023 this.dd.setHandleElId(this._dragHandle.id);
1027 YAHOO.log('Dragging disabled', 'info', 'Toolbar');
1028 if (this._dragHandle) {
1029 this._dragHandle.parentNode.removeChild(this._dragHandle);
1030 this._dragHandle = null;
1034 if (this._titlebar) {
1036 this.dd = new YAHOO.util.DD(this.get('id'));
1037 this.dd.setHandleElId(this._titlebar);
1038 Dom.addClass(this._titlebar, 'draggable');
1040 Dom.removeClass(this._titlebar, 'draggable');
1048 validator: function(value) {
1050 if (!YAHOO.util.DD) {
1059 * @method addButtonGroup
1060 * @description Add a new button group to the toolbar. (uses addButton)
1061 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1063 addButtonGroup: function(oGroup) {
1064 if (!this.get('element')) {
1065 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1069 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1070 this.addClass(this.CLASS_PREFIX + '-grouped');
1072 var div = document.createElement('DIV');
1073 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1074 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1076 var label = document.createElement('h3');
1077 label.innerHTML = oGroup.label;
1078 div.appendChild(label);
1080 if (!this.get('grouplabels')) {
1081 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1084 this.get('cont').appendChild(div);
1086 //For accessibility, let's put all of the group buttons in an Unordered List
1087 var ul = document.createElement('ul');
1088 div.appendChild(ul);
1090 if (!this._buttonGroupList) {
1091 this._buttonGroupList = {};
1094 this._buttonGroupList[oGroup.group] = ul;
1096 //An array of the button ids added to this group
1097 //This is used for destruction later...
1098 var addedButtons = [],
1102 for (var i = 0; i < oGroup.buttons.length; i++) {
1103 var li = document.createElement('li');
1104 li.className = this.CLASS_PREFIX + '-groupitem';
1106 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1107 this.addSeparator(li);
1109 oGroup.buttons[i].container = li;
1110 button = this.addButton(oGroup.buttons[i]);
1112 addedButtons[addedButtons.length] = button.id;
1116 return addedButtons;
1119 * @method addButtonToGroup
1120 * @description Add a new button to a toolbar group. Buttons supported:
1121 * push, split, menu, select, color, spin
1122 * @param {Object} oButton Object literal reference to the Button's Config
1123 * @param {String} group The Group identifier passed into the initial config
1124 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1126 addButtonToGroup: function(oButton, group, after) {
1127 var groupCont = this._buttonGroupList[group],
1128 li = document.createElement('li');
1130 li.className = this.CLASS_PREFIX + '-groupitem';
1131 oButton.container = li;
1132 this.addButton(oButton, after);
1133 groupCont.appendChild(li);
1137 * @description Add a new button to the toolbar. Buttons supported:
1138 * push, split, menu, select, color, spin
1139 * @param {Object} oButton Object literal reference to the Button's Config
1140 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1142 addButton: function(oButton, after) {
1143 if (!this.get('element')) {
1144 this._queue[this._queue.length] = ['addButton', arguments];
1147 if (!this._buttonList) {
1148 this._buttonList = [];
1150 YAHOO.log('Adding button of type: ' + oButton.type, 'info', 'Toolbar');
1151 if (!oButton.container) {
1152 oButton.container = this.get('cont');
1155 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1156 if (Lang.isArray(oButton.menu)) {
1157 for (var i in oButton.menu) {
1158 if (Lang.hasOwnProperty(oButton.menu, i)) {
1160 fn: function(ev, x, oMenu) {
1161 if (!oButton.menucmd) {
1162 oButton.menucmd = oButton.value;
1164 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1168 oButton.menu[i].onclick = funcObject;
1173 var _oButton = {}, skip = false;
1174 for (var o in oButton) {
1175 if (Lang.hasOwnProperty(oButton, o)) {
1176 if (!this._toolbarConfigs[o]) {
1177 _oButton[o] = oButton[o];
1181 if (oButton.type == 'select') {
1182 _oButton.type = 'menu';
1184 if (oButton.type == 'spin') {
1185 _oButton.type = 'push';
1187 if (_oButton.type == 'color') {
1188 if (YAHOO.widget.Overlay) {
1189 _oButton = this._makeColorButton(_oButton);
1194 if (_oButton.menu) {
1195 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1196 oButton.menu.showEvent.subscribe(function() {
1197 this._button = _oButton;
1200 for (var m = 0; m < _oButton.menu.length; m++) {
1201 if (!_oButton.menu[m].value) {
1202 _oButton.menu[m].value = _oButton.menu[m].text;
1205 if (this.browser.webkit) {
1206 _oButton.focusmenu = false;
1213 //Add to .get('buttons') manually
1214 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1216 var tmp = new this.buttonType(_oButton);
1217 tmp.get('element').tabIndex = '-1';
1218 tmp.get('element').setAttribute('role', 'button');
1219 tmp._selected = true;
1221 if (this.get('disabled')) {
1222 //Toolbar is disabled, disable the new button too!
1223 tmp.set('disabled', true);
1226 oButton.id = tmp.get('id');
1228 YAHOO.log('Button created (' + oButton.type + ')', 'info', 'Toolbar');
1231 var el = tmp.get('element');
1234 nextSib = after.get('element').nextSibling;
1235 } else if (after.nextSibling) {
1236 nextSib = after.nextSibling;
1239 nextSib.parentNode.insertBefore(el, nextSib);
1242 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1244 var icon = document.createElement('span');
1245 icon.className = this.CLASS_PREFIX + '-icon';
1246 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1247 if (tmp._button.tagName.toLowerCase() == 'button') {
1248 tmp.get('element').setAttribute('unselectable', 'on');
1249 //Replace the Button HTML Element with an a href if it exists
1250 var a = document.createElement('a');
1251 a.innerHTML = tmp._button.innerHTML;
1254 Event.on(a, 'click', function(ev) {
1255 Event.stopEvent(ev);
1257 tmp._button.parentNode.replaceChild(a, tmp._button);
1261 if (oButton.type == 'select') {
1262 if (tmp._button.tagName.toLowerCase() == 'select') {
1263 icon.parentNode.removeChild(icon);
1264 var iel = tmp._button,
1265 parEl = tmp.get('element');
1266 parEl.parentNode.replaceChild(iel, parEl);
1267 //The 'element' value is currently the orphaned element
1268 //In order for "destroy" to execute we need to get('element') to reference the correct node.
1269 //I'm not sure if there is a direct approach to setting this value.
1270 tmp._configs.element.value = iel;
1272 //Don't put a class on it if it's a real select element
1273 tmp.addClass(this.CLASS_PREFIX + '-select');
1276 if (oButton.type == 'spin') {
1277 if (!Lang.isArray(oButton.range)) {
1278 oButton.range = [ 10, 100 ];
1280 this._makeSpinButton(tmp, oButton);
1282 tmp.get('element').setAttribute('title', tmp.get('label'));
1283 if (oButton.type != 'spin') {
1284 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1285 var showPicker = function(ev) {
1287 if (ev.keyCode && (ev.keyCode == 9)) {
1291 if (this._colorPicker) {
1292 this._colorPicker._button = oButton.value;
1294 var menuEL = tmp.getMenu().element;
1295 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1296 tmp.getMenu().show();
1298 tmp.getMenu().hide();
1301 YAHOO.util.Event.stopEvent(ev);
1303 tmp.on('mousedown', showPicker, oButton, this);
1304 tmp.on('keydown', showPicker, oButton, this);
1306 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1307 tmp.on('keypress', this._buttonClick, oButton, this);
1308 tmp.on('mousedown', function(ev) {
1309 YAHOO.util.Event.stopEvent(ev);
1310 this._buttonClick(ev, oButton);
1312 tmp.on('click', function(ev) {
1313 YAHOO.util.Event.stopEvent(ev);
1316 //Stop the mousedown event so we can trap the selection in the editor!
1317 tmp.on('mousedown', function(ev) {
1318 YAHOO.util.Event.stopEvent(ev);
1320 tmp.on('click', function(ev) {
1321 YAHOO.util.Event.stopEvent(ev);
1323 tmp.on('change', function(ev) {
1325 if (!oButton.menucmd) {
1326 oButton.menucmd = oButton.value;
1328 oButton.value = ev.value;
1329 this._buttonClick(ev, oButton);
1334 //Hijack the mousedown event in the menu and make it fire a button click..
1335 tmp.on('appendTo', function() {
1337 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1338 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1339 YAHOO.log('mouseDownEvent', 'warn', 'Toolbar');
1340 var oMenu = args[1];
1341 YAHOO.util.Event.stopEvent(args[0]);
1342 tmp._onMenuClick(args[0], tmp);
1343 if (!oButton.menucmd) {
1344 oButton.menucmd = oButton.value;
1346 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1347 self._buttonClick.call(self, args[1], oButton);
1351 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1352 YAHOO.log('clickEvent', 'warn', 'Toolbar');
1353 YAHOO.util.Event.stopEvent(args[0]);
1355 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1356 YAHOO.log('mouseUpEvent', 'warn', 'Toolbar');
1357 YAHOO.util.Event.stopEvent(args[0]);
1364 //Stop the mousedown event so we can trap the selection in the editor!
1365 tmp.on('mousedown', function(ev) {
1366 YAHOO.util.Event.stopEvent(ev);
1368 tmp.on('click', function(ev) {
1369 YAHOO.util.Event.stopEvent(ev);
1372 if (this.browser.ie) {
1374 //Add a couple of new events for IE
1375 tmp.DOM_EVENTS.focusin = true;
1376 tmp.DOM_EVENTS.focusout = true;
1378 //Stop them so we don't loose focus in the Editor
1379 tmp.on('focusin', function(ev) {
1380 YAHOO.util.Event.stopEvent(ev);
1383 tmp.on('focusout', function(ev) {
1384 YAHOO.util.Event.stopEvent(ev);
1386 tmp.on('click', function(ev) {
1387 YAHOO.util.Event.stopEvent(ev);
1391 if (this.browser.webkit) {
1392 //This will keep the document from gaining focus and the editor from loosing it..
1393 //Forcefully remove the focus calls in button!
1394 tmp.hasFocus = function() {
1398 this._buttonList[this._buttonList.length] = tmp;
1399 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1400 if (Lang.isArray(oButton.menu)) {
1401 YAHOO.log('Button type is (' + oButton.type + '), doing extra renderer work.', 'info', 'Toolbar');
1402 var menu = tmp.getMenu();
1403 if (menu && menu.renderEvent) {
1404 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1405 if (oButton.renderer) {
1406 menu.renderEvent.subscribe(oButton.renderer, tmp);
1415 * @method addSeparator
1416 * @description Add a new button separator to the toolbar.
1417 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1418 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1420 addSeparator: function(cont, after) {
1421 if (!this.get('element')) {
1422 this._queue[this._queue.length] = ['addSeparator', arguments];
1425 var sepCont = ((cont) ? cont : this.get('cont'));
1426 if (!this.get('element')) {
1427 this._queue[this._queue.length] = ['addSeparator', arguments];
1430 if (this._sepCount === null) {
1434 YAHOO.log('Separator does not yet exist, creating', 'info', 'Toolbar');
1435 this._sep = document.createElement('SPAN');
1436 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1437 this._sep.innerHTML = '|';
1439 YAHOO.log('Separator does exist, cloning', 'info', 'Toolbar');
1440 var _sep = this._sep.cloneNode(true);
1442 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1446 nextSib = after.get('element').nextSibling;
1447 } else if (after.nextSibling) {
1448 nextSib = after.nextSibling;
1453 if (nextSib == after) {
1454 nextSib.parentNode.appendChild(_sep);
1456 nextSib.parentNode.insertBefore(_sep, nextSib);
1460 sepCont.appendChild(_sep);
1465 * @method _createColorPicker
1467 * @description Creates the core DOM reference to the color picker menu item.
1468 * @param {String} id the id of the toolbar to prefix this DOM container with.
1470 _createColorPicker: function(id) {
1471 if (Dom.get(id + '_colors')) {
1472 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1474 var picker = document.createElement('div');
1475 picker.className = 'yui-toolbar-colors';
1476 picker.id = id + '_colors';
1477 picker.style.display = 'none';
1478 Event.on(window, 'load', function() {
1479 document.body.appendChild(picker);
1482 this._colorPicker = picker;
1485 for (var i in this._colorData) {
1486 if (Lang.hasOwnProperty(this._colorData, i)) {
1487 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1490 html += '<span><em>X</em><strong></strong></span>';
1491 window.setTimeout(function() {
1492 picker.innerHTML = html;
1495 Event.on(picker, 'mouseover', function(ev) {
1496 var picker = this._colorPicker;
1497 var em = picker.getElementsByTagName('em')[0];
1498 var strong = picker.getElementsByTagName('strong')[0];
1499 var tar = Event.getTarget(ev);
1500 if (tar.tagName.toLowerCase() == 'a') {
1501 em.style.backgroundColor = tar.style.backgroundColor;
1502 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1505 Event.on(picker, 'focus', function(ev) {
1506 Event.stopEvent(ev);
1508 Event.on(picker, 'click', function(ev) {
1509 Event.stopEvent(ev);
1511 Event.on(picker, 'mousedown', function(ev) {
1512 Event.stopEvent(ev);
1513 var tar = Event.getTarget(ev);
1514 if (tar.tagName.toLowerCase() == 'a') {
1515 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1516 if (retVal !== false) {
1518 color: tar.innerHTML,
1519 colorName: this._colorData['#' + tar.innerHTML],
1520 value: this._colorPicker._button
1523 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1525 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1530 * @method _resetColorPicker
1532 * @description Clears the currently selected color or mouseover color in the color picker.
1534 _resetColorPicker: function() {
1535 var em = this._colorPicker.getElementsByTagName('em')[0];
1536 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1537 em.style.backgroundColor = 'transparent';
1538 strong.innerHTML = '';
1541 * @method _makeColorButton
1543 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1544 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1546 _makeColorButton: function(_oButton) {
1547 if (!this._colorPicker) {
1548 this._createColorPicker(this.get('id'));
1550 _oButton.type = 'color';
1551 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1552 _oButton.menu.setBody('');
1553 _oButton.menu.render(this.get('cont'));
1554 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1555 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1556 _oButton.menu.beforeShowEvent.subscribe(function() {
1557 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1558 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1559 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1560 this._resetColorPicker();
1561 var _p = this._colorPicker;
1562 if (_p.parentNode) {
1563 _p.parentNode.removeChild(_p);
1565 _oButton.menu.setBody('');
1566 _oButton.menu.appendToBody(_p);
1567 this._colorPicker.style.display = 'block';
1573 * @method _makeSpinButton
1574 * @description Create a button similar to an OS Spin button.. It has an up/down arrow combo to scroll through a range of int values.
1575 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1576 * @param {Object} oButton Object literal containing the buttons initial config
1578 _makeSpinButton: function(_button, oButton) {
1579 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1581 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1582 range = oButton.range,
1583 _b1 = document.createElement('a'),
1584 _b2 = document.createElement('a');
1587 _b1.tabIndex = '-1';
1588 _b2.tabIndex = '-1';
1590 //Setup the up and down arrows
1591 _b1.className = 'up';
1592 _b1.title = this.STR_SPIN_UP;
1593 _b1.innerHTML = this.STR_SPIN_UP;
1594 _b2.className = 'down';
1595 _b2.title = this.STR_SPIN_DOWN;
1596 _b2.innerHTML = this.STR_SPIN_DOWN;
1598 //Append them to the container
1599 _par.appendChild(_b1);
1600 _par.appendChild(_b2);
1602 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1603 _button.set('title', label);
1605 var cleanVal = function(value) {
1606 value = ((value < range[0]) ? range[0] : value);
1607 value = ((value > range[1]) ? range[1] : value);
1611 var br = this.browser;
1613 var strLabel = this.STR_SPIN_LABEL;
1614 if (this._titlebar && this._titlebar.firstChild) {
1615 tbar = this._titlebar.firstChild;
1618 var _intUp = function(ev) {
1619 YAHOO.util.Event.stopEvent(ev);
1620 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1621 var value = parseInt(_button.get('label'), 10);
1623 value = cleanVal(value);
1624 _button.set('label', ''+value);
1625 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1626 _button.set('title', label);
1627 if (!br.webkit && tbar) {
1628 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1631 self._buttonClick(ev, oButton);
1635 var _intDown = function(ev) {
1636 YAHOO.util.Event.stopEvent(ev);
1637 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1638 var value = parseInt(_button.get('label'), 10);
1640 value = cleanVal(value);
1642 _button.set('label', ''+value);
1643 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1644 _button.set('title', label);
1645 if (!br.webkit && tbar) {
1646 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1649 self._buttonClick(ev, oButton);
1653 var _intKeyUp = function(ev) {
1654 if (ev.keyCode == 38) {
1656 } else if (ev.keyCode == 40) {
1658 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1660 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1665 //Handle arrow keys..
1666 _button.on('keydown', _intKeyUp, this, true);
1668 //Listen for the click on the up button and act on it
1669 //Listen for the click on the down button and act on it
1670 Event.on(_b1, 'mousedown',function(ev) {
1671 Event.stopEvent(ev);
1673 Event.on(_b2, 'mousedown', function(ev) {
1674 Event.stopEvent(ev);
1676 Event.on(_b1, 'click', _intUp, this, true);
1677 Event.on(_b2, 'click', _intDown, this, true);
1681 * @method _buttonClick
1682 * @description Click handler for all buttons in the toolbar.
1683 * @param {String} ev The event that was passed in.
1684 * @param {Object} info Object literal of information about the button that was clicked.
1686 _buttonClick: function(ev, info) {
1689 if (ev && ev.type == 'keypress') {
1690 if (ev.keyCode == 9) {
1692 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1699 var fireNextEvent = true,
1702 info.isSelected = this.isSelected(info.id);
1705 YAHOO.log('fireEvent::' + info.value + 'Click', 'info', 'Toolbar');
1706 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1707 if (retValue === false) {
1708 fireNextEvent = false;
1712 if (info.menucmd && fireNextEvent) {
1713 YAHOO.log('fireEvent::' + info.menucmd + 'Click', 'info', 'Toolbar');
1714 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1715 if (retValue === false) {
1716 fireNextEvent = false;
1719 if (fireNextEvent) {
1720 YAHOO.log('fireEvent::buttonClick', 'info', 'Toolbar');
1721 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1724 if (info.type == 'select') {
1725 var button = this.getButtonById(info.id);
1726 if (button.buttonType == 'rich') {
1727 var txt = info.value;
1728 for (var i = 0; i < info.menu.length; i++) {
1729 if (info.menu[i].value == info.value) {
1730 txt = info.menu[i].text;
1734 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1735 var _items = button.getMenu().getItems();
1736 for (var m = 0; m < _items.length; m++) {
1737 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1738 _items[m].cfg.setProperty('checked', true);
1740 _items[m].cfg.setProperty('checked', false);
1746 Event.stopEvent(ev);
1753 * @description Flag to determine if the arrow nav listeners have been attached
1759 * @property _navCounter
1760 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1766 * @method _navigateButtons
1767 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1768 * @param {Event} ev The Key Event
1770 _navigateButtons: function(ev) {
1771 switch (ev.keyCode) {
1774 if (ev.keyCode == 37) {
1779 if (this._navCounter > (this._buttonList.length - 1)) {
1780 this._navCounter = 0;
1782 if (this._navCounter < 0) {
1783 this._navCounter = (this._buttonList.length - 1);
1785 if (this._buttonList[this._navCounter]) {
1786 var el = this._buttonList[this._navCounter].get('element');
1787 if (this.browser.ie) {
1788 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1790 if (this._buttonList[this._navCounter].get('disabled')) {
1791 this._navigateButtons(ev);
1801 * @method _handleFocus
1802 * @description Sets up the listeners for the arrow key navigation
1804 _handleFocus: function() {
1805 if (!this._keyNav) {
1806 var ev = 'keypress';
1807 if (this.browser.ie) {
1810 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1811 this._keyNav = true;
1812 this._navCounter = -1;
1816 * @method getButtonById
1817 * @description Gets a button instance from the toolbar by is Dom id.
1818 * @param {String} id The Dom id to query for.
1819 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1821 getButtonById: function(id) {
1822 var len = this._buttonList.length;
1823 for (var i = 0; i < len; i++) {
1824 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1825 return this._buttonList[i];
1831 * @method getButtonByValue
1832 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1833 * @param {String} value The button value to query for.
1834 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1836 getButtonByValue: function(value) {
1837 var _buttons = this.get('buttons');
1841 var len = _buttons.length;
1842 for (var i = 0; i < len; i++) {
1843 if (_buttons[i].group !== undefined) {
1844 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1845 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1846 return this.getButtonById(_buttons[i].buttons[m].id);
1848 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1849 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1850 if (_buttons[i].buttons[m].menu[s].value == value) {
1851 return this.getButtonById(_buttons[i].buttons[m].id);
1857 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1858 return this.getButtonById(_buttons[i].id);
1860 if (_buttons[i].menu) { //Menu Button, loop through the values
1861 for (var j = 0; j < _buttons[i].menu.length; j++) {
1862 if (_buttons[i].menu[j].value == value) {
1863 return this.getButtonById(_buttons[i].id);
1872 * @method getButtonByIndex
1873 * @description Gets a button instance from the toolbar by is index in _buttonList.
1874 * @param {Number} index The index of the button in _buttonList.
1875 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1877 getButtonByIndex: function(index) {
1878 if (this._buttonList[index]) {
1879 return this._buttonList[index];
1885 * @method getButtons
1886 * @description Returns an array of buttons in the current toolbar
1889 getButtons: function() {
1890 return this._buttonList;
1893 * @method disableButton
1894 * @description Disables a button in the toolbar.
1895 * @param {String/Number} id Disable a button by it's id, index or value.
1898 disableButton: function(id) {
1899 var button = getButton.call(this, id);
1901 button.set('disabled', true);
1907 * @method enableButton
1908 * @description Enables a button in the toolbar.
1909 * @param {String/Number} id Enable a button by it's id, index or value.
1912 enableButton: function(id) {
1913 if (this.get('disabled')) {
1916 var button = getButton.call(this, id);
1918 if (button.get('disabled')) {
1919 button.set('disabled', false);
1926 * @method isSelected
1927 * @description Tells if a button is selected or not.
1928 * @param {String/Number} id A button by it's id, index or value.
1931 isSelected: function(id) {
1932 var button = getButton.call(this, id);
1934 return button._selected;
1939 * @method selectButton
1940 * @description Selects a button in the toolbar.
1941 * @param {String/Number} id Select a button by it's id, index or value.
1942 * @param {String} value If this is a Menu Button, check this item in the menu
1945 selectButton: function(id, value) {
1946 var button = getButton.call(this, id);
1948 button.addClass('yui-button-selected');
1949 button.addClass('yui-button-' + button.get('value') + '-selected');
1950 button._selected = true;
1952 if (button.buttonType == 'rich') {
1953 var _items = button.getMenu().getItems();
1954 for (var m = 0; m < _items.length; m++) {
1955 if (_items[m].value == value) {
1956 _items[m].cfg.setProperty('checked', true);
1957 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1959 _items[m].cfg.setProperty('checked', false);
1969 * @method deselectButton
1970 * @description Deselects a button in the toolbar.
1971 * @param {String/Number} id Deselect a button by it's id, index or value.
1974 deselectButton: function(id) {
1975 var button = getButton.call(this, id);
1977 button.removeClass('yui-button-selected');
1978 button.removeClass('yui-button-' + button.get('value') + '-selected');
1979 button.removeClass('yui-button-hover');
1980 button._selected = false;
1986 * @method deselectAllButtons
1987 * @description Deselects all buttons in the toolbar.
1990 deselectAllButtons: function() {
1991 var len = this._buttonList.length;
1992 for (var i = 0; i < len; i++) {
1993 this.deselectButton(this._buttonList[i]);
1997 * @method disableAllButtons
1998 * @description Disables all buttons in the toolbar.
2001 disableAllButtons: function() {
2002 if (this.get('disabled')) {
2005 var len = this._buttonList.length;
2006 for (var i = 0; i < len; i++) {
2007 this.disableButton(this._buttonList[i]);
2011 * @method enableAllButtons
2012 * @description Enables all buttons in the toolbar.
2015 enableAllButtons: function() {
2016 if (this.get('disabled')) {
2019 var len = this._buttonList.length;
2020 for (var i = 0; i < len; i++) {
2021 this.enableButton(this._buttonList[i]);
2025 * @method resetAllButtons
2026 * @description Resets all buttons to their initial state.
2027 * @param {Object} _ex Except these buttons
2030 resetAllButtons: function(_ex) {
2031 if (!Lang.isObject(_ex)) {
2034 if (this.get('disabled') || !this._buttonList) {
2037 var len = this._buttonList.length;
2038 for (var i = 0; i < len; i++) {
2039 var _button = this._buttonList[i];
2041 var disabled = _button._configs.disabled._initialConfig.value;
2042 if (_ex[_button.get('id')]) {
2043 this.enableButton(_button);
2044 this.selectButton(_button);
2047 this.disableButton(_button);
2049 this.enableButton(_button);
2051 this.deselectButton(_button);
2057 * @method destroyButton
2058 * @description Destroy a button in the toolbar.
2059 * @param {String/Number} id Destroy a button by it's id or index.
2062 destroyButton: function(id) {
2063 var button = getButton.call(this, id);
2065 var thisID = button.get('id'),
2066 new_list = [], i = 0,
2067 len = this._buttonList.length;
2071 for (i = 0; i < len; i++) {
2072 if (this._buttonList[i].get('id') != thisID) {
2073 new_list[new_list.length]= this._buttonList[i];
2077 this._buttonList = new_list;
2084 * @description Destroys the toolbar, all of it's elements and objects.
2087 destroy: function() {
2088 var len = this._configuredButtons.length, j, i;
2089 for(b = 0; b < len; b++) {
2090 this.destroyButton(this._configuredButtons[b]);
2093 this._configuredButtons = null;
2095 this.get('element').innerHTML = '';
2096 this.get('element').className = '';
2097 //Brutal Object Destroy
2099 if (Lang.hasOwnProperty(this, i)) {
2107 * @description Programatically collapse the toolbar.
2108 * @param {Boolean} collapse True to collapse, false to expand.
2110 collapse: function(collapse) {
2111 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2112 if (collapse === false) {
2113 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2115 Dom.removeClass(el[0], 'collapsed');
2116 el[0].title = this.STR_COLLAPSE;
2118 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2121 Dom.addClass(el[0], 'collapsed');
2122 el[0].title = this.STR_EXPAND;
2124 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2125 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2130 * @description Returns a string representing the toolbar.
2133 toString: function() {
2134 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2138 * @event buttonClick
2139 * @param {Object} o The object passed to this handler is the button config used to create the button.
2140 * @description Fires when any botton receives a click event. Passes back a single object representing the buttons config object. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2141 * @type YAHOO.util.CustomEvent
2145 * @param {Object} o The object passed to this handler is the button config used to create the button.
2146 * @description This is a special dynamic event that is created and dispatched based on the value property
2147 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2151 * { type: 'button', value: 'test', value: 'testButton' }
2154 * With the valueClick event you could subscribe to this buttons click event with this:
2155 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2156 * @type YAHOO.util.CustomEvent
2159 * @event toolbarExpanded
2160 * @description Fires when the toolbar is expanded via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2161 * @type YAHOO.util.CustomEvent
2164 * @event toolbarCollapsed
2165 * @description Fires when the toolbar is collapsed via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2166 * @type YAHOO.util.CustomEvent
2171 * @description <p>The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.</p>
2172 * @namespace YAHOO.widget
2173 * @requires yahoo, dom, element, event, toolbar
2174 * @optional animation, container_core, resize, dragdrop
2178 var Dom = YAHOO.util.Dom,
2179 Event = YAHOO.util.Event,
2181 Toolbar = YAHOO.widget.Toolbar;
2184 * The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.
2186 * @class SimpleEditor
2187 * @extends YAHOO.util.Element
2188 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2189 * @param {Object} attrs Object liternal containing configuration parameters.
2192 YAHOO.widget.SimpleEditor = function(el, attrs) {
2193 YAHOO.log('SimpleEditor Initalizing', 'info', 'SimpleEditor');
2196 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2197 Lang.augmentObject(o, el); //Break the config reference
2198 el = document.createElement('textarea');
2199 this.DOMReady = true;
2201 var c = Dom.get(o.container);
2204 document.body.appendChild(el);
2208 Lang.augmentObject(o, attrs); //Break the config reference
2217 if (Lang.isString(el)) {
2220 if (oConfig.attributes.id) {
2221 id = oConfig.attributes.id;
2223 this.DOMReady = true;
2224 id = Dom.generateId(el);
2227 oConfig.element = el;
2229 var element_cont = document.createElement('DIV');
2230 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2231 id: id + '_container'
2233 var div = document.createElement('div');
2234 Dom.addClass(div, 'first-child');
2235 oConfig.attributes.element_cont.appendChild(div);
2237 if (!oConfig.attributes.toolbar_cont) {
2238 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2239 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2240 div.appendChild(oConfig.attributes.toolbar_cont);
2242 var editorWrapper = document.createElement('DIV');
2243 div.appendChild(editorWrapper);
2244 oConfig.attributes.editor_wrapper = editorWrapper;
2246 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2250 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2253 * @property _resizeConfig
2254 * @description The default config for the Resize Utility
2266 * @method _setupResize
2267 * @description Creates the Resize instance and binds its events.
2269 _setupResize: function() {
2270 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2271 if (this.get('resize')) {
2273 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2274 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2275 this.resize.on('resize', function(args) {
2276 var anim = this.get('animate');
2277 this.set('animate', false);
2278 this.set('width', args.width + 'px');
2279 var h = args.height,
2280 th = (this.toolbar.get('element').clientHeight + 2),
2283 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2285 var newH = (h - th - dh);
2286 this.set('height', newH + 'px');
2287 this.get('element_cont').setStyle('height', '');
2288 this.set('animate', anim);
2294 * @description A reference to the Resize object
2295 * @type YAHOO.util.Resize
2301 * @description Sets up the DD instance used from the 'drag' config option.
2303 _setupDD: function() {
2304 if (!YAHOO.util.DD) { return false; }
2305 if (this.get('drag')) {
2306 YAHOO.log('Attaching DD instance to Editor', 'info', 'SimpleEditor');
2307 var d = this.get('drag'),
2309 if (d === 'proxy') {
2310 dd = YAHOO.util.DDProxy;
2313 this.dd = new dd(this.get('element_cont').get('element'));
2314 this.toolbar.addClass('draggable');
2315 this.dd.setHandleElId(this.toolbar._titlebar);
2320 * @description A reference to the DragDrop object.
2321 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2326 * @property _lastCommand
2327 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2331 _undoNodeChange: function() {},
2332 _storeUndo: function() {},
2336 * @description Checks a keyMap entry against a key event
2337 * @param {Object} k The _keyMap object
2338 * @param {Event} e The Mouse Event
2341 _checkKey: function(k, e) {
2343 if ((e.keyCode === k.key)) {
2344 if (k.mods && (k.mods.length > 0)) {
2346 for (var i = 0; i < k.mods.length; i++) {
2347 if (this.browser.mac) {
2348 if (k.mods[i] == 'ctrl') {
2352 if (e[k.mods[i] + 'Key'] === true) {
2356 if (val === k.mods.length) {
2363 //YAHOO.log('Shortcut Key Check: (' + k.key + ') return: ' + ret, 'info', 'SimpleEditor');
2369 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2370 * This entry shows that when key 87 (W) is found with the modifiers of shift and control, the window will close. You can customize this object to tweak keyboard shortcuts.
2371 * @type {Object/Mixed}
2380 mods: ['shift', 'ctrl']
2391 mods: ['shift', 'ctrl']
2395 mods: ['shift', 'ctrl']
2399 mods: ['shift', 'ctrl']
2403 mods: ['shift', 'ctrl']
2407 mods: ['shift', 'ctrl']
2411 mods: ['shift', 'ctrl']
2419 mods: ['shift', 'ctrl']
2423 mods: ['shift', 'ctrl']
2427 mods: ['shift', 'ctrl']
2431 mods: ['shift', 'ctrl']
2436 * @method _cleanClassName
2437 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2438 * @param {String} str The classname to clean up
2441 _cleanClassName: function(str) {
2442 return str.replace(/ /g, '-').toLowerCase();
2445 * @property _textarea
2446 * @description Flag to determine if we are using a textarea or an HTML Node.
2451 * @property _docType
2452 * @description The DOCTYPE to use in the editable container.
2455 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2457 * @property editorDirty
2458 * @description This flag will be set when certain things in the Editor happen. It is to be used by the developer to check to see if content has changed.
2463 * @property _defaultCSS
2464 * @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' }
2467 _defaultCSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } .warning-localfile { border-bottom: 1px dashed red !important; } .yui-busy { cursor: wait !important; } img.selected { border: 2px dotted #808080; } img { cursor: pointer !important; border: none; } body.ptags.webkit div.yui-wk-p { margin: 11px 0; } body.ptags.webkit div.yui-wk-div { margin: 0; }',
2469 * @property _defaultToolbar
2471 * @description Default toolbar config.
2474 _defaultToolbar: null,
2476 * @property _lastButton
2478 * @description The last button pressed, so we don't disable it.
2483 * @property _baseHREF
2485 * @description The base location of the editable page (this page) so that relative paths for image work.
2488 _baseHREF: function() {
2489 var href = document.location.href;
2490 if (href.indexOf('?') !== -1) { //Remove the query string
2491 href = href.substring(0, href.indexOf('?'));
2493 href = href.substring(0, href.lastIndexOf('/')) + '/';
2497 * @property _lastImage
2499 * @description Safari reference for the last image selected (for styling as selected).
2504 * @property _blankImageLoaded
2506 * @description Don't load the blank image more than once..
2509 _blankImageLoaded: null,
2511 * @property _fixNodesTimer
2513 * @description Holder for the fixNodes timer
2516 _fixNodesTimer: null,
2518 * @property _nodeChangeTimer
2520 * @description Holds a reference to the nodeChange setTimeout call
2523 _nodeChangeTimer: null,
2525 * @property _nodeChangeDelayTimer
2527 * @description Holds a reference to the nodeChangeDelay setTimeout call
2530 _nodeChangeDelayTimer: null,
2532 * @property _lastNodeChangeEvent
2534 * @description Flag to determine the last event that fired a node change
2537 _lastNodeChangeEvent: null,
2539 * @property _lastNodeChange
2541 * @description Flag to determine when the last node change was fired
2546 * @property _rendered
2548 * @description Flag to determine if editor has been rendered or not
2553 * @property DOMReady
2555 * @description Flag to determine if DOM is ready or not
2560 * @property _selection
2562 * @description Holder for caching iframe selections
2569 * @description DOM Element holder for the editor Mask when disabled
2574 * @property _showingHiddenElements
2576 * @description Status of the hidden elements button
2579 _showingHiddenElements: null,
2581 * @property currentWindow
2582 * @description A reference to the currently open EditorWindow
2585 currentWindow: null,
2587 * @property currentEvent
2588 * @description A reference to the current editor event
2593 * @property operaEvent
2595 * @description setTimeout holder for Opera and Image DoubleClick event..
2600 * @property currentFont
2601 * @description A reference to the last font selected from the Toolbar
2606 * @property currentElement
2607 * @description A reference to the current working element in the editor
2610 currentElement: null,
2613 * @description A reference to the dompath container for writing the current working dom path to.
2618 * @property beforeElement
2619 * @description A reference to the H2 placed before the editor for Accessibilty.
2622 beforeElement: null,
2624 * @property afterElement
2625 * @description A reference to the H2 placed after the editor for Accessibilty.
2630 * @property invalidHTML
2631 * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found. If you set the value of a key to "{ keepContents: true }", then the element will be replaced with a yui-non span to be filtered out when cleanHTML is called. The only tag that is ignored here is the span tag as it will force the Editor into a loop and freeze the browser. However.. all of these tags will be removed in the cleanHTML routine.
2649 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2650 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2655 * @property _contentTimer
2656 * @description setTimeout holder for documentReady check
2658 _contentTimer: null,
2661 * @property _contentTimerMax
2662 * @description The number of times the loaded content should be checked before giving up. Default: 500
2664 _contentTimerMax: 500,
2667 * @property _contentTimerCounter
2668 * @description Counter to check the number of times the body is polled for before giving up
2671 _contentTimerCounter: 0,
2674 * @property _disabled
2675 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2678 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2681 * @property _alwaysDisabled
2682 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2685 _alwaysDisabled: { undo: true, redo: true },
2688 * @property _alwaysEnabled
2689 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2692 _alwaysEnabled: { },
2695 * @property _semantic
2696 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2699 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2702 * @property _tag2cmd
2703 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2712 'sup': 'superscript',
2714 'img': 'insertimage',
2716 'ul' : 'insertunorderedlist',
2717 'ol' : 'insertorderedlist'
2721 * @private _createIframe
2722 * @description Creates the DOM and YUI Element for the iFrame editor area.
2723 * @param {String} id The string ID to prefix the iframe with
2724 * @return {Object} iFrame object
2726 _createIframe: function() {
2727 var ifrmDom = document.createElement('iframe');
2728 ifrmDom.id = this.get('id') + '_editor';
2736 allowTransparency: 'true',
2739 if (this.get('autoHeight')) {
2740 config.scrolling = 'no';
2742 for (var i in config) {
2743 if (Lang.hasOwnProperty(config, i)) {
2744 ifrmDom.setAttribute(i, config[i]);
2747 var isrc = 'javascript:;';
2748 if (this.browser.ie) {
2749 //isrc = 'about:blank';
2750 //TODO - Check this, I have changed it before..
2751 isrc = 'javascript:false;';
2753 ifrmDom.setAttribute('src', isrc);
2754 var ifrm = new YAHOO.util.Element(ifrmDom);
2755 ifrm.setStyle('visibility', 'hidden');
2759 * @private _isElement
2760 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2761 * @param {HTMLElement} el The element to check
2762 * @param {String} tag The tag that the element needs to be
2765 _isElement: function(el, tag) {
2766 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2769 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2775 * @private _hasParent
2776 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2777 * @param {HTMLElement} el The element to check
2778 * @param {String} tag The tag that the element needs to be
2779 * @return HTMLElement
2781 _hasParent: function(el, tag) {
2782 if (!el || !el.parentNode) {
2786 while (el.parentNode) {
2787 if (this._isElement(el, tag)) {
2790 if (el.parentNode) {
2801 * @description Get the Document of the IFRAME
2804 _getDoc: function() {
2807 if (this.get('iframe').get('element').contentWindow.document) {
2808 value = this.get('iframe').get('element').contentWindow.document;
2817 * @method _getWindow
2818 * @description Get the Window of the IFRAME
2821 _getWindow: function() {
2822 return this.get('iframe').get('element').contentWindow;
2826 * @description Attempt to set the focus of the iframes window.
2829 this._getWindow().focus();
2833 * @depreciated - This should not be used, moved to this.focus();
2834 * @method _focusWindow
2835 * @description Attempt to set the focus of the iframes window.
2837 _focusWindow: function() {
2838 YAHOO.log('_focusWindow: depreciated in favor of this.focus()', 'warn', 'Editor');
2843 * @method _hasSelection
2844 * @description Determines if there is a selection in the editor document.
2847 _hasSelection: function() {
2848 var sel = this._getSelection();
2849 var range = this._getRange();
2852 if (!sel || !range) {
2857 if (this.browser.ie || this.browser.opera) {
2865 if (this.browser.webkit) {
2866 if (sel+'' !== '') {
2870 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2879 * @method _getSelection
2880 * @description Handles the different selection objects across the A-Grade list.
2881 * @return {Object} Selection Object
2883 _getSelection: function() {
2885 if (this._getDoc() && this._getWindow()) {
2886 if (this._getDoc().selection) {
2887 _sel = this._getDoc().selection;
2889 _sel = this._getWindow().getSelection();
2891 //Handle Safari's lack of Selection Object
2892 if (this.browser.webkit) {
2893 if (_sel.baseNode) {
2894 this._selection = {};
2895 this._selection.baseNode = _sel.baseNode;
2896 this._selection.baseOffset = _sel.baseOffset;
2897 this._selection.extentNode = _sel.extentNode;
2898 this._selection.extentOffset = _sel.extentOffset;
2899 } else if (this._selection !== null) {
2900 _sel = this._getWindow().getSelection();
2901 _sel.setBaseAndExtent(
2902 this._selection.baseNode,
2903 this._selection.baseOffset,
2904 this._selection.extentNode,
2905 this._selection.extentOffset);
2906 this._selection = null;
2914 * @method _selectNode
2915 * @description Places the highlight around a given node
2916 * @param {HTMLElement} node The node to select
2918 _selectNode: function(node, collapse) {
2922 var sel = this._getSelection(),
2925 if (this.browser.ie) {
2926 try { //IE freaks out here sometimes..
2927 range = this._getDoc().body.createTextRange();
2928 range.moveToElementText(node);
2931 YAHOO.log('IE failed to select element: ' + node.tagName, 'warn', 'SimpleEditor');
2933 } else if (this.browser.webkit) {
2935 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2937 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2939 } else if (this.browser.opera) {
2940 sel = this._getWindow().getSelection();
2941 range = this._getDoc().createRange();
2942 range.selectNode(node);
2943 sel.removeAllRanges();
2944 sel.addRange(range);
2946 range = this._getDoc().createRange();
2947 range.selectNodeContents(node);
2948 sel.removeAllRanges();
2949 sel.addRange(range);
2951 //TODO - Check Performance
2957 * @description Handles the different range objects across the A-Grade list.
2958 * @return {Object} Range Object
2960 _getRange: function() {
2961 var sel = this._getSelection();
2967 if (this.browser.webkit && !sel.getRangeAt) {
2968 var _range = this._getDoc().createRange();
2970 _range.setStart(sel.anchorNode, sel.anchorOffset);
2971 _range.setEnd(sel.focusNode, sel.focusOffset);
2973 _range = this._getWindow().getSelection()+'';
2978 if (this.browser.ie || this.browser.opera) {
2980 return sel.createRange();
2986 if (sel.rangeCount > 0) {
2987 return sel.getRangeAt(0);
2993 * @method _setDesignMode
2994 * @description Sets the designMode property of the iFrame document's body.
2995 * @param {String} state This should be either on or off
2997 _setDesignMode: function(state) {
2998 if (this.get('setDesignMode')) {
3000 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on');
3006 * @method _toggleDesignMode
3007 * @description Toggles the designMode property of the iFrame document on and off.
3008 * @return {String} The state that it was set to.
3010 _toggleDesignMode: function() {
3011 YAHOO.log('It is not recommended to use this method and it will be depreciated.', 'warn', 'SimpleEditor');
3012 var _dMode = this._getDoc().designMode,
3013 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on');
3014 this._setDesignMode(_state);
3019 * @property _focused
3020 * @description Holder for trapping focus/blur state and prevent double events
3026 * @method _handleFocus
3027 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
3028 * @param {Event} e The DOM Event
3030 _handleFocus: function(e) {
3031 if (!this._focused) {
3032 //YAHOO.log('Editor Window Focused', 'info', 'SimpleEditor');
3033 this._focused = true;
3034 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
3039 * @method _handleBlur
3040 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
3041 * @param {Event} e The DOM Event
3043 _handleBlur: function(e) {
3044 if (this._focused) {
3045 //YAHOO.log('Editor Window Blurred', 'info', 'SimpleEditor');
3046 this._focused = false;
3047 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
3052 * @method _initEditorEvents
3053 * @description This method sets up the listeners on the Editors document.
3055 _initEditorEvents: function() {
3056 //Setup Listeners on iFrame
3057 var doc = this._getDoc(),
3058 win = this._getWindow();
3060 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3061 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3062 Event.on(doc, 'click', this._handleClick, this, true);
3063 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3064 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3065 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3066 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3067 /* TODO -- Everyone but Opera works here..
3068 Event.on(doc, 'paste', function() {
3069 YAHOO.log('PASTE', 'info', 'SimpleEditor');
3074 Event.on(win, 'focus', this._handleFocus, this, true);
3075 Event.on(win, 'blur', this._handleBlur, this, true);
3079 * @method _removeEditorEvents
3080 * @description This method removes the listeners on the Editors document (for disabling).
3082 _removeEditorEvents: function() {
3083 //Remove Listeners on iFrame
3084 var doc = this._getDoc(),
3085 win = this._getWindow();
3087 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3088 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3089 Event.removeListener(doc, 'click', this._handleClick, this, true);
3090 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3091 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3092 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3093 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3096 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3097 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3099 _fixWebkitDivs: function() {
3100 if (this.browser.webkit) {
3101 var divs = this._getDoc().body.getElementsByTagName('div');
3102 Dom.addClass(divs, 'yui-wk-div');
3107 * @method _initEditor
3108 * @param {Boolean} raw Don't add events.
3109 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3111 _initEditor: function(raw) {
3112 if (this._editorInit) {
3115 this._editorInit = true;
3116 if (this.browser.ie) {
3117 this._getDoc().body.style.margin = '0';
3119 if (!this.get('disabled')) {
3120 this._setDesignMode('on');
3121 this._contentTimerCounter = 0;
3123 if (!this._getDoc().body) {
3124 YAHOO.log('Body is null, check again', 'error', 'SimpleEditor');
3125 this._contentTimerCounter = 0;
3126 this._editorInit = false;
3127 this._checkLoaded();
3131 YAHOO.log('editorLoaded', 'info', 'SimpleEditor');
3133 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3135 if (!this.get('disabled')) {
3136 this._initEditorEvents();
3137 this.toolbar.set('disabled', false);
3141 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this });
3143 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3145 this._fixWebkitDivs();
3146 if (this.get('dompath')) {
3147 YAHOO.log('Delayed DomPath write', 'info', 'SimpleEditor');
3149 setTimeout(function() {
3150 self._writeDomPath.call(self);
3151 self._setupResize.call(self);
3155 for (var i in this.browser) {
3156 if (this.browser[i]) {
3160 if (this.get('ptags')) {
3163 Dom.addClass(this._getDoc().body, br.join(' '));
3164 this.nodeChange(true);
3168 * @method _checkLoaded
3169 * @param {Boolean} raw Don't add events.
3170 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3172 _checkLoaded: function(raw) {
3173 this._editorInit = false;
3174 this._contentTimerCounter++;
3175 if (this._contentTimer) {
3176 clearTimeout(this._contentTimer);
3178 if (this._contentTimerCounter > this._contentTimerMax) {
3179 YAHOO.log('ERROR: Body Did Not load', 'error', 'SimpleEditor');
3184 if (this._getDoc() && this._getDoc().body) {
3185 if (this.browser.ie) {
3186 if (this._getDoc().body.readyState == 'complete') {
3190 if (this._getDoc().body._rteLoaded === true) {
3197 YAHOO.log('checking body (e)' + e, 'error', 'SimpleEditor');
3200 if (init === true) {
3201 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3202 YAHOO.log('Firing _initEditor', 'info', 'SimpleEditor');
3203 this._initEditor(raw);
3206 this._contentTimer = setTimeout(function() {
3207 self._checkLoaded.call(self, raw);
3213 * @method _setInitialContent
3214 * @param {Boolean} raw Don't add events.
3215 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3217 _setInitialContent: function(raw) {
3218 YAHOO.log('Populating editor body with contents of the text area', 'info', 'SimpleEditor');
3220 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3227 var html = Lang.substitute(this.get('html'), {
3228 TITLE: this.STR_TITLE,
3229 CONTENT: this._cleanIncomingHTML(value),
3230 CSS: this.get('css'),
3231 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3232 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3236 html = html.replace(/RIGHT_BRACKET/gi, '{');
3237 html = html.replace(/LEFT_BRACKET/gi, '}');
3239 if (document.compatMode != 'BackCompat') {
3240 YAHOO.log('Adding Doctype to editable area', 'info', 'SimpleEditor');
3241 html = this._docType + "\n" + html;
3243 YAHOO.log('DocType skipped because we are in BackCompat Mode.', 'warn', 'SimpleEditor');
3246 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3247 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3250 if (this.browser.air) {
3251 doc = this._getDoc().implementation.createHTMLDocument();
3252 var origDoc = this._getDoc();
3258 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3259 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3260 origDoc.body._rteLoaded = true;
3262 doc = this._getDoc();
3268 YAHOO.log('Setting doc failed.. (_setInitialContent)', 'error', 'SimpleEditor');
3269 //Safari will only be here if we are hidden
3273 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3274 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3276 this.get('iframe').setStyle('visibility', '');
3278 this._checkLoaded(raw);
3283 * @method _setMarkupType
3284 * @param {String} action The action to take. Possible values are: css, default or semantic
3285 * @description This method will turn on/off the useCSS execCommand.
3287 _setMarkupType: function(action) {
3288 switch (this.get('markup')) {
3290 this._setEditorStyle(true);
3293 this._setEditorStyle(false);
3297 if (this._semantic[action]) {
3298 this._setEditorStyle(false);
3300 this._setEditorStyle(true);
3306 * Set the editor to use CSS instead of HTML
3307 * @param {Booleen} stat True/False
3309 _setEditorStyle: function(stat) {
3311 this._getDoc().execCommand('useCSS', false, !stat);
3317 * @method _getSelectedElement
3318 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3319 * @return {HTMLElement} The currently selected element.
3321 _getSelectedElement: function() {
3322 var doc = this._getDoc(),
3328 if (this.browser.ie) {
3329 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3330 range = this._getRange();
3332 elm = range.item ? range.item(0) : range.parentElement();
3333 if (this._hasSelection()) {
3335 //WTF.. Why can't I get an element reference here?!??!
3337 if (elm === doc.body) {
3341 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3342 elm = Event.getTarget(this.currentEvent);
3345 sel = this._getSelection();
3346 range = this._getRange();
3348 if (!sel || !range) {
3352 if (!this._hasSelection() && this.browser.webkit3) {
3355 if (this.browser.gecko) {
3357 if (range.startContainer) {
3358 if (range.startContainer.nodeType === 3) {
3359 elm = range.startContainer.parentNode;
3360 } else if (range.startContainer.nodeType === 1) {
3361 elm = range.startContainer;
3364 if (this.currentEvent) {
3365 var tar = Event.getTarget(this.currentEvent);
3366 if (!this._isElement(tar, 'html')) {
3376 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3377 if (sel.anchorNode.parentNode) { //next check parentNode
3378 elm = sel.anchorNode.parentNode;
3380 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3381 elm = sel.anchorNode.nextSibling;
3384 if (this._isElement(elm, 'br')) {
3388 elm = range.commonAncestorContainer;
3389 if (!range.collapsed) {
3390 if (range.startContainer == range.endContainer) {
3391 if (range.startOffset - range.endOffset < 2) {
3392 if (range.startContainer.hasChildNodes()) {
3393 elm = range.startContainer.childNodes[range.startOffset];
3402 if (this.currentEvent !== null) {
3404 switch (this.currentEvent.type) {
3408 if (this.browser.webkit) {
3409 elm = Event.getTarget(this.currentEvent);
3417 YAHOO.log('Firefox 1.5 errors here: ' + e, 'error', 'SimpleEditor');
3419 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3420 //TODO is this still needed?
3421 //elm = this.currentElement[0];
3425 if (this.browser.opera || this.browser.webkit) {
3426 if (this.currentEvent && !elm) {
3427 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3430 if (!elm || !elm.tagName) {
3433 if (this._isElement(elm, 'html')) {
3434 //Safari sometimes gives us the HTML node back..
3437 if (this._isElement(elm, 'body')) {
3438 //make sure that body means this body not the parent..
3441 if (elm && !elm.parentNode) { //Not in document
3444 if (elm === undefined) {
3451 * @method _getDomPath
3452 * @description This method will attempt to build the DOM path from the currently selected element.
3453 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3454 * @return {Array} An array of node references that will create the DOM Path.
3456 _getDomPath: function(el) {
3458 el = this._getSelectedElement();
3461 while (el !== null) {
3462 if (el.ownerDocument != this._getDoc()) {
3466 //Check to see if we get el.nodeName and nodeType
3467 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3468 domPath[domPath.length] = el;
3471 if (this._isElement(el, 'body')) {
3477 if (domPath.length === 0) {
3478 if (this._getDoc() && this._getDoc().body) {
3479 domPath[0] = this._getDoc().body;
3482 return domPath.reverse();
3486 * @method _writeDomPath
3487 * @description Write the current DOM path out to the dompath container below the editor.
3489 _writeDomPath: function() {
3490 var path = this._getDomPath(),
3495 for (var i = 0; i < path.length; i++) {
3496 var tag = path[i].tagName.toLowerCase();
3497 if ((tag == 'ol') && (path[i].type)) {
3498 tag += ':' + path[i].type;
3500 if (Dom.hasClass(path[i], 'yui-tag')) {
3501 tag = path[i].getAttribute('tag');
3503 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3505 case 'b': tag = 'strong'; break;
3506 case 'i': tag = 'em'; break;
3509 if (!Dom.hasClass(path[i], 'yui-non')) {
3510 if (Dom.hasClass(path[i], 'yui-tag')) {
3513 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3514 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3517 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3524 if (path[i].getAttribute('href', 2)) {
3525 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3529 var h = path[i].height;
3530 var w = path[i].width;
3531 if (path[i].style.height) {
3532 h = parseInt(path[i].style.height, 10);
3534 if (path[i].style.width) {
3535 w = parseInt(path[i].style.width, 10);
3537 pathStr += '(' + w + 'x' + h + ')';
3541 if (pathStr.length > 10) {
3542 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3544 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3546 pathArr[pathArr.length] = pathStr;
3549 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3550 //Prevent flickering
3551 if (this.dompath.innerHTML != str) {
3552 this.dompath.innerHTML = str;
3558 * @description Fix href and imgs as well as remove invalid HTML.
3560 _fixNodes: function() {
3562 var doc = this._getDoc(),
3565 for (var v in this.invalidHTML) {
3566 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3567 if (v.toLowerCase() != 'span') {
3568 var tags = doc.body.getElementsByTagName(v);
3570 for (var i = 0; i < tags.length; i++) {
3577 for (var h = 0; h < els.length; h++) {
3578 if (els[h].parentNode) {
3579 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3580 this._swapEl(els[h], 'span', function(el) {
3581 el.className = 'yui-non';
3584 els[h].parentNode.removeChild(els[h]);
3588 var imgs = this._getDoc().getElementsByTagName('img');
3589 Dom.addClass(imgs, 'yui-img');
3594 * @method _isNonEditable
3595 * @param Event ev The Dom event being checked
3596 * @description Method is called at the beginning of all event handlers to check if this element or a parent element has the class yui-noedit (this.CLASS_NOEDIT) applied.
3597 * If it does, then this method will stop the event and return true. The event handlers will then return false and stop the nodeChange from occuring. This method will also
3598 * disable and enable the Editor's toolbar based on the noedit state.
3601 _isNonEditable: function(ev) {
3602 if (this.get('allowNoEdit')) {
3603 var el = Event.getTarget(ev);
3604 if (this._isElement(el, 'html')) {
3607 var path = this._getDomPath(el);
3608 for (var i = (path.length - 1); i > -1; i--) {
3609 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3610 //if (this.toolbar.get('disabled') === false) {
3611 // this.toolbar.set('disabled', true);
3614 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3617 Event.stopEvent(ev);
3618 YAHOO.log('CLASS_NOEDIT found in DOM Path, stopping event', 'info', 'SimpleEditor');
3622 //if (this.toolbar.get('disabled') === true) {
3623 //Should only happen once..
3624 //this.toolbar.set('disabled', false);
3626 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3634 * @method _setCurrentEvent
3635 * @param {Event} ev The event to cache
3636 * @description Sets the current event property
3638 _setCurrentEvent: function(ev) {
3639 this.currentEvent = ev;
3643 * @method _handleClick
3644 * @param {Event} ev The event we are working on.
3645 * @description Handles all click events inside the iFrame document.
3647 _handleClick: function(ev) {
3648 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3649 if (ret === false) {
3652 if (this._isNonEditable(ev)) {
3655 this._setCurrentEvent(ev);
3656 if (this.currentWindow) {
3659 if (this.currentWindow) {
3662 if (this.browser.webkit) {
3663 var tar =Event.getTarget(ev);
3664 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3665 Event.stopEvent(ev);
3671 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3675 * @method _handleMouseUp
3676 * @param {Event} ev The event we are working on.
3677 * @description Handles all mouseup events inside the iFrame document.
3679 _handleMouseUp: function(ev) {
3680 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3681 if (ret === false) {
3684 if (this._isNonEditable(ev)) {
3687 //Don't set current event for mouseup.
3688 //It get's fired after a menu is closed and gives up a bogus event to work with
3689 //this._setCurrentEvent(ev);
3691 if (this.browser.opera) {
3693 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3695 * @description This work around traps the MouseUp event and sets a timer to check if another MouseUp event fires in so many seconds. If another event is fired, they we internally fire the DoubleClick event.
3697 var sel = Event.getTarget(ev);
3698 if (this._isElement(sel, 'img')) {
3700 if (this.operaEvent) {
3701 clearTimeout(this.operaEvent);
3702 this.operaEvent = null;
3703 this._handleDoubleClick(ev);
3705 this.operaEvent = window.setTimeout(function() {
3706 self.operaEvent = false;
3711 //This will stop Safari from selecting the entire document if you select all the text in the editor
3712 if (this.browser.webkit || this.browser.opera) {
3713 if (this.browser.webkit) {
3714 Event.stopEvent(ev);
3718 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3722 * @method _handleMouseDown
3723 * @param {Event} ev The event we are working on.
3724 * @description Handles all mousedown events inside the iFrame document.
3726 _handleMouseDown: function(ev) {
3727 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3728 if (ret === false) {
3731 if (this._isNonEditable(ev)) {
3734 this._setCurrentEvent(ev);
3735 var sel = Event.getTarget(ev);
3736 if (this.browser.webkit && this._hasSelection()) {
3737 var _sel = this._getSelection();
3738 if (!this.browser.webkit3) {
3739 _sel.collapse(true);
3741 _sel.collapseToStart();
3744 if (this.browser.webkit && this._lastImage) {
3745 Dom.removeClass(this._lastImage, 'selected');
3746 this._lastImage = null;
3748 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3749 if (this.browser.webkit) {
3750 Event.stopEvent(ev);
3751 if (this._isElement(sel, 'img')) {
3752 Dom.addClass(sel, 'selected');
3753 this._lastImage = sel;
3756 if (this.currentWindow) {
3761 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3765 * @method _handleDoubleClick
3766 * @param {Event} ev The event we are working on.
3767 * @description Handles all doubleclick events inside the iFrame document.
3769 _handleDoubleClick: function(ev) {
3770 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3771 if (ret === false) {
3774 if (this._isNonEditable(ev)) {
3777 this._setCurrentEvent(ev);
3778 var sel = Event.getTarget(ev);
3779 if (this._isElement(sel, 'img')) {
3780 this.currentElement[0] = sel;
3781 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3782 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3783 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3784 this.currentElement[0] = this._hasParent(sel, 'a');
3785 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3786 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3789 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3793 * @method _handleKeyUp
3794 * @param {Event} ev The event we are working on.
3795 * @description Handles all keyup events inside the iFrame document.
3797 _handleKeyUp: function(ev) {
3798 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3799 if (ret === false) {
3802 if (this._isNonEditable(ev)) {
3806 this._setCurrentEvent(ev);
3807 switch (ev.keyCode) {
3808 case this._keyMap.SELECT_ALL.key:
3809 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3813 case 32: //Space Bar
3816 case 37: //Left Arrow
3818 case 39: //Right Arrow
3819 case 40: //Down Arrow
3820 case 46: //Forward Delete
3822 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3823 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3824 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3828 if (!this.browser.ie) {
3829 if (this._nodeChangeTimer) {
3830 clearTimeout(this._nodeChangeTimer);
3833 this._nodeChangeTimer = setTimeout(function() {
3834 self._nodeChangeTimer = null;
3835 self.nodeChange.call(self);
3840 this.editorDirty = true;
3844 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3848 * @method _handleKeyPress
3849 * @param {Event} ev The event we are working on.
3850 * @description Handles all keypress events inside the iFrame document.
3852 _handleKeyPress: function(ev) {
3853 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3854 if (ret === false) {
3858 if (this.get('allowNoEdit')) {
3859 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3860 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3861 //Forward delete key
3862 YAHOO.log('allowNoEdit is set, forward delete key has been disabled', 'warn', 'SimpleEditor');
3863 Event.stopEvent(ev);
3866 if (this._isNonEditable(ev)) {
3869 this._setCurrentEvent(ev);
3871 if (this.browser.opera) {
3872 if (ev.keyCode === 13) {
3873 var tar = this._getSelectedElement();
3874 if (!this._isElement(tar, 'li')) {
3875 this.execCommand('inserthtml', '<br>');
3876 Event.stopEvent(ev);
3880 if (this.browser.webkit) {
3881 if (!this.browser.webkit3) {
3882 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3883 //This is CMD + z (for undo)
3884 if (this._hasParent(this._getSelectedElement(), 'li')) {
3885 YAHOO.log('We are in an LI and we found CMD + z, stopping the event', 'warn', 'SimpleEditor');
3886 Event.stopEvent(ev);
3892 this._fixListDupIds();
3893 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3897 * @method _handleKeyDown
3898 * @param {Event} ev The event we are working on.
3899 * @description Handles all keydown events inside the iFrame document.
3901 _handleKeyDown: function(ev) {
3902 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3903 if (ret === false) {
3906 var tar = null, _range = null;
3907 if (this._isNonEditable(ev)) {
3910 this._setCurrentEvent(ev);
3911 if (this.currentWindow) {
3914 if (this.currentWindow) {
3922 //YAHOO.log('keyCode: ' + ev.keyCode, 'info', 'SimpleEditor');
3924 switch (ev.keyCode) {
3925 case this._keyMap.FOCUS_TOOLBAR.key:
3926 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3927 var h = this.toolbar.getElementsByTagName('h2')[0];
3928 if (h && h.firstChild) {
3929 h.firstChild.focus();
3931 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3932 //Focus After Element - Esc
3933 this.afterElement.focus();
3935 Event.stopEvent(ev);
3939 case this._keyMap.CREATE_LINK.key: //L
3940 if (this._hasSelection()) {
3941 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3942 var makeLink = true;
3943 if (this.get('limitCommands')) {
3944 if (!this.toolbar.getButtonByValue('createlink')) {
3945 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
3950 this.execCommand('createlink', '');
3951 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3952 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3959 case this._keyMap.UNDO.key:
3960 case this._keyMap.REDO.key:
3961 if (this._checkKey(this._keyMap.REDO, ev)) {
3964 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3970 case this._keyMap.BOLD.key:
3971 if (this._checkKey(this._keyMap.BOLD, ev)) {
3976 case this._keyMap.FONT_SIZE_UP.key:
3977 case this._keyMap.FONT_SIZE_DOWN.key:
3978 var uk = false, dk = false;
3979 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3982 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3986 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3987 label = parseInt(fs_button.get('label'), 10),
3988 newValue = (label + 1);
3991 newValue = (label - 1);
3994 action = 'fontsize';
3995 value = newValue + 'px';
4000 case this._keyMap.ITALIC.key:
4001 if (this._checkKey(this._keyMap.ITALIC, ev)) {
4007 case this._keyMap.UNDERLINE.key:
4008 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
4009 action = 'underline';
4014 if (this.browser.ie) {
4015 //Insert a tab in Internet Explorer
4016 _range = this._getRange();
4017 tar = this._getSelectedElement();
4018 if (!this._isElement(tar, 'li')) {
4020 _range.pasteHTML(' ');
4021 _range.collapse(false);
4024 Event.stopEvent(ev);
4028 if (this.browser.gecko > 1.8) {
4029 tar = this._getSelectedElement();
4030 if (this._isElement(tar, 'li')) {
4032 this._getDoc().execCommand('outdent', null, '');
4034 this._getDoc().execCommand('indent', null, '');
4037 } else if (!this._hasSelection()) {
4038 this.execCommand('inserthtml', ' ');
4040 Event.stopEvent(ev);
4044 var p = null, i = 0;
4045 if (this.get('ptags') && !ev.shiftKey) {
4046 if (this.browser.gecko) {
4047 tar = this._getSelectedElement();
4048 if (!this._hasParent(tar, 'li')) {
4049 if (this._hasParent(tar, 'p')) {
4050 p = this._getDoc().createElement('p');
4051 p.innerHTML = ' ';
4052 Dom.insertAfter(p, tar);
4053 this._selectNode(p.firstChild);
4054 } else if (this._isElement(tar, 'body')) {
4055 this.execCommand('insertparagraph', null);
4056 var ps = this._getDoc().body.getElementsByTagName('p');
4057 for (i = 0; i < ps.length; i++) {
4058 if (ps[i].getAttribute('_moz_dirty') !== null) {
4059 p = this._getDoc().createElement('p');
4060 p.innerHTML = ' ';
4061 Dom.insertAfter(p, ps[i]);
4062 this._selectNode(p.firstChild);
4063 ps[i].removeAttribute('_moz_dirty');
4067 YAHOO.log('Something went wrong with paragraphs, please file a bug!!', 'error', 'SimpleEditor');
4069 action = 'insertparagraph';
4071 Event.stopEvent(ev);
4074 if (this.browser.webkit) {
4075 tar = this._getSelectedElement();
4076 if (!this._hasParent(tar, 'li')) {
4077 this.execCommand('insertparagraph', null);
4078 var divs = this._getDoc().body.getElementsByTagName('div');
4079 for (i = 0; i < divs.length; i++) {
4080 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4081 Dom.addClass(divs[i], 'yui-wk-p');
4084 Event.stopEvent(ev);
4088 if (this.browser.webkit) {
4089 tar = this._getSelectedElement();
4090 if (!this._hasParent(tar, 'li')) {
4091 if (this.browser.webkit4) {
4092 this.execCommand('insertlinebreak');
4094 this.execCommand('inserthtml', '<var id="yui-br"></var>');
4095 var holder = this._getDoc().getElementById('yui-br'),
4096 br = this._getDoc().createElement('br'),
4097 caret = this._getDoc().createElement('span');
4099 holder.parentNode.replaceChild(br, holder);
4100 caret.className = 'yui-non';
4101 caret.innerHTML = ' ';
4102 Dom.insertAfter(caret, br);
4103 this._selectNode(caret);
4105 Event.stopEvent(ev);
4108 if (this.browser.ie) {
4109 YAHOO.log('Stopping P tags', 'info', 'SimpleEditor');
4110 //Insert a <br> instead of a <p></p> in Internet Explorer
4111 _range = this._getRange();
4112 tar = this._getSelectedElement();
4113 if (!this._isElement(tar, 'li')) {
4115 _range.pasteHTML('<br>');
4116 _range.collapse(false);
4119 Event.stopEvent(ev);
4125 if (this.browser.ie) {
4128 if (doExec && action) {
4129 this.execCommand(action, value);
4130 Event.stopEvent(ev);
4134 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4138 * @property _fixListRunning
4140 * @description Keeps more than one _fixListDupIds from running at the same time.
4142 _fixListRunning: null,
4145 * @method _fixListDupIds
4146 * @description Some browsers will duplicate the id of an LI when created in designMode.
4147 * This method will fix the duplicate id issue. However it will only preserve the first element
4148 * in the document list with the unique id.
4150 _fixListDupIds: function() {
4151 if (this._fixListRunning) {
4154 if (this._getDoc()) {
4155 this._fixListRunning = true;
4156 var lis = this._getDoc().body.getElementsByTagName('li'),
4158 for (i = 0; i < lis.length; i++) {
4160 if (ids[lis[i].id]) {
4163 ids[lis[i].id] = true;
4166 this._fixListRunning = false;
4172 * @param {Event} ev The event we are working on.
4173 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4175 _listFix: function(ev) {
4176 //YAHOO.log('Lists Fix (' + ev.keyCode + ')', 'info', 'SimpleEditor');
4177 var testLi = null, par = null, preContent = false, range = null;
4179 if (this.browser.webkit) {
4180 if (ev.keyCode && (ev.keyCode == 13)) {
4181 if (this._hasParent(this._getSelectedElement(), 'li')) {
4182 var tar = this._hasParent(this._getSelectedElement(), 'li');
4183 if (tar.previousSibling) {
4184 if (tar.firstChild && (tar.firstChild.length == 1)) {
4185 this._selectNode(tar);
4192 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4193 testLi = this._getSelectedElement();
4194 if (this._hasParent(testLi, 'li')) {
4195 testLi = this._hasParent(testLi, 'li');
4196 YAHOO.log('We have a SHIFT tab in an LI, reverse it..', 'info', 'SimpleEditor');
4197 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4198 YAHOO.log('We have a double parent, move up a level', 'info', 'SimpleEditor');
4199 par = this._hasParent(testLi, 'ul');
4201 par = this._hasParent(testLi, 'ol');
4203 //YAHOO.log(par.previousSibling + ' :: ' + par.previousSibling.innerHTML);
4204 if (this._isElement(par.previousSibling, 'li')) {
4205 par.removeChild(testLi);
4206 par.parentNode.insertBefore(testLi, par.nextSibling);
4207 if (this.browser.ie) {
4208 range = this._getDoc().body.createTextRange();
4209 range.moveToElementText(testLi);
4210 range.collapse(false);
4213 if (this.browser.webkit) {
4214 this._selectNode(testLi.firstChild);
4216 Event.stopEvent(ev);
4222 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4223 YAHOO.log('List Fix - Tab', 'info', 'SimpleEditor');
4224 var preLi = this._getSelectedElement();
4225 if (this._hasParent(preLi, 'li')) {
4226 preContent = this._hasParent(preLi, 'li').innerHTML;
4228 //YAHOO.log('preLI: ' + preLi.tagName + ' :: ' + preLi.innerHTML);
4229 if (this.browser.webkit) {
4230 this._getDoc().execCommand('inserttext', false, '\t');
4232 testLi = this._getSelectedElement();
4233 if (this._hasParent(testLi, 'li')) {
4234 YAHOO.log('We have a tab in an LI', 'info', 'SimpleEditor');
4235 par = this._hasParent(testLi, 'li');
4236 YAHOO.log('parLI: ' + par.tagName + ' :: ' + par.innerHTML);
4237 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4238 if (this.browser.webkit) {
4239 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4240 //Remove the span element that Safari puts in
4242 par.removeChild(span[0]);
4243 par.innerHTML = Lang.trim(par.innerHTML);
4244 //Put the HTML from the LI into this new LI
4246 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4248 par.innerHTML = '<span class="yui-non"> </span> ';
4253 par.innerHTML = preContent + ' ';
4255 par.innerHTML = ' ';
4259 par.parentNode.replaceChild(newUl, par);
4260 newUl.appendChild(par);
4261 if (this.browser.webkit) {
4262 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4263 if (!this.browser.webkit3) {
4264 par.parentNode.parentNode.style.display = 'list-item';
4265 setTimeout(function() {
4266 par.parentNode.parentNode.style.display = 'block';
4269 } else if (this.browser.ie) {
4270 range = this._getDoc().body.createTextRange();
4271 range.moveToElementText(par);
4272 range.collapse(false);
4275 this._selectNode(par);
4277 Event.stopEvent(ev);
4279 if (this.browser.webkit) {
4280 Event.stopEvent(ev);
4286 * @method nodeChange
4287 * @param {Boolean} force Optional paramenter to skip the threshold counter
4288 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4290 nodeChange: function(force) {
4293 if (this.get('nodeChangeDelay')) {
4294 this._nodeChangeDelayTimer = window.setTimeout(function() {
4295 NCself._nodeChangeDelayTimer = null;
4296 NCself._nodeChange.apply(NCself, arguments);
4304 * @method _nodeChange
4305 * @param {Boolean} force Optional paramenter to skip the threshold counter
4306 * @description Fired from nodeChange in a setTimeout.
4308 _nodeChange: function(force) {
4309 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4310 thisNodeChange = Math.round(new Date().getTime() / 1000),
4313 if (force === true) {
4314 this._lastNodeChange = 0;
4317 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4318 if (this._fixNodesTimer === null) {
4319 this._fixNodesTimer = window.setTimeout(function() {
4320 self._fixNodes.call(self);
4321 self._fixNodesTimer = null;
4325 this._lastNodeChange = thisNodeChange;
4326 if (this.currentEvent) {
4328 this._lastNodeChangeEvent = this.currentEvent.type;
4332 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4333 if (beforeNodeChange === false) {
4336 if (this.get('dompath')) {
4337 window.setTimeout(function() {
4338 self._writeDomPath.call(self);
4341 //Check to see if we are disabled before continuing
4342 if (!this.get('disabled')) {
4343 if (this.STOP_NODE_CHANGE) {
4344 //Reset this var for next action
4345 this.STOP_NODE_CHANGE = false;
4348 var sel = this._getSelection(),
4349 range = this._getRange(),
4350 el = this._getSelectedElement(),
4351 fn_button = this.toolbar.getButtonByValue('fontname'),
4352 fs_button = this.toolbar.getButtonByValue('fontsize'),
4353 undo_button = this.toolbar.getButtonByValue('undo'),
4354 redo_button = this.toolbar.getButtonByValue('redo');
4356 //Handle updating the toolbar with active buttons
4358 if (this._lastButton) {
4359 _ex[this._lastButton.id] = true;
4360 //this._lastButton = null;
4362 if (!this._isElement(el, 'body')) {
4364 _ex[fn_button.get('id')] = true;
4367 _ex[fs_button.get('id')] = true;
4371 delete _ex[redo_button.get('id')];
4373 this.toolbar.resetAllButtons(_ex);
4375 //Handle disabled buttons
4376 for (var d = 0; d < this._disabled.length; d++) {
4377 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4378 if (_button && _button.get) {
4379 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4382 if (!this._hasSelection() && !this.get('insert')) {
4383 switch (this._disabled[d]) {
4388 //No Selection - disable
4389 this.toolbar.disableButton(_button);
4392 if (!this._alwaysDisabled[this._disabled[d]]) {
4393 this.toolbar.enableButton(_button);
4396 if (!this._alwaysEnabled[this._disabled[d]]) {
4397 this.toolbar.deselectButton(_button);
4402 var path = this._getDomPath();
4403 var tag = null, cmd = null;
4404 for (var i = 0; i < path.length; i++) {
4405 tag = path[i].tagName.toLowerCase();
4406 if (path[i].getAttribute('tag')) {
4407 tag = path[i].getAttribute('tag').toLowerCase();
4409 cmd = this._tag2cmd[tag];
4410 if (cmd === undefined) {
4413 if (!Lang.isArray(cmd)) {
4417 //Bold and Italic styles
4418 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4419 cmd[cmd.length] = 'bold';
4421 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4422 cmd[cmd.length] = 'italic';
4424 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4425 cmd[cmd.length] = 'underline';
4427 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4428 cmd[cmd.length] = 'strikethrough';
4430 if (cmd.length > 0) {
4431 for (var j = 0; j < cmd.length; j++) {
4432 this.toolbar.selectButton(cmd[j]);
4433 this.toolbar.enableButton(cmd[j]);
4437 switch (path[i].style.textAlign.toLowerCase()) {
4442 var alignType = path[i].style.textAlign.toLowerCase();
4443 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4446 this.toolbar.selectButton('justify' + alignType);
4447 this.toolbar.enableButton('justify' + alignType);
4453 //Reset Font Family and Size to the inital configs
4455 var family = fn_button._configs.label._initialConfig.value;
4456 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4457 this._updateMenuChecked('fontname', family);
4461 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4464 var hd_button = this.toolbar.getButtonByValue('heading');
4466 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4467 this._updateMenuChecked('heading', 'none');
4469 var img_button = this.toolbar.getButtonByValue('insertimage');
4470 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4471 this.toolbar.disableButton(img_button);
4473 if (this._lastButton && this._lastButton.isSelected) {
4474 this.toolbar.deselectButton(this._lastButton.id);
4476 this._undoNodeChange();
4480 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4484 * @method _updateMenuChecked
4485 * @param {Object} button The command identifier of the button you want to check
4486 * @param {String} value The value of the menu item you want to check
4487 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4488 * @description Gets the menu from a button instance, if the menu is not rendered it will render it. It will then search the menu for the specified value, unchecking all other items and checking the specified on.
4490 _updateMenuChecked: function(button, value, tbar) {
4492 tbar = this.toolbar;
4494 var _button = tbar.getButtonByValue(button);
4495 _button.checkValue(value);
4499 * @method _handleToolbarClick
4500 * @param {Event} ev The event that triggered the button click
4501 * @description This is an event handler attached to the Toolbar's buttonClick event. It will fire execCommand with the command identifier from the Toolbar Button.
4503 _handleToolbarClick: function(ev) {
4506 var cmd = ev.button.value;
4507 if (ev.button.menucmd) {
4509 cmd = ev.button.menucmd;
4511 this._lastButton = ev.button;
4512 if (this.STOP_EXEC_COMMAND) {
4513 YAHOO.log('execCommand skipped because we found the STOP_EXEC_COMMAND flag set to true', 'warn', 'SimpleEditor');
4514 YAHOO.log('NOEXEC::execCommand::(' + cmd + '), (' + value + ')', 'warn', 'SimpleEditor');
4515 this.STOP_EXEC_COMMAND = false;
4518 this.execCommand(cmd, value);
4519 if (!this.browser.webkit) {
4521 setTimeout(function() {
4522 Fself.focus.call(Fself);
4526 Event.stopEvent(ev);
4530 * @method _setupAfterElement
4531 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4533 _setupAfterElement: function() {
4534 if (!this.beforeElement) {
4535 this.beforeElement = document.createElement('h2');
4536 this.beforeElement.className = 'yui-editor-skipheader';
4537 this.beforeElement.tabIndex = '-1';
4538 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4539 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4541 if (!this.afterElement) {
4542 this.afterElement = document.createElement('h2');
4543 this.afterElement.className = 'yui-editor-skipheader';
4544 this.afterElement.tabIndex = '-1';
4545 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4546 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4551 * @method _disableEditor
4552 * @param {Boolean} disabled Pass true to disable, false to enable
4553 * @description Creates a mask to place over the Editor.
4555 _disableEditor: function(disabled) {
4556 var iframe, par, html, height;
4557 if (!this.get('disabled_iframe')) {
4558 iframe = this._createIframe();
4559 iframe.set('id', 'disabled_' + this.get('iframe').get('id'));
4560 iframe.setStyle('height', '100%');
4561 iframe.setStyle('display', 'none');
4562 iframe.setStyle('visibility', 'visible');
4563 this.set('disabled_iframe', iframe);
4564 par = this.get('iframe').get('parentNode');
4565 par.appendChild(iframe.get('element'));
4568 iframe = this.get('disabled_iframe');
4571 this._orgIframe = this.get('iframe');
4574 this.toolbar.set('disabled', true);
4577 html = this.getEditorHTML();
4578 height = this.get('iframe').get('offsetHeight');
4579 iframe.setStyle('visibility', '');
4580 iframe.setStyle('position', '');
4581 iframe.setStyle('top', '');
4582 iframe.setStyle('left', '');
4583 this._orgIframe.setStyle('visibility', 'hidden');
4584 this._orgIframe.setStyle('position', 'absolute');
4585 this._orgIframe.setStyle('top', '-99999px');
4586 this._orgIframe.setStyle('left', '-99999px');
4587 this.set('iframe', iframe);
4588 this._setInitialContent(true);
4591 this._mask = document.createElement('DIV');
4592 Dom.addClass(this._mask, 'yui-editor-masked');
4593 if (this.browser.ie) {
4594 this._mask.style.height = height + 'px';
4596 this.get('iframe').get('parentNode').appendChild(this._mask);
4598 this.on('editorContentReloaded', function() {
4599 this._getDoc().body._rteLoaded = false;
4600 this.setEditorHTML(html);
4601 iframe.setStyle('display', 'block');
4602 this.unsubscribeAll('editorContentReloaded');
4606 this._mask.parentNode.removeChild(this._mask);
4609 this.toolbar.set('disabled', false);
4611 iframe.setStyle('visibility', 'hidden');
4612 iframe.setStyle('position', 'absolute');
4613 iframe.setStyle('top', '-99999px');
4614 iframe.setStyle('left', '-99999px');
4615 this._orgIframe.setStyle('visibility', '');
4616 this._orgIframe.setStyle('position', '');
4617 this._orgIframe.setStyle('top', '');
4618 this._orgIframe.setStyle('left', '');
4619 this.set('iframe', this._orgIframe);
4623 window.setTimeout(function() {
4624 self.nodeChange.call(self);
4630 * @property SEP_DOMPATH
4631 * @description The value to place in between the Dom path items
4636 * @property STR_LEAVE_EDITOR
4637 * @description The accessibility string for the element after the iFrame
4640 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4642 * @property STR_BEFORE_EDITOR
4643 * @description The accessibility string for the element before the iFrame
4646 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Shift + Escape to place focus on the toolbar and navigate between options with your arrow keys. To exit this text editor use the Escape key and continue tabbing. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift L adds an HTML link</li></ul>',
4648 * @property STR_TITLE
4649 * @description The Title of the HTML document that is created in the iFrame
4652 STR_TITLE: 'Rich Text Area.',
4654 * @property STR_IMAGE_HERE
4655 * @description The text to place in the URL textbox when using the blankimage.
4658 STR_IMAGE_HERE: 'Image URL Here',
4660 * @property STR_IMAGE_URL
4661 * @description The label string for Image URL
4664 STR_IMAGE_URL: 'Image URL',
4666 * @property STR_LINK_URL
4667 * @description The label string for the Link URL.
4670 STR_LINK_URL: 'Link URL',
4673 * @property STOP_EXEC_COMMAND
4674 * @description Set to true when you want the default execCommand function to not process anything
4677 STOP_EXEC_COMMAND: false,
4680 * @property STOP_NODE_CHANGE
4681 * @description Set to true when you want the default nodeChange function to not process anything
4684 STOP_NODE_CHANGE: false,
4687 * @property CLASS_NOEDIT
4688 * @description CSS class applied to elements that are not editable.
4691 CLASS_NOEDIT: 'yui-noedit',
4694 * @property CLASS_CONTAINER
4695 * @description Default CSS class to apply to the editors container element
4698 CLASS_CONTAINER: 'yui-editor-container',
4701 * @property CLASS_EDITABLE
4702 * @description Default CSS class to apply to the editors iframe element
4705 CLASS_EDITABLE: 'yui-editor-editable',
4708 * @property CLASS_EDITABLE_CONT
4709 * @description Default CSS class to apply to the editors iframe's parent element
4712 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4715 * @property CLASS_PREFIX
4716 * @description Default prefix for dynamically created class names
4719 CLASS_PREFIX: 'yui-editor',
4722 * @description Standard browser detection
4725 browser: function() {
4726 var br = YAHOO.env.ua;
4728 if (br.webkit >= 420) {
4729 br.webkit3 = br.webkit;
4733 if (br.webkit >= 530) {
4734 br.webkit4 = br.webkit;
4740 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4748 * @description The Editor class' initialization method
4750 init: function(p_oElement, p_oAttributes) {
4751 YAHOO.log('init', 'info', 'SimpleEditor');
4753 if (!this._defaultToolbar) {
4754 this._defaultToolbar = {
4756 titlebar: 'Text Editing Tools',
4759 { group: 'fontstyle', label: 'Font Name and Size',
4761 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4763 { text: 'Arial', checked: true },
4764 { text: 'Arial Black' },
4765 { text: 'Comic Sans MS' },
4766 { text: 'Courier New' },
4767 { text: 'Lucida Console' },
4769 { text: 'Times New Roman' },
4770 { text: 'Trebuchet MS' },
4774 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4777 { type: 'separator' },
4778 { group: 'textstyle', label: 'Font Style',
4780 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4781 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4782 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4783 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4784 { type: 'separator' },
4785 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4786 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4790 { type: 'separator' },
4791 { group: 'indentlist', label: 'Lists',
4793 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4794 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4797 { type: 'separator' },
4798 { group: 'insertitem', label: 'Insert Item',
4800 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4801 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4808 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4809 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4812 this.currentElement = [];
4813 this.on('contentReady', function() {
4814 this.DOMReady = true;
4820 * @method initAttributes
4821 * @description Initializes all of the configuration attributes used to create
4823 * @param {Object} attr Object literal specifying a set of
4824 * configuration attributes used to create the editor.
4826 initAttributes: function(attr) {
4827 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4831 * @config setDesignMode
4832 * @description Should the Editor set designMode on the document. Default: true.
4836 this.setAttributeConfig('setDesignMode', {
4837 value: ((attr.setDesignMode === false) ? false : true)
4840 * @config nodeChangeDelay
4841 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true.
4845 this.setAttributeConfig('nodeChangeDelay', {
4846 value: ((attr.nodeChangeDelay === false) ? false : true)
4850 * @description The max number of undo levels to store.
4854 this.setAttributeConfig('maxUndo', {
4856 value: attr.maxUndo || 30
4861 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4865 this.setAttributeConfig('ptags', {
4867 value: attr.ptags || false
4871 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4875 this.setAttributeConfig('insert', {
4877 value: attr.insert || false,
4878 method: function(insert) {
4886 var tmp = this._defaultToolbar.buttons;
4887 for (var i = 0; i < tmp.length; i++) {
4888 if (tmp[i].buttons) {
4889 for (var a = 0; a < tmp[i].buttons.length; a++) {
4890 if (tmp[i].buttons[a].value) {
4891 if (buttons[tmp[i].buttons[a].value]) {
4892 delete tmp[i].buttons[a].disabled;
4903 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4904 * We will create one and place it in this container. If no container is passed we will append to document.body.
4908 this.setAttributeConfig('container', {
4910 value: attr.container || false
4914 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4918 this.setAttributeConfig('plainText', {
4920 value: attr.plainText || false
4925 * @description Internal config for holding the iframe element.
4929 this.setAttributeConfig('iframe', {
4934 * @config disabled_iframe
4935 * @description Internal config for holding the iframe element used when disabling the Editor.
4939 this.setAttributeConfig('disabled_iframe', {
4944 * @depreciated - No longer used, should use this.get('element')
4946 * @description Internal config for holding the textarea element (replaced with element).
4950 this.setAttributeConfig('textarea', {
4955 * @config nodeChangeThreshold
4956 * @description The number of seconds that need to be in between nodeChange processing
4960 this.setAttributeConfig('nodeChangeThreshold', {
4961 value: attr.nodeChangeThreshold || 3,
4962 validator: YAHOO.lang.isNumber
4965 * @config allowNoEdit
4966 * @description Should the editor check for non-edit fields. It should be noted that this technique is not perfect. If the user does the right things, they will still be able to make changes.
4967 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4971 this.setAttributeConfig('allowNoEdit', {
4972 value: attr.allowNoEdit || false,
4973 validator: YAHOO.lang.isBoolean
4976 * @config limitCommands
4977 * @description Should the Editor limit the allowed execCommands to the ones available in the toolbar. If true, then execCommand and keyboard shortcuts will fail if they are not defined in the toolbar.
4981 this.setAttributeConfig('limitCommands', {
4982 value: attr.limitCommands || false,
4983 validator: YAHOO.lang.isBoolean
4986 * @config element_cont
4987 * @description Internal config for the editors container
4991 this.setAttributeConfig('element_cont', {
4992 value: attr.element_cont
4996 * @config editor_wrapper
4997 * @description The outter wrapper for the entire editor.
5001 this.setAttributeConfig('editor_wrapper', {
5002 value: attr.editor_wrapper || null,
5007 * @description The height of the editor iframe container, not including the toolbar..
5008 * @default Best guessed size of the textarea, for best results use CSS to style the height of the textarea or pass it in as an argument
5011 this.setAttributeConfig('height', {
5012 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
5013 method: function(height) {
5014 if (this._rendered) {
5015 //We have been rendered, change the height
5016 if (this.get('animate')) {
5017 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
5019 to: parseInt(height, 10)
5024 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
5030 * @config autoHeight
5031 * @description Remove the scrollbars from the edit area and resize it to fit the content. It will not go any lower than the current config height.
5033 * @type Boolean || Number
5035 this.setAttributeConfig('autoHeight', {
5036 value: attr.autoHeight || false,
5037 method: function(a) {
5039 if (this.get('iframe')) {
5040 this.get('iframe').get('element').setAttribute('scrolling', 'no');
5042 this.on('afterNodeChange', this._handleAutoHeight, this, true);
5043 this.on('editorKeyDown', this._handleAutoHeight, this, true);
5044 this.on('editorKeyPress', this._handleAutoHeight, this, true);
5046 if (this.get('iframe')) {
5047 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
5049 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
5050 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
5051 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
5057 * @description The width of the editor container.
5058 * @default Best guessed size of the textarea, for best results use CSS to style the width of the textarea or pass it in as an argument
5061 this.setAttributeConfig('width', {
5062 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
5063 method: function(width) {
5064 if (this._rendered) {
5065 //We have been rendered, change the width
5066 if (this.get('animate')) {
5067 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
5069 to: parseInt(width, 10)
5074 this.get('element_cont').setStyle('width', width);
5081 * @attribute blankimage
5082 * @description The URL for the image placeholder to put in when inserting an image.
5083 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
5086 this.setAttributeConfig('blankimage', {
5087 value: attr.blankimage || this._getBlankImage()
5091 * @description The Base CSS used to format the content of the editor
5092 * @default <code><pre>html {
5097 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
5101 text-decoration: underline;
5104 .warning-localfile {
5105 border-bottom: 1px dashed red !important;
5108 cursor: wait !important;
5110 img.selected { //Safari image selection
5111 border: 2px dotted #808080;
5114 cursor: pointer !important;
5120 this.setAttributeConfig('css', {
5121 value: attr.css || this._defaultCSS,
5126 * @description The default HTML to be written to the iframe document before the contents are loaded (Note that the DOCTYPE attr will be added at render item)
5127 * @default This HTML requires a few things if you are to override:
5128 <p><code>{TITLE}, {CSS}, {HIDDEN_CSS}, {EXTRA_CSS}</code> and <code>{CONTENT}</code> need to be there, they are passed to YAHOO.lang.substitute to be replace with other strings.<p>
5129 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
5134 <title>{TITLE}</title>
5135 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5146 <body onload="document.body._rteLoaded = true;">
5154 this.setAttributeConfig('html', {
5155 value: attr.html || '<html><head><title>{TITLE}</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><base href="' + this._baseHREF + '"><style>{CSS}</style><style>{HIDDEN_CSS}</style><style>{EXTRA_CSS}</style></head><body onload="document.body._rteLoaded = true;">{CONTENT}</body></html>',
5160 * @attribute extracss
5161 * @description Extra user defined css to load after the default SimpleEditor CSS
5165 this.setAttributeConfig('extracss', {
5166 value: attr.extracss || '',
5171 * @attribute handleSubmit
5172 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
5173 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
5174 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
5178 this.setAttributeConfig('handleSubmit', {
5179 value: attr.handleSubmit || false,
5180 method: function(exec) {
5181 if (this.get('element').form) {
5182 if (!this._formButtons) {
5183 this._formButtons = [];
5186 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5187 var i = this.get('element').form.getElementsByTagName('input');
5188 for (var s = 0; s < i.length; s++) {
5189 var type = i[s].getAttribute('type');
5190 if (type && (type.toLowerCase() == 'submit')) {
5191 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5192 this._formButtons[this._formButtons.length] = i[s];
5196 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5197 if (this._formButtons) {
5198 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5205 * @attribute disabled
5206 * @description This will toggle the editor's disabled state. When the editor is disabled, designMode is turned off and a mask is placed over the iframe so no interaction can take place.
5207 All Toolbar buttons are also disabled so they cannot be used.
5212 this.setAttributeConfig('disabled', {
5214 method: function(disabled) {
5215 if (this._rendered) {
5216 this._disableEditor(disabled);
5222 * @description When save HTML is called, this element will be updated as well as the source of data.
5226 this.setAttributeConfig('saveEl', {
5227 value: this.get('element')
5230 * @config toolbar_cont
5231 * @description Internal config for the toolbars container
5235 this.setAttributeConfig('toolbar_cont', {
5240 * @attribute toolbar
5241 * @description The default toolbar config.
5244 this.setAttributeConfig('toolbar', {
5245 value: attr.toolbar || this._defaultToolbar,
5247 method: function(toolbar) {
5248 if (!toolbar.buttonType) {
5249 toolbar.buttonType = this._defaultToolbar.buttonType;
5251 this._defaultToolbar = toolbar;
5255 * @attribute animate
5256 * @description Should the editor animate window movements
5257 * @default false unless Animation is found, then true
5260 this.setAttributeConfig('animate', {
5261 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5262 validator: function(value) {
5264 if (!YAHOO.util.Anim) {
5272 * @description A reference to the panel we are using for windows.
5276 this.setAttributeConfig('panel', {
5279 validator: function(value) {
5281 if (!YAHOO.widget.Overlay) {
5288 * @attribute focusAtStart
5289 * @description Should we focus the window when the content is ready?
5293 this.setAttributeConfig('focusAtStart', {
5294 value: attr.focusAtStart || false,
5296 method: function(fs) {
5298 this.on('editorContentLoaded', function() {
5300 setTimeout(function() {
5301 self.focus.call(self);
5302 self.editorDirty = false;
5309 * @attribute dompath
5310 * @description Toggle the display of the current Dom path below the editor
5314 this.setAttributeConfig('dompath', {
5315 value: attr.dompath || false,
5316 method: function(dompath) {
5317 if (dompath && !this.dompath) {
5318 this.dompath = document.createElement('DIV');
5319 this.dompath.id = this.get('id') + '_dompath';
5320 Dom.addClass(this.dompath, 'dompath');
5321 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5322 if (this.get('iframe')) {
5323 this._writeDomPath();
5325 } else if (!dompath && this.dompath) {
5326 this.dompath.parentNode.removeChild(this.dompath);
5327 this.dompath = null;
5333 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5334 * @default "semantic"
5337 this.setAttributeConfig('markup', {
5338 value: attr.markup || 'semantic',
5339 validator: function(markup) {
5340 switch (markup.toLowerCase()) {
5351 * @attribute removeLineBreaks
5352 * @description Should we remove linebreaks and extra spaces on cleanup
5356 this.setAttributeConfig('removeLineBreaks', {
5357 value: attr.removeLineBreaks || false,
5358 validator: YAHOO.lang.isBoolean
5363 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5364 * @type {Boolean/String}
5366 this.setAttributeConfig('drag', {
5368 value: attr.drag || false
5373 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5374 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5377 this.setAttributeConfig('resize', {
5379 value: attr.resize || false
5383 * @config filterWord
5384 * @description Attempt to filter out MS Word HTML from the Editor's output.
5387 this.setAttributeConfig('filterWord', {
5388 value: attr.filterWord || false,
5389 validator: YAHOO.lang.isBoolean
5395 * @method _getBlankImage
5396 * @description Retrieves the full url of the image to use as the blank image.
5397 * @return {String} The URL to the blank image
5399 _getBlankImage: function() {
5400 if (!this.DOMReady) {
5401 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5405 if (!this._blankImageLoaded) {
5406 if (YAHOO.widget.EditorInfo.blankImage) {
5407 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5408 this._blankImageLoaded = true;
5410 var div = document.createElement('div');
5411 div.style.position = 'absolute';
5412 div.style.top = '-9999px';
5413 div.style.left = '-9999px';
5414 div.className = this.CLASS_PREFIX + '-blankimage';
5415 document.body.appendChild(div);
5416 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5417 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5419 img = img.replace('app:/', '');
5420 this.set('blankimage', img);
5421 this._blankImageLoaded = true;
5422 div.parentNode.removeChild(div);
5423 YAHOO.widget.EditorInfo.blankImage = img;
5426 img = this.get('blankimage');
5432 * @method _handleAutoHeight
5433 * @description Handles resizing the editor's height based on the content
5435 _handleAutoHeight: function() {
5436 var doc = this._getDoc(),
5438 docEl = doc.documentElement;
5440 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5441 var newHeight = body.scrollHeight;
5442 if (this.browser.webkit) {
5443 newHeight = docEl.scrollHeight;
5445 if (newHeight < parseInt(this.get('height'), 10)) {
5446 newHeight = parseInt(this.get('height'), 10);
5448 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5449 var anim = this.get('animate');
5450 this.set('animate', false);
5451 this.set('height', newHeight + 'px');
5452 this.set('animate', anim);
5453 if (this.browser.ie) {
5454 //Internet Explorer needs this
5455 this.get('iframe').setStyle('height', '99%');
5456 this.get('iframe').setStyle('zoom', '1');
5458 window.setTimeout(function() {
5459 self.get('iframe').setStyle('height', '100%');
5466 * @property _formButtons
5467 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5473 * @property _formButtonClicked
5474 * @description The form button that was clicked to submit the form.
5477 _formButtonClicked: null,
5480 * @method _handleFormButtonClick
5481 * @description The click listener assigned to each submit button in the Editor's parent form.
5482 * @param {Event} ev The click event
5484 _handleFormButtonClick: function(ev) {
5485 var tar = Event.getTarget(ev);
5486 this._formButtonClicked = tar;
5490 * @method _handleFormSubmit
5491 * @description Handles the form submission.
5492 * @param {Object} ev The Form Submit Event
5494 _handleFormSubmit: function(ev) {
5497 var form = this.get('element').form,
5498 tar = this._formButtonClicked || false;
5500 Event.removeListener(form, 'submit', this._handleFormSubmit);
5501 if (YAHOO.env.ua.ie) {
5502 //form.fireEvent("onsubmit");
5503 if (tar && !tar.disabled) {
5506 } else { // Gecko, Opera, and Safari
5507 if (tar && !tar.disabled) {
5510 var oEvent = document.createEvent("HTMLEvents");
5511 oEvent.initEvent("submit", true, true);
5512 form.dispatchEvent(oEvent);
5513 if (YAHOO.env.ua.webkit) {
5514 if (YAHOO.lang.isFunction(form.submit)) {
5520 //Removed this, not need since removing Safari 2.x
5521 //Event.stopEvent(ev);
5525 * @method _handleFontSize
5526 * @description Handles the font size button in the toolbar.
5527 * @param {Object} o Object returned from Toolbar's buttonClick Event
5529 _handleFontSize: function(o) {
5530 var button = this.toolbar.getButtonById(o.button.id);
5531 var value = button.get('label') + 'px';
5532 this.execCommand('fontsize', value);
5537 * @description Handles the colorpicker buttons in the toolbar.
5538 * @param {Object} o Object returned from Toolbar's buttonClick Event
5540 _handleColorPicker: function(o) {
5542 var value = '#' + o.color;
5543 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5544 this.execCommand(cmd, value);
5549 * @method _handleAlign
5550 * @description Handles the alignment buttons in the toolbar.
5551 * @param {Object} o Object returned from Toolbar's buttonClick Event
5553 _handleAlign: function(o) {
5555 for (var i = 0; i < o.button.menu.length; i++) {
5556 if (o.button.menu[i].value == o.button.value) {
5557 cmd = o.button.menu[i].value;
5560 var value = this._getSelection();
5562 this.execCommand(cmd, value);
5567 * @method _handleAfterNodeChange
5568 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5570 _handleAfterNodeChange: function() {
5571 var path = this._getDomPath(),
5576 fn_button = this.toolbar.getButtonByValue('fontname'),
5577 fs_button = this.toolbar.getButtonByValue('fontsize'),
5578 hd_button = this.toolbar.getButtonByValue('heading');
5580 for (var i = 0; i < path.length; i++) {
5583 var tag = elm.tagName.toLowerCase();
5586 if (elm.getAttribute('tag')) {
5587 tag = elm.getAttribute('tag');
5590 family = elm.getAttribute('face');
5591 if (Dom.getStyle(elm, 'font-family')) {
5592 family = Dom.getStyle(elm, 'font-family');
5594 family = family.replace(/'/g, '');
5597 if (tag.substring(0, 1) == 'h') {
5599 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5600 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5601 hd_button.set('label', hd_button._configs.menu.value[h].text);
5604 this._updateMenuChecked('heading', tag);
5610 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5611 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5613 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5617 family = fn_button._configs.label._initialConfig.value;
5619 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5620 if (fn_button.get('label') != familyLabel) {
5621 fn_button.set('label', familyLabel);
5622 this._updateMenuChecked('fontname', family);
5627 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5628 if ((fontsize === null) || isNaN(fontsize)) {
5629 fontsize = fs_button._configs.label._initialConfig.value;
5631 fs_button.set('label', ''+fontsize);
5634 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5635 this.toolbar.enableButton(fn_button);
5636 this.toolbar.enableButton(fs_button);
5637 this.toolbar.enableButton('forecolor');
5638 this.toolbar.enableButton('backcolor');
5640 if (this._isElement(elm, 'img')) {
5641 if (YAHOO.widget.Overlay) {
5642 this.toolbar.enableButton('createlink');
5645 if (this._hasParent(elm, 'blockquote')) {
5646 this.toolbar.selectButton('indent');
5647 this.toolbar.disableButton('indent');
5648 this.toolbar.enableButton('outdent');
5650 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5651 this.toolbar.disableButton('indent');
5653 this._lastButton = null;
5658 * @method _handleInsertImageClick
5659 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5661 _handleInsertImageClick: function() {
5662 if (this.get('limitCommands')) {
5663 if (!this.toolbar.getButtonByValue('insertimage')) {
5664 YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'SimpleEditor');
5669 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5670 var _handleAEC = function() {
5671 var el = this.currentElement[0],
5674 el = this._getSelectedElement();
5677 if (el.getAttribute('src')) {
5678 src = el.getAttribute('src', 2);
5679 if (src.indexOf(this.get('blankimage')) != -1) {
5680 src = this.STR_IMAGE_HERE;
5684 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5685 if ((str !== '') && (str !== null)) {
5686 el.setAttribute('src', str);
5687 } else if (str === '') {
5688 el.parentNode.removeChild(el);
5689 this.currentElement = [];
5691 } else if ((str === null)) {
5692 src = el.getAttribute('src', 2);
5693 if (src.indexOf(this.get('blankimage')) != -1) {
5694 el.parentNode.removeChild(el);
5695 this.currentElement = [];
5700 this.toolbar.set('disabled', false);
5701 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5703 this.on('afterExecCommand', _handleAEC, this, true);
5707 * @method _handleInsertImageWindowClose
5708 * @description Handles the closing of the Image Properties Window.
5710 _handleInsertImageWindowClose: function() {
5715 * @method _isLocalFile
5716 * @param {String} url THe url/string to check
5717 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5719 _isLocalFile: function(url) {
5720 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5727 * @method _handleCreateLinkClick
5728 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5730 _handleCreateLinkClick: function() {
5731 if (this.get('limitCommands')) {
5732 if (!this.toolbar.getButtonByValue('createlink')) {
5733 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
5738 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5740 var _handleAEC = function() {
5741 var el = this.currentElement[0],
5745 if (el.getAttribute('href', 2) !== null) {
5746 url = el.getAttribute('href', 2);
5749 var str = prompt(this.STR_LINK_URL + ': ', url);
5750 if ((str !== '') && (str !== null)) {
5752 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5753 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5754 //Found an @ sign, prefix with mailto:
5755 urlValue = 'mailto:' + urlValue;
5757 /* :// not found adding */
5758 if (urlValue.substring(0, 1) != '#') {
5759 //urlValue = 'http:/'+'/' + urlValue;
5763 el.setAttribute('href', urlValue);
5764 } else if (str !== null) {
5765 var _span = this._getDoc().createElement('span');
5766 _span.innerHTML = el.innerHTML;
5767 Dom.addClass(_span, 'yui-non');
5768 el.parentNode.replaceChild(_span, el);
5771 this.toolbar.set('disabled', false);
5772 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5774 this.on('afterExecCommand', _handleAEC, this);
5779 * @method _handleCreateLinkWindowClose
5780 * @description Handles the closing of the Link Properties Window.
5782 _handleCreateLinkWindowClose: function() {
5784 this.currentElement = [];
5788 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5790 render: function() {
5791 if (this._rendered) {
5794 YAHOO.log('Render', 'info', 'SimpleEditor');
5795 if (!this.DOMReady) {
5796 YAHOO.log('!DOMReady', 'info', 'SimpleEditor');
5797 this._queue[this._queue.length] = ['render', arguments];
5800 if (this.get('element')) {
5801 if (this.get('element').tagName) {
5802 this._textarea = true;
5803 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5804 this._textarea = false;
5807 YAHOO.log('No Valid Element', 'error', 'SimpleEditor');
5811 YAHOO.log('No Element', 'error', 'SimpleEditor');
5814 this._rendered = true;
5816 window.setTimeout(function() {
5817 self._render.call(self);
5823 * @description Causes the toolbar and the editor to render and replace the textarea.
5825 _render: function() {
5827 this.set('textarea', this.get('element'));
5829 this.get('element_cont').setStyle('display', 'none');
5830 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5832 this.set('iframe', this._createIframe());
5834 window.setTimeout(function() {
5835 self._setInitialContent.call(self);
5838 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5840 if (this.get('disabled')) {
5841 this._disableEditor(true);
5844 var tbarConf = this.get('toolbar');
5845 //Create Toolbar instance
5846 if (tbarConf instanceof Toolbar) {
5847 this.toolbar = tbarConf;
5848 //Set the toolbar to disabled until content is loaded
5849 this.toolbar.set('disabled', true);
5851 //Set the toolbar to disabled until content is loaded
5852 tbarConf.disabled = true;
5853 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5856 YAHOO.log('fireEvent::toolbarLoaded', 'info', 'SimpleEditor');
5857 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5860 this.toolbar.on('toolbarCollapsed', function() {
5861 if (this.currentWindow) {
5865 this.toolbar.on('toolbarExpanded', function() {
5866 if (this.currentWindow) {
5870 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5872 this.toolbar.on('colorPickerClicked', function(o) {
5873 this._handleColorPicker(o);
5874 return false; //Stop the buttonClick event
5877 this.toolbar.on('alignClick', this._handleAlign, this, true);
5878 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5879 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5880 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5881 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5882 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5885 //Replace Textarea with editable area
5886 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5889 this.setStyle('visibility', 'hidden');
5890 this.setStyle('position', 'absolute');
5891 this.setStyle('top', '-9999px');
5892 this.setStyle('left', '-9999px');
5893 this.get('element_cont').appendChild(this.get('element'));
5894 this.get('element_cont').setStyle('display', 'block');
5897 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5898 this.get('iframe').addClass(this.CLASS_EDITABLE);
5900 //Set height and width of editor container
5901 this.get('element_cont').setStyle('width', this.get('width'));
5902 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5904 this.get('iframe').setStyle('width', '100%'); //WIDTH
5905 this.get('iframe').setStyle('height', '100%');
5909 window.setTimeout(function() {
5910 self._setupAfterElement.call(self);
5912 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5915 * @method execCommand
5916 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5917 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5918 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5920 execCommand: function(action, value) {
5921 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5922 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5923 this.STOP_EXEC_COMMAND = false;
5926 this._lastCommand = action;
5927 this._setMarkupType(action);
5928 if (this.browser.ie) {
5929 this._getWindow().focus();
5933 if (this.get('limitCommands')) {
5934 if (!this.toolbar.getButtonByValue(action)) {
5935 YAHOO.log('Toolbar Button for (' + action + ') was not found, skipping exec.', 'info', 'SimpleEditor');
5940 this.editorDirty = true;
5942 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5943 YAHOO.log('Found execCommand override method: (cmd_' + action.toLowerCase() + ')', 'info', 'SimpleEditor');
5944 var retValue = this['cmd_' + action.toLowerCase()](value);
5947 action = retValue[1];
5950 value = retValue[2];
5954 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'SimpleEditor');
5956 this._getDoc().execCommand(action, false, value);
5958 YAHOO.log('execCommand Failed', 'error', 'SimpleEditor');
5961 YAHOO.log('OVERRIDE::execCommand::(' + action + '),(' + value + ') skipped', 'warn', 'SimpleEditor');
5963 this.on('afterExecCommand', function() {
5964 this.unsubscribeAll('afterExecCommand');
5967 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5970 /* {{{ Command Overrides */
5974 * @param value Value passed from the execCommand method
5975 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5977 cmd_bold: function(value) {
5978 if (!this.browser.webkit) {
5979 var el = this._getSelectedElement();
5980 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5981 if (el.style.fontWeight == 'bold') {
5982 el.style.fontWeight = '';
5983 var b = this._getDoc().createElement('b'),
5984 par = el.parentNode;
5985 par.replaceChild(b, el);
5993 * @method cmd_italic
5994 * @param value Value passed from the execCommand method
5995 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5998 cmd_italic: function(value) {
5999 if (!this.browser.webkit) {
6000 var el = this._getSelectedElement();
6001 if (el && this._isElement(el, 'span') && this._hasSelection()) {
6002 if (el.style.fontStyle == 'italic') {
6003 el.style.fontStyle = '';
6004 var i = this._getDoc().createElement('i'),
6005 par = el.parentNode;
6006 par.replaceChild(i, el);
6016 * @method cmd_underline
6017 * @param value Value passed from the execCommand method
6018 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
6020 cmd_underline: function(value) {
6021 if (!this.browser.webkit) {
6022 var el = this._getSelectedElement();
6023 if (el && this._isElement(el, 'span')) {
6024 if (el.style.textDecoration == 'underline') {
6025 el.style.textDecoration = 'none';
6027 el.style.textDecoration = 'underline';
6035 * @method cmd_backcolor
6036 * @param value Value passed from the execCommand method
6037 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
6039 cmd_backcolor: function(value) {
6041 el = this._getSelectedElement(),
6042 action = 'backcolor';
6044 if (this.browser.gecko || this.browser.opera) {
6045 this._setEditorStyle(true);
6046 action = 'hilitecolor';
6049 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6050 el.style.backgroundColor = value;
6051 this._selectNode(el);
6054 if (this.get('insert')) {
6055 el = this._createInsertElement({ backgroundColor: value });
6057 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
6058 this._selectNode(this.currentElement[0]);
6063 return [exec, action];
6066 * @method cmd_forecolor
6067 * @param value Value passed from the execCommand method
6068 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
6070 cmd_forecolor: function(value) {
6072 el = this._getSelectedElement();
6074 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6075 Dom.setStyle(el, 'color', value);
6076 this._selectNode(el);
6079 if (this.get('insert')) {
6080 el = this._createInsertElement({ color: value });
6082 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
6083 this._selectNode(this.currentElement[0]);
6090 * @method cmd_unlink
6091 * @param value Value passed from the execCommand method
6092 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
6094 cmd_unlink: function(value) {
6095 this._swapEl(this.currentElement[0], 'span', function(el) {
6096 el.className = 'yui-non';
6101 * @method cmd_createlink
6102 * @param value Value passed from the execCommand method
6103 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
6105 cmd_createlink: function(value) {
6106 var el = this._getSelectedElement(), _a = null;
6107 if (this._hasParent(el, 'a')) {
6108 this.currentElement[0] = this._hasParent(el, 'a');
6109 } else if (this._isElement(el, 'li')) {
6110 _a = this._getDoc().createElement('a');
6111 _a.innerHTML = el.innerHTML;
6114 this.currentElement[0] = _a;
6115 } else if (!this._isElement(el, 'a')) {
6116 this._createCurrentElement('a');
6117 _a = this._swapEl(this.currentElement[0], 'a');
6118 this.currentElement[0] = _a;
6120 this.currentElement[0] = el;
6125 * @method cmd_insertimage
6126 * @param value Value passed from the execCommand method
6127 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
6129 cmd_insertimage: function(value) {
6130 var exec = true, _img = null, action = 'insertimage',
6131 el = this._getSelectedElement();
6134 value = this.get('blankimage');
6138 * @knownissue Safari Cursor Position
6139 * @browser Safari 2.x
6140 * @description The issue here is that we have no way of knowing where the cursor position is
6141 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6144 YAHOO.log('InsertImage: ' + el.tagName, 'info', 'SimpleEditor');
6145 if (this._isElement(el, 'img')) {
6146 this.currentElement[0] = el;
6149 if (this._getDoc().queryCommandEnabled(action)) {
6150 this._getDoc().execCommand(action, false, value);
6151 var imgs = this._getDoc().getElementsByTagName('img');
6152 for (var i = 0; i < imgs.length; i++) {
6153 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
6154 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
6155 this.currentElement[0] = imgs[i];
6160 if (el == this._getDoc().body) {
6161 _img = this._getDoc().createElement('img');
6162 _img.setAttribute('src', value);
6163 YAHOO.util.Dom.addClass(_img, 'yui-img');
6164 this._getDoc().body.appendChild(_img);
6166 this._createCurrentElement('img');
6167 _img = this._getDoc().createElement('img');
6168 _img.setAttribute('src', value);
6169 YAHOO.util.Dom.addClass(_img, 'yui-img');
6170 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
6172 this.currentElement[0] = _img;
6179 * @method cmd_inserthtml
6180 * @param value Value passed from the execCommand method
6181 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6183 cmd_inserthtml: function(value) {
6184 var exec = true, action = 'inserthtml', _span = null, _range = null;
6186 * @knownissue Safari cursor position
6187 * @browser Safari 2.x
6188 * @description The issue here is that we have no way of knowing where the cursor position is
6189 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6191 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6192 YAHOO.log('More Safari DOM tricks (inserthtml)', 'info', 'EditorSafari');
6193 this._createCurrentElement('img');
6194 _span = this._getDoc().createElement('span');
6195 _span.innerHTML = value;
6196 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6198 } else if (this.browser.ie) {
6199 _range = this._getRange();
6201 _range.item(0).outerHTML = value;
6203 _range.pasteHTML(value);
6211 * @param tag The tag of the list you want to create (eg, ul or ol)
6212 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6214 cmd_list: function(tag) {
6215 var exec = true, list = null, li = 0, el = null, str = '',
6216 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6218 action = 'insertunorderedlist';
6221 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6222 * @browser Safari 2.x
6223 * The issue with this workaround is that when applied to a set of text
6224 * that has BR's in it, Safari may or may not pick up the individual items as
6225 * list items. This is fixed in WebKit (Safari 3)
6226 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6228 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6229 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) {
6230 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6231 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6232 el = selEl.parentNode;
6233 list = this._getDoc().createElement('span');
6234 YAHOO.util.Dom.addClass(list, 'yui-non');
6236 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div');
6237 for (li = 0; li < lis.length; li++) {
6238 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>';
6240 list.innerHTML = str;
6241 this.currentElement[0] = el;
6242 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6244 YAHOO.log('Create list item', 'info', 'SimpleEditor');
6245 this._createCurrentElement(tag.toLowerCase());
6246 list = this._getDoc().createElement(tag);
6247 for (li = 0; li < this.currentElement.length; li++) {
6248 var newli = this._getDoc().createElement('li');
6249 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
6250 list.appendChild(newli);
6252 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6255 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'),
6256 items = list.firstChild.innerHTML.split(b_tag), i, item;
6257 if (items.length > 0) {
6258 list.innerHTML = '';
6259 for (i = 0; i < items.length; i++) {
6260 item = this._getDoc().createElement('li');
6261 item.innerHTML = items[i];
6262 list.appendChild(item);
6266 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6267 this.currentElement[0] = list;
6268 var _h = this.currentElement[0].firstChild;
6269 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6270 if (this.browser.webkit) {
6271 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6276 el = this._getSelectedElement();
6277 YAHOO.log(el.tagName);
6278 if (this._isElement(el, 'li') && this._isElement(el.parentNode, tag) || (this.browser.ie && this._isElement(this._getRange().parentElement, 'li')) || (this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { //we are in a list..
6279 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6280 if (this.browser.ie) {
6281 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6282 el = el.getElementsByTagName('li')[0];
6284 YAHOO.log('Undo IE', 'info', 'SimpleEditor');
6286 var lis2 = el.parentNode.getElementsByTagName('li');
6287 for (var j = 0; j < lis2.length; j++) {
6288 str += lis2[j].innerHTML + '<br>';
6290 var newEl = this._getDoc().createElement('span');
6291 newEl.innerHTML = str;
6292 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6295 this._getDoc().execCommand(action, '', el.parentNode);
6300 if (this.browser.opera) {
6302 window.setTimeout(function() {
6303 var liso = self._getDoc().getElementsByTagName('li');
6304 for (var i = 0; i < liso.length; i++) {
6305 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6306 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6311 if (this.browser.ie && exec) {
6313 if (this._getRange().html) {
6314 html = '<li>' + this._getRange().html+ '</li>';
6316 var t = this._getRange().text.split('\n');
6319 for (var ie = 0; ie < t.length; ie++) {
6320 html += '<li>' + t[ie] + '</li>';
6323 var txt = this._getRange().text;
6325 html = '<li id="new_list_item">' + txt + '</li>';
6327 html = '<li>' + txt + '</li>';
6331 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6332 var new_item = this._getDoc().getElementById('new_list_item');
6334 var range = this._getDoc().body.createTextRange();
6335 range.moveToElementText(new_item);
6336 range.collapse(false);
6346 * @method cmd_insertorderedlist
6347 * @param value Value passed from the execCommand method
6348 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6350 cmd_insertorderedlist: function(value) {
6351 return [this.cmd_list('ol')];
6354 * @method cmd_insertunorderedlist
6355 * @param value Value passed from the execCommand method
6356 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6358 cmd_insertunorderedlist: function(value) {
6359 return [this.cmd_list('ul')];
6362 * @method cmd_fontname
6363 * @param value Value passed from the execCommand method
6364 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6366 cmd_fontname: function(value) {
6368 selEl = this._getSelectedElement();
6370 this.currentFont = value;
6371 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6372 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6374 } else if (this.get('insert') && !this._hasSelection()) {
6375 YAHOO.log('No selection and no selected element and we are in insert mode', 'info', 'SimpleEditor');
6376 var el = this._createInsertElement({ fontFamily: value });
6382 * @method cmd_fontsize
6383 * @param value Value passed from the execCommand method
6384 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6386 cmd_fontsize: function(value) {
6387 var el = null, go = true;
6388 el = this._getSelectedElement();
6389 if (this.browser.webkit) {
6390 if (this.currentElement[0]) {
6391 if (el == this.currentElement[0]) {
6393 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6394 this._selectNode(el);
6395 this.currentElement[0] = el;
6400 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6401 el = this._getSelectedElement();
6402 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6403 if (this.get('insert') && this.browser.ie) {
6404 var r = this._getRange();
6408 this._selectNode(el);
6410 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6411 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6413 if (this.get('insert') && !this._hasSelection()) {
6414 el = this._createInsertElement({ fontSize: value });
6415 this.currentElement[0] = el;
6416 this._selectNode(this.currentElement[0]);
6418 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6419 this._selectNode(this.currentElement[0]);
6429 * @param {HTMLElement} el The element to swap with
6430 * @param {String} tagName The tagname of the element that you wish to create
6431 * @param {Function} callback (optional) A function to run on the element after it is created, but before it is replaced. An element reference is passed to this function.
6432 * @description This function will create a new element in the DOM and populate it with the contents of another element. Then it will assume it's place.
6434 _swapEl: function(el, tagName, callback) {
6435 var _el = this._getDoc().createElement(tagName);
6437 _el.innerHTML = el.innerHTML;
6439 if (typeof callback == 'function') {
6440 callback.call(this, _el);
6443 el.parentNode.replaceChild(_el, el);
6449 * @method _createInsertElement
6450 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6451 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6452 * @return {HTMLElement}
6454 _createInsertElement: function(css) {
6455 this._createCurrentElement('span', css);
6456 var el = this.currentElement[0];
6457 if (this.browser.webkit) {
6458 //Little Safari Hackery here..
6459 el.innerHTML = '<span class="yui-non"> </span>';
6461 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6462 } else if (this.browser.ie || this.browser.opera) {
6463 el.innerHTML = ' ';
6466 this._selectNode(el, true);
6471 * @method _createCurrentElement
6472 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6473 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6474 * @description This is a work around for the various browser issues with execCommand. This method will run <code>execCommand('fontname', false, 'yui-tmp')</code> on the given selection.
6475 * It will then search the document for an element with the font-family set to <strong>yui-tmp</strong> and replace that with another span that has other information in it, then assign the new span to the
6476 * <code>this.currentElement</code> array, so we now have element references to the elements that were just modified. At this point we can use standard DOM manipulation to change them as we see fit.
6478 _createCurrentElement: function(tagName, tagStyle) {
6479 tagName = ((tagName) ? tagName : 'a');
6482 _doc = this._getDoc();
6484 if (this.currentFont) {
6488 tagStyle.fontFamily = this.currentFont;
6489 this.currentFont = null;
6491 this.currentElement = [];
6493 var _elCreate = function(tagName, tagStyle) {
6495 tagName = ((tagName) ? tagName : 'span');
6496 tagName = tagName.toLowerCase();
6504 el = _doc.createElement(tagName);
6507 el = _doc.createElement(tagName);
6508 if (tagName === 'span') {
6509 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6510 YAHOO.util.Dom.addClass(el, 'yui-tag');
6511 el.setAttribute('tag', tagName);
6514 for (var k in tagStyle) {
6515 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6516 el.style[k] = tagStyle[k];
6524 if (!this._hasSelection()) {
6525 if (this._getDoc().queryCommandEnabled('insertimage')) {
6526 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6527 var imgs = this._getDoc().getElementsByTagName('img');
6528 for (var j = 0; j < imgs.length; j++) {
6529 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6530 el = _elCreate(tagName, tagStyle);
6531 imgs[j].parentNode.replaceChild(el, imgs[j]);
6532 this.currentElement[this.currentElement.length] = el;
6536 if (this.currentEvent) {
6537 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6540 tar = this._getDoc().body;
6545 * @knownissue Safari Cursor Position
6546 * @browser Safari 2.x
6547 * @description The issue here is that we have no way of knowing where the cursor position is
6548 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6550 el = _elCreate(tagName, tagStyle);
6551 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6552 if (this._isElement(tar, 'html')) {
6553 tar = this._getDoc().body;
6555 tar.appendChild(el);
6556 } else if (tar.nextSibling) {
6557 tar.parentNode.insertBefore(el, tar.nextSibling);
6559 tar.parentNode.appendChild(el);
6561 //this.currentElement = el;
6562 this.currentElement[this.currentElement.length] = el;
6563 this.currentEvent = null;
6564 if (this.browser.webkit) {
6565 //Force Safari to focus the new element
6566 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6567 if (this.browser.webkit3) {
6568 this._getSelection().collapseToStart();
6570 this._getSelection().collapse(true);
6575 //Force CSS Styling for this action...
6576 this._setEditorStyle(true);
6577 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6578 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6580 if (!this._isElement(this._getSelectedElement(), 'body')) {
6581 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6582 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6584 for (var _els = 0; _els < __els.length; _els++) {
6585 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6586 for (var e = 0; e < _tmp1.length; e++) {
6587 _tmp[_tmp.length] = _tmp1[e];
6592 for (var i = 0; i < _tmp.length; i++) {
6593 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6594 if (tagName !== 'span') {
6595 el = _elCreate(tagName, tagStyle);
6597 el = _elCreate(_tmp[i].tagName, tagStyle);
6599 el.innerHTML = _tmp[i].innerHTML;
6600 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6601 var fc = _tmp[i].getElementsByTagName('li')[0];
6602 _tmp[i].style.fontFamily = 'inherit';
6603 fc.style.fontFamily = 'inherit';
6604 el.innerHTML = fc.innerHTML;
6607 this.currentElement[this.currentElement.length] = el;
6608 } else if (this._isElement(_tmp[i], 'li')) {
6609 _tmp[i].innerHTML = '';
6610 _tmp[i].appendChild(el);
6611 _tmp[i].style.fontFamily = 'inherit';
6612 this.currentElement[this.currentElement.length] = el;
6614 if (_tmp[i].parentNode) {
6615 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6616 this.currentElement[this.currentElement.length] = el;
6617 this.currentEvent = null;
6618 if (this.browser.webkit) {
6619 //Force Safari to focus the new element
6620 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6621 if (this.browser.webkit3) {
6622 this._getSelection().collapseToStart();
6624 this._getSelection().collapse(true);
6627 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6628 this._getSelection().empty();
6630 if (this.browser.gecko) {
6631 this._getSelection().collapseToStart();
6637 var len = this.currentElement.length;
6638 for (var o = 0; o < len; o++) {
6639 if ((o + 1) != len) { //Skip the last one in the list
6640 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6641 if (this._isElement(this.currentElement[o], 'br')) {
6642 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6651 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6654 saveHTML: function() {
6655 var html = this.cleanHTML();
6656 if (this._textarea) {
6657 this.get('element').value = html;
6659 this.get('element').innerHTML = html;
6661 if (this.get('saveEl') !== this.get('element')) {
6662 var out = this.get('saveEl');
6663 if (Lang.isString(out)) {
6667 if (out.tagName.toLowerCase() === 'textarea') {
6670 out.innerHTML = html;
6677 * @method setEditorHTML
6678 * @param {String} incomingHTML The html content to load into the editor
6679 * @description Loads HTML into the editors body
6681 setEditorHTML: function(incomingHTML) {
6682 var html = this._cleanIncomingHTML(incomingHTML);
6683 html = html.replace(/RIGHT_BRACKET/gi, '{');
6684 html = html.replace(/LEFT_BRACKET/gi, '}');
6685 this._getDoc().body.innerHTML = html;
6689 * @method getEditorHTML
6690 * @description Gets the unprocessed/unfiltered HTML from the editor
6692 getEditorHTML: function() {
6694 var b = this._getDoc().body;
6696 YAHOO.log('Body is null, returning null.', 'error', 'SimpleEditor');
6699 return this._getDoc().body.innerHTML;
6706 * @description This method needs to be called if the Editor was hidden (like in a TabView or Panel). It is used to reset the editor after being in a container that was set to display none.
6709 if (this.browser.gecko) {
6710 this._setDesignMode('on');
6713 if (this.browser.webkit) {
6715 window.setTimeout(function() {
6716 self._setInitialContent.call(self);
6719 //Adding this will close all other Editor window's when showing this one.
6720 if (this.currentWindow) {
6723 //Put the iframe back in place
6724 this.get('iframe').setStyle('position', 'static');
6725 this.get('iframe').setStyle('left', '');
6729 * @description This method needs to be called if the Editor is to be hidden (like in a TabView or Panel). It should be called to clear timeouts and close open editor windows.
6732 //Adding this will close all other Editor window's.
6733 if (this.currentWindow) {
6736 if (this._fixNodesTimer) {
6737 clearTimeout(this._fixNodesTimer);
6738 this._fixNodesTimer = null;
6740 if (this._nodeChangeTimer) {
6741 clearTimeout(this._nodeChangeTimer);
6742 this._nodeChangeTimer = null;
6744 this._lastNodeChange = 0;
6745 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6746 this.get('iframe').setStyle('position', 'absolute');
6747 this.get('iframe').setStyle('left', '-9999px');
6750 * @method _cleanIncomingHTML
6751 * @param {String} html The unfiltered HTML
6752 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6753 * @return {String} The filtered HTML
6755 _cleanIncomingHTML: function(html) {
6756 html = html.replace(/{/gi, 'RIGHT_BRACKET');
6757 html = html.replace(/}/gi, 'LEFT_BRACKET');
6759 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6760 html = html.replace(/<\/strong>/gi, '</b>');
6762 //replace embed before em check
6763 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6764 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6766 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6767 html = html.replace(/<\/em>/gi, '</i>');
6768 html = html.replace(/_moz_dirty=""/gi, '');
6770 //Put embed tags back in..
6771 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6772 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6773 if (this.get('plainText')) {
6774 YAHOO.log('Filtering as plain text', 'info', 'SimpleEditor');
6775 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6776 html = html.replace(/ /gi, ' '); //Replace all double spaces
6777 html = html.replace(/\t/gi, ' '); //Replace all tabs
6779 //Removing Script Tags from the Editor
6780 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6781 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6782 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6783 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6784 //Replace the line feeds
6785 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6787 //Remove Bad HTML elements (used to be script nodes)
6788 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6789 //Replace the lines feeds
6790 html = html.replace(/<YUI_LF>/g, '\n');
6795 * @param {String} html The unfiltered HTML
6796 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6797 * @return {String} The filtered HTML
6799 cleanHTML: function(html) {
6800 //Start Filtering Output
6803 html = this.getEditorHTML();
6805 var markup = this.get('markup');
6806 //Make some backups...
6807 html = this.pre_filter_linebreaks(html, markup);
6810 html = this.filter_msword(html);
6812 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6813 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6815 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6816 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6818 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6819 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6820 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6821 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6823 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6824 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6826 //Convert b and i tags to strong and em tags
6827 if ((markup == 'semantic') || (markup == 'xhtml')) {
6828 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6829 html = html.replace(/<\/i>/gi, '</em>');
6830 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6831 html = html.replace(/<\/b>/gi, '</strong>');
6834 html = html.replace(/_moz_dirty=""/gi, '');
6836 //normalize strikethrough
6837 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6838 html = html.replace(/\/strike>/gi, '/span>');
6842 if (this.browser.ie) {
6843 html = html.replace(/text-decoration/gi, 'text-decoration');
6844 html = html.replace(/font-weight/gi, 'font-weight');
6845 html = html.replace(/_width="([^>]*)"/gi, '');
6846 html = html.replace(/_height="([^>]*)"/gi, '');
6847 //Cleanup Image URL's
6848 var url = this._baseHREF.replace(/\//gi, '\\/'),
6849 re = new RegExp('src="' + url, 'gi');
6850 html = html.replace(re, 'src="');
6852 html = html.replace(/<font/gi, '<font');
6853 html = html.replace(/<\/font>/gi, '</font>');
6854 html = html.replace(/<span/gi, '<span');
6855 html = html.replace(/<\/span>/gi, '</span>');
6856 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6857 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6858 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6859 if (this.browser.webkit) {
6860 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6861 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6863 html = html.replace(/\/u>/gi, '/span>');
6864 if (markup == 'css') {
6865 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6866 html = html.replace(/<\/em>/gi, '</i>');
6867 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6868 html = html.replace(/<\/strong>/gi, '</b>');
6869 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6870 html = html.replace(/\/b>/gi, '/span>');
6871 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6872 html = html.replace(/\/i>/gi, '/span>');
6874 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6876 html = html.replace(/<u/gi, '<u');
6877 html = html.replace(/\/u>/gi, '/u>');
6879 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6880 html = html.replace(/\/ol>/gi, '/ol>');
6881 html = html.replace(/<li/gi, '<li');
6882 html = html.replace(/\/li>/gi, '/li>');
6883 html = this.filter_safari(html);
6885 html = this.filter_internals(html);
6887 html = this.filter_all_rgb(html);
6889 //Replace our backups with the real thing
6890 html = this.post_filter_linebreaks(html, markup);
6892 if (markup == 'xhtml') {
6893 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6894 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6896 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6897 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6899 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6900 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6902 html = this.filter_invalid_lists(html);
6904 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6905 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6907 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6908 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6910 //This should fix &'s in URL's
6911 html = html.replace(/ & /gi, ' YUI_AMP ');
6912 html = html.replace(/ &/gi, ' YUI_AMP_F ');
6913 html = html.replace(/& /gi, ' YUI_AMP_R ');
6914 html = html.replace(/&/gi, '&');
6915 html = html.replace(/ YUI_AMP /gi, ' & ');
6916 html = html.replace(/ YUI_AMP_F /gi, ' &');
6917 html = html.replace(/ YUI_AMP_R /gi, '& ');
6919 //Trim the output, removing whitespace from the beginning and end
6920 html = YAHOO.lang.trim(html);
6922 if (this.get('removeLineBreaks')) {
6923 html = html.replace(/\n/g, '').replace(/\r/g, '');
6924 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6927 for (var v in this.invalidHTML) {
6928 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6929 if (Lang.isObject(v) && v.keepContents) {
6930 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6932 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6937 /* LATER -- Add DOM manipulation
6939 var frag = document.createDocumentFragment();
6940 frag.innerHTML = html;
6942 var ps = frag.getElementsByTagName('p'),
6944 for (var i = 0; i < len; i++) {
6945 var ps2 = ps[i].getElementsByTagName('p');
6951 html = frag.innerHTML;
6955 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6960 * @method filter_msword
6961 * @param String html The HTML string to filter
6962 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6964 filter_msword: function(html) {
6965 if (!this.get('filterWord')) {
6968 //Remove the ms o: tags
6969 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6970 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6972 //Remove the ms w: tags
6973 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6975 //Remove mso-? styles.
6976 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6978 //Remove more bogus MS styles.
6979 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6980 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6981 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6982 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6983 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6984 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6985 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6986 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6988 //Remove XML declarations
6989 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6992 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6994 //Remove language tags
6995 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6997 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6998 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6999 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
7004 * @method filter_invalid_lists
7005 * @param String html The HTML string to filter
7006 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
7008 filter_invalid_lists: function(html) {
7009 html = html.replace(/<\/li>\n/gi, '</li>');
7011 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
7012 html = html.replace(/<\/ol>/gi, '</ol></li>');
7013 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
7015 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
7016 html = html.replace(/<\/ul>/gi, '</ul></li>');
7017 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
7019 html = html.replace(/<\/li>/gi, "</li>");
7020 html = html.replace(/<\/ol>/gi, "</ol>");
7021 html = html.replace(/<ol>/gi, "<ol>");
7022 html = html.replace(/<ul>/gi, "<ul>");
7026 * @method filter_safari
7027 * @param String html The HTML string to filter
7028 * @description Filters strings specific to Safari
7031 filter_safari: function(html) {
7032 if (this.browser.webkit) {
7033 //<span class="Apple-tab-span" style="white-space:pre"> </span>
7034 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
7035 html = html.replace(/Apple-style-span/gi, '');
7036 html = html.replace(/style="line-height: normal;"/gi, '');
7037 html = html.replace(/yui-wk-div/gi, '');
7038 html = html.replace(/yui-wk-p/gi, '');
7042 html = html.replace(/<li><\/li>/gi, '');
7043 html = html.replace(/<li> <\/li>/gi, '');
7044 html = html.replace(/<li> <\/li>/gi, '');
7045 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
7046 if (this.get('ptags')) {
7047 html = html.replace(/<div([^>]*)>/g, '<p$1>');
7048 html = html.replace(/<\/div>/gi, '</p>');
7050 //html = html.replace(/<div>/gi, '<br>');
7051 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>');
7052 html = html.replace(/<\/div>/gi, '');
7058 * @method filter_internals
7059 * @param String html The HTML string to filter
7060 * @description Filters internal RTE strings and bogus attrs we don't want
7063 filter_internals: function(html) {
7064 html = html.replace(/\r/g, '');
7065 //Fix stuff we don't want
7066 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
7068 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
7070 html = html.replace(/yui-tag-span/gi, '');
7071 html = html.replace(/yui-tag/gi, '');
7072 html = html.replace(/yui-non/gi, '');
7073 html = html.replace(/yui-img/gi, '');
7074 html = html.replace(/ tag="span"/gi, '');
7075 html = html.replace(/ class=""/gi, '');
7076 html = html.replace(/ style=""/gi, '');
7077 html = html.replace(/ class=" "/gi, '');
7078 html = html.replace(/ class=" "/gi, '');
7079 html = html.replace(/ target=""/gi, '');
7080 html = html.replace(/ title=""/gi, '');
7082 if (this.browser.ie) {
7083 html = html.replace(/ class= /gi, '');
7084 html = html.replace(/ class= >/gi, '');
7090 * @method filter_all_rgb
7091 * @param String str The HTML string to filter
7092 * @description Converts all RGB color strings found in passed string to a hex color, example: style="color: rgb(0, 255, 0)" converts to style="color: #00ff00"
7095 filter_all_rgb: function(str) {
7096 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
7097 var arr = str.match(exp);
7098 if (Lang.isArray(arr)) {
7099 for (var i = 0; i < arr.length; i++) {
7100 var color = this.filter_rgb(arr[i]);
7101 str = str.replace(arr[i].toString(), color);
7108 * @method filter_rgb
7109 * @param String css The CSS string containing rgb(#,#,#);
7110 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
7113 filter_rgb: function(css) {
7114 if (css.toLowerCase().indexOf('rgb') != -1) {
7115 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
7116 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
7118 if (rgb.length == 5) {
7119 var r = parseInt(rgb[1], 10).toString(16);
7120 var g = parseInt(rgb[2], 10).toString(16);
7121 var b = parseInt(rgb[3], 10).toString(16);
7123 r = r.length == 1 ? '0' + r : r;
7124 g = g.length == 1 ? '0' + g : g;
7125 b = b.length == 1 ? '0' + b : b;
7127 css = "#" + r + g + b;
7133 * @method pre_filter_linebreaks
7134 * @param String html The HTML to filter
7135 * @param String markup The markup type to filter to
7136 * @description HTML Pre Filter
7139 pre_filter_linebreaks: function(html, markup) {
7140 if (this.browser.webkit) {
7141 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
7142 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
7144 html = html.replace(/<br>/gi, '<YUI_BR>');
7145 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
7146 html = html.replace(/<br\/>/gi, '<YUI_BR>');
7147 html = html.replace(/<br \/>/gi, '<YUI_BR>');
7148 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
7149 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
7150 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
7151 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
7153 html = html.replace(/<YUI_BR>$/, '');
7155 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
7156 if (this.browser.ie) {
7157 html = html.replace(/ /g, '\t');
7162 * @method post_filter_linebreaks
7163 * @param String html The HTML to filter
7164 * @param String markup The markup type to filter to
7165 * @description HTML Pre Filter
7168 post_filter_linebreaks: function(html, markup) {
7169 if (markup == 'xhtml') {
7170 html = html.replace(/<YUI_BR>/g, '<br />');
7172 html = html.replace(/<YUI_BR>/g, '<br>');
7177 * @method clearEditorDoc
7178 * @description Clear the doc of the Editor
7180 clearEditorDoc: function() {
7181 this._getDoc().body.innerHTML = ' ';
7184 * @method openWindow
7185 * @description Override Method for Advanced Editor
7187 openWindow: function(win) {
7190 * @method moveWindow
7191 * @description Override Method for Advanced Editor
7193 moveWindow: function() {
7197 * @method _closeWindow
7198 * @description Override Method for Advanced Editor
7200 _closeWindow: function() {
7203 * @method closeWindow
7204 * @description Override Method for Advanced Editor
7206 closeWindow: function() {
7207 //this.unsubscribeAll('afterExecCommand');
7208 this.toolbar.resetAllButtons();
7213 * @description Destroys the editor, all of it's elements and objects.
7216 destroy: function() {
7217 if (this._nodeChangeDelayTimer) {
7218 clearTimeout(this._nodeChangeDelayTimer);
7222 YAHOO.log('Destroying Editor', 'warn', 'SimpleEditor');
7224 YAHOO.log('Destroying Resize', 'warn', 'SimpleEditor');
7225 this.resize.destroy();
7228 YAHOO.log('Unreg DragDrop Instance', 'warn', 'SimpleEditor');
7231 if (this.get('panel')) {
7232 YAHOO.log('Destroying Editor Panel', 'warn', 'SimpleEditor');
7233 this.get('panel').destroy();
7236 this.toolbar.destroy();
7237 YAHOO.log('Restoring TextArea', 'info', 'SimpleEditor');
7238 this.setStyle('visibility', 'visible');
7239 this.setStyle('position', 'static');
7240 this.setStyle('top', '');
7241 this.setStyle('left', '');
7242 var textArea = this.get('element');
7243 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7244 this.get('element_cont').get('element').innerHTML = '';
7245 this.set('handleSubmit', false); //Remove the submit handler
7250 * @description Returns a string representing the editor.
7253 toString: function() {
7254 var str = 'SimpleEditor';
7255 if (this.get && this.get('element_cont')) {
7256 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7263 * @event toolbarLoaded
7264 * @description Event is fired during the render process directly after the Toolbar is loaded. Allowing you to attach events to the toolbar. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7265 * @type YAHOO.util.CustomEvent
7269 * @description Event is fired after the cleanHTML method is called.
7270 * @type YAHOO.util.CustomEvent
7273 * @event afterRender
7274 * @description Event is fired after the render process finishes. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7275 * @type YAHOO.util.CustomEvent
7278 * @event editorContentLoaded
7279 * @description Event is fired after the editor iframe's document fully loads and fires it's onload event. From here you can start injecting your own things into the document. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7280 * @type YAHOO.util.CustomEvent
7283 * @event beforeNodeChange
7284 * @description Event fires at the beginning of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7285 * @type YAHOO.util.CustomEvent
7288 * @event afterNodeChange
7289 * @description Event fires at the end of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7290 * @type YAHOO.util.CustomEvent
7293 * @event beforeExecCommand
7294 * @description Event fires at the beginning of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7295 * @type YAHOO.util.CustomEvent
7298 * @event afterExecCommand
7299 * @description Event fires at the end of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7300 * @type YAHOO.util.CustomEvent
7303 * @event editorMouseUp
7304 * @param {Event} ev The DOM Event that occured
7305 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7306 * @type YAHOO.util.CustomEvent
7309 * @event editorMouseDown
7310 * @param {Event} ev The DOM Event that occured
7311 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7312 * @type YAHOO.util.CustomEvent
7315 * @event editorDoubleClick
7316 * @param {Event} ev The DOM Event that occured
7317 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7318 * @type YAHOO.util.CustomEvent
7321 * @event editorClick
7322 * @param {Event} ev The DOM Event that occured
7323 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7324 * @type YAHOO.util.CustomEvent
7327 * @event editorKeyUp
7328 * @param {Event} ev The DOM Event that occured
7329 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7330 * @type YAHOO.util.CustomEvent
7333 * @event editorKeyPress
7334 * @param {Event} ev The DOM Event that occured
7335 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7336 * @type YAHOO.util.CustomEvent
7339 * @event editorKeyDown
7340 * @param {Event} ev The DOM Event that occured
7341 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7342 * @type YAHOO.util.CustomEvent
7345 * @event beforeEditorMouseUp
7346 * @param {Event} ev The DOM Event that occured
7347 * @description Fires before editor event, returning false will stop the internal processing.
7348 * @type YAHOO.util.CustomEvent
7351 * @event beforeEditorMouseDown
7352 * @param {Event} ev The DOM Event that occured
7353 * @description Fires before editor event, returning false will stop the internal processing.
7354 * @type YAHOO.util.CustomEvent
7357 * @event beforeEditorDoubleClick
7358 * @param {Event} ev The DOM Event that occured
7359 * @description Fires before editor event, returning false will stop the internal processing.
7360 * @type YAHOO.util.CustomEvent
7363 * @event beforeEditorClick
7364 * @param {Event} ev The DOM Event that occured
7365 * @description Fires before editor event, returning false will stop the internal processing.
7366 * @type YAHOO.util.CustomEvent
7369 * @event beforeEditorKeyUp
7370 * @param {Event} ev The DOM Event that occured
7371 * @description Fires before editor event, returning false will stop the internal processing.
7372 * @type YAHOO.util.CustomEvent
7375 * @event beforeEditorKeyPress
7376 * @param {Event} ev The DOM Event that occured
7377 * @description Fires before editor event, returning false will stop the internal processing.
7378 * @type YAHOO.util.CustomEvent
7381 * @event beforeEditorKeyDown
7382 * @param {Event} ev The DOM Event that occured
7383 * @description Fires before editor event, returning false will stop the internal processing.
7384 * @type YAHOO.util.CustomEvent
7388 * @event editorWindowFocus
7389 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7390 * @type YAHOO.util.CustomEvent
7393 * @event editorWindowBlur
7394 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7395 * @type YAHOO.util.CustomEvent
7400 * @description Singleton object used to track the open window objects and panels across the various open editors
7404 YAHOO.widget.EditorInfo = {
7407 * @property _instances
7408 * @description A reference to all editors on the page.
7414 * @property blankImage
7415 * @description A reference to the blankImage url
7422 * @description A reference to the currently open window object in any editor on the page.
7423 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7429 * @description A reference to the currently open panel in any editor on the page.
7430 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7434 * @method getEditorById
7435 * @description Returns a reference to the Editor object associated with the given textarea
7436 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7437 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7439 getEditorById: function(id) {
7440 if (!YAHOO.lang.isString(id)) {
7441 //Not a string, assume a node Reference
7444 if (this._instances[id]) {
7445 return this._instances[id];
7451 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved.
7452 * @param {HTMLElement} form The form to check if this Editor instance belongs to
7454 saveAll: function(form) {
7455 var i, e, items = YAHOO.widget.EditorInfo._instances;
7458 if (Lang.hasOwnProperty(items, i)) {
7460 if (e.get('element').form && (e.get('element').form == form)) {
7467 if (Lang.hasOwnProperty(items, i)) {
7468 items[i].saveHTML();
7475 * @description Returns a string representing the EditorInfo.
7478 toString: function() {
7480 for (var i in this._instances) {
7481 if (Lang.hasOwnProperty(this._instances, i)) {
7485 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7493 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.8.0r4", build: "2449"});