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) {
70 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
73 var local_attrs = (attrs || {});
77 attributes: local_attrs
80 if (!oConfig.attributes.type) {
81 oConfig.attributes.type = 'push';
84 oConfig.element = document.createElement('span');
85 oConfig.element.setAttribute('unselectable', 'on');
86 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
87 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
88 oConfig.element.firstChild.firstChild.tabIndex = '-1';
89 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId());
90 oConfig.element.id = oConfig.attributes.id;
92 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
95 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
97 * @property buttonType
99 * @description Tells if the Button is a Rich Button or a Simple Button
101 buttonType: 'normal',
103 * @method _handleMouseOver
105 * @description Adds classes to the button elements on mouseover (hover)
107 _handleMouseOver: function() {
108 if (!this.get('disabled')) {
109 this.addClass('yui-button-hover');
110 this.addClass('yui-' + this.get('type') + '-button-hover');
114 * @method _handleMouseOut
116 * @description Removes classes from the button elements on mouseout (hover)
118 _handleMouseOut: function() {
119 this.removeClass('yui-button-hover');
120 this.removeClass('yui-' + this.get('type') + '-button-hover');
124 * @param {String} value The value of the option that we want to mark as selected
125 * @description Select an option by value
127 checkValue: function(value) {
128 if (this.get('type') == 'menu') {
129 var opts = this._button.options;
130 for (var i = 0; i < opts.length; i++) {
131 if (opts[i].value == value) {
132 opts.selectedIndex = i;
139 * @description The ToolbarButton class's initialization method
141 init: function(p_oElement, p_oAttributes) {
142 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
144 this.on('mouseover', this._handleMouseOver, this, true);
145 this.on('mouseout', this._handleMouseOut, this, true);
146 this.on('click', function(ev) {
152 * @method initAttributes
153 * @description Initializes all of the configuration attributes used to create
155 * @param {Object} attr Object literal specifying a set of
156 * configuration attributes used to create the toolbar.
158 initAttributes: function(attr) {
159 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
162 * @description The value of the button
165 this.setAttributeConfig('value', {
170 * @description The menu attribute, see YAHOO.widget.Button
173 this.setAttributeConfig('menu', {
174 value: attr.menu || false
178 * @description The type of button to create: push, menu, color, select, spin
181 this.setAttributeConfig('type', {
184 method: function(type) {
187 this._button = this.get('element').getElementsByTagName('a')[0];
192 el = document.createElement('select');
193 el.id = this.get('id');
194 var menu = this.get('menu');
195 for (var i = 0; i < menu.length; i++) {
196 opt = document.createElement('option');
197 opt.innerHTML = menu[i].text;
198 opt.value = menu[i].value;
199 if (menu[i].checked) {
204 this._button.parentNode.replaceChild(el, this._button);
205 Event.on(el, 'change', this._handleSelect, this, true);
213 * @attribute disabled
214 * @description Set the button into a disabled state
217 this.setAttributeConfig('disabled', {
218 value: attr.disabled || false,
219 method: function(disabled) {
221 this.addClass('yui-button-disabled');
222 this.addClass('yui-' + this.get('type') + '-button-disabled');
224 this.removeClass('yui-button-disabled');
225 this.removeClass('yui-' + this.get('type') + '-button-disabled');
227 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) {
228 this._button.disabled = disabled;
235 * @description The text label for the button
238 this.setAttributeConfig('label', {
240 method: function(label) {
242 this._button = this.get('element').getElementsByTagName('a')[0];
244 if (this.get('type') == 'push') {
245 this._button.innerHTML = label;
252 * @description The title of the button
255 this.setAttributeConfig('title', {
261 * @description The container that the button is rendered to, handled by Toolbar
264 this.setAttributeConfig('container', {
267 method: function(cont) {
275 * @method _handleSelect
276 * @description The event fired when a change event gets fired on a select element
277 * @param {Event} ev The change event.
279 _handleSelect: function(ev) {
280 var tar = Event.getTarget(ev);
281 var value = tar.options[tar.selectedIndex].value;
282 this.fireEvent('change', {type: 'change', value: value });
286 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
288 getMenu: function() {
289 return this.get('menu');
293 * @description Destroy the button
295 destroy: function() {
296 Event.purgeElement(this.get('element'), true);
297 this.get('element').parentNode.removeChild(this.get('element'));
298 //Brutal Object Destroy
299 for (var i in this) {
300 if (Lang.hasOwnProperty(this, i)) {
307 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
309 fireEvent: function(p_sType, p_aArgs) {
310 // Disabled buttons should not respond to DOM events
311 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
312 Event.stopEvent(p_aArgs);
316 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
320 * @description Returns a string representing the toolbar.
323 toString: function() {
324 return 'ToolbarButton (' + this.get('id') + ')';
331 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
332 * @namespace YAHOO.widget
333 * @requires yahoo, dom, element, event, toolbarbutton
334 * @optional container_core, dragdrop
337 var Dom = YAHOO.util.Dom,
338 Event = YAHOO.util.Event,
341 var getButton = function(id) {
343 if (Lang.isString(id)) {
344 button = this.getButtonById(id);
346 if (Lang.isNumber(id)) {
347 button = this.getButtonByIndex(id);
349 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
350 button = this.getButtonByValue(id);
352 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
359 * Provides a rich toolbar widget based on the button and menu widgets
362 * @extends YAHOO.util.Element
363 * @param {String/HTMLElement} el The element to turn into a toolbar.
364 * @param {Object} attrs Object liternal containing configuration parameters.
366 YAHOO.widget.Toolbar = function(el, attrs) {
368 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
371 var local_attrs = {};
373 Lang.augmentObject(local_attrs, attrs); //Break the config reference
379 attributes: local_attrs
383 if (Lang.isString(el) && Dom.get(el)) {
384 oConfig.element = Dom.get(el);
385 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
386 oConfig.element = Dom.get(el);
390 if (!oConfig.element) {
391 oConfig.element = document.createElement('DIV');
392 oConfig.element.id = Dom.generateId();
394 if (local_attrs.container && Dom.get(local_attrs.container)) {
395 Dom.get(local_attrs.container).appendChild(oConfig.element);
400 if (!oConfig.element.id) {
401 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
404 var fs = document.createElement('fieldset');
405 var lg = document.createElement('legend');
406 lg.innerHTML = 'Toolbar';
409 var cont = document.createElement('DIV');
410 oConfig.attributes.cont = cont;
411 Dom.addClass(cont, 'yui-toolbar-subcont');
412 fs.appendChild(cont);
413 oConfig.element.appendChild(fs);
415 oConfig.element.tabIndex = -1;
418 oConfig.attributes.element = oConfig.element;
419 oConfig.attributes.id = oConfig.element.id;
421 this._configuredButtons = [];
423 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
427 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
430 * @property _configuredButtons
433 _configuredButtons: null,
435 * @method _addMenuClasses
437 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
438 * @param {String} ev The event that fired.
439 * @param {Array} na Array of event information.
440 * @param {Object} o Button config object.
442 _addMenuClasses: function(ev, na, o) {
443 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
444 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
445 Dom.addClass(this.element, 'yui-toolbar-select-menu');
447 var items = this.getItems();
448 for (var i = 0; i < items.length; i++) {
449 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()));
450 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
454 * @property buttonType
455 * @description The default button to use
458 buttonType: YAHOO.widget.ToolbarButton,
461 * @description The DragDrop instance associated with the Toolbar
466 * @property _colorData
467 * @description Object reference containing colors hex and text values.
472 '#111111': 'Obsidian',
473 '#2D2D2D': 'Dark Gray',
477 '#8B8B8B': 'Concrete',
479 '#B9B9B9': 'Titanium',
481 '#D0D0D0': 'Light Gray',
484 '#BFBF00': 'Pumpkin',
487 '#FFFF80': 'Pale Yellow',
489 '#525330': 'Raw Siena',
492 '#7F7F00': 'Paprika',
497 '#80FF00': 'Chartreuse',
499 '#C0FF80': 'Pale Lime',
500 '#DFFFBF': 'Light Mint',
502 '#668F5A': 'Lime Gray',
505 '#8A9B55': 'Pistachio',
506 '#B7C296': 'Light Jade',
507 '#E6EBD5': 'Breakwater',
508 '#00BF00': 'Spring Frost',
509 '#00FF80': 'Pastel Green',
510 '#40FFA0': 'Light Emerald',
511 '#80FFC0': 'Sea Foam',
512 '#BFFFDF': 'Sea Mist',
513 '#033D21': 'Dark Forrest',
515 '#7FA37C': 'Medium Green',
517 '#8DAE94': 'Yellow Gray Green',
518 '#ACC6B5': 'Aqua Lung',
519 '#DDEBE2': 'Sea Vapor',
522 '#40FFFF': 'Turquoise Blue',
523 '#80FFFF': 'Light Aqua',
524 '#BFFFFF': 'Pale Cyan',
525 '#033D3D': 'Dark Teal',
526 '#347D7E': 'Gray Turquoise',
527 '#609A9F': 'Green Blue',
528 '#007F7F': 'Seaweed',
529 '#96BDC4': 'Green Gray',
530 '#B5D1D7': 'Soapstone',
531 '#E2F1F4': 'Light Turquoise',
532 '#0060BF': 'Summer Sky',
533 '#0080FF': 'Sky Blue',
534 '#40A0FF': 'Electric Blue',
535 '#80C0FF': 'Light Azure',
536 '#BFDFFF': 'Ice Blue',
539 '#57708F': 'Dusty Blue',
540 '#00407F': 'Sea Blue',
541 '#7792AC': 'Sky Blue Gray',
542 '#A8BED1': 'Morning Sky',
544 '#0000BF': 'Deep Blue',
546 '#4040FF': 'Cerulean Blue',
547 '#8080FF': 'Evening Blue',
548 '#BFBFFF': 'Light Blue',
549 '#212143': 'Deep Indigo',
550 '#373E68': 'Sea Blue',
551 '#444F75': 'Night Blue',
552 '#00007F': 'Indigo Blue',
553 '#585E82': 'Dockside',
554 '#8687A4': 'Blue Gray',
555 '#D2D1E1': 'Light Blue Gray',
556 '#6000BF': 'Neon Violet',
557 '#8000FF': 'Blue Violet',
558 '#A040FF': 'Violet Purple',
559 '#C080FF': 'Violet Dusk',
560 '#DFBFFF': 'Pale Lavender',
561 '#302449': 'Cool Shale',
562 '#54466F': 'Dark Indigo',
563 '#655A7F': 'Dark Violet',
565 '#726284': 'Smoky Violet',
566 '#9E8FA9': 'Slate Gray',
567 '#DCD1DF': 'Violet White',
568 '#BF00BF': 'Royal Violet',
569 '#FF00FF': 'Fuchsia',
570 '#FF40FF': 'Magenta',
572 '#FFBFFF': 'Pale Magenta',
573 '#4A234A': 'Dark Purple',
574 '#794A72': 'Medium Purple',
575 '#936386': 'Cool Granite',
577 '#9D7292': 'Purple Moon',
578 '#C0A0B6': 'Pale Purple',
579 '#ECDAE5': 'Pink Cloud',
580 '#BF005F': 'Hot Pink',
581 '#FF007F': 'Deep Pink',
583 '#FF80BF': 'Electric Pink',
585 '#451528': 'Purple Red',
586 '#823857': 'Purple Dino',
587 '#A94A76': 'Purple Gray',
589 '#BC6F95': 'Antique Mauve',
590 '#D8A5BB': 'Cool Marble',
591 '#F7DDE9': 'Pink Granite',
593 '#FF0000': 'Fire Truck',
594 '#FF4040': 'Pale Red',
596 '#FFC0C0': 'Warm Pink',
600 '#800000': 'Brick Red',
602 '#D8A3A4': 'Shrimp Pink',
603 '#F8DDDD': 'Shell Pink',
604 '#BF5F00': 'Dark Orange',
606 '#FF9F40': 'Grapefruit',
607 '#FFBF80': 'Canteloupe',
609 '#482C1B': 'Dark Brick',
613 '#C49B71': 'Mustard',
614 '#E1C4A8': 'Pale Tan',
619 * @property _colorPicker
620 * @description The HTML Element containing the colorPicker
625 * @property STR_COLLAPSE
626 * @description String for Toolbar Collapse Button
629 STR_COLLAPSE: 'Collapse Toolbar',
631 * @property STR_EXPAND
632 * @description String for Toolbar Collapse Button - Expand
635 STR_EXPAND: 'Expand Toolbar',
637 * @property STR_SPIN_LABEL
638 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
641 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.',
643 * @property STR_SPIN_UP
644 * @description String for spinbutton up
647 STR_SPIN_UP: 'Click to increase the value of this input',
649 * @property STR_SPIN_DOWN
650 * @description String for spinbutton down
653 STR_SPIN_DOWN: 'Click to decrease the value of this input',
655 * @property _titlebar
656 * @description Object reference to the titlebar
662 * @description Standard browser detection
665 browser: YAHOO.env.ua,
668 * @property _buttonList
669 * @description Internal property list of current buttons in the toolbar
675 * @property _buttonGroupList
676 * @description Internal property list of current button groups in the toolbar
679 _buttonGroupList: null,
683 * @description Internal reference to the separator HTML Element for cloning
689 * @property _sepCount
690 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
696 * @property draghandle
702 * @property _toolbarConfigs
710 * @property CLASS_CONTAINER
711 * @description Default CSS class to apply to the toolbar container element
714 CLASS_CONTAINER: 'yui-toolbar-container',
717 * @property CLASS_DRAGHANDLE
718 * @description Default CSS class to apply to the toolbar's drag handle element
721 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
724 * @property CLASS_SEPARATOR
725 * @description Default CSS class to apply to all separators in the toolbar
728 CLASS_SEPARATOR: 'yui-toolbar-separator',
731 * @property CLASS_DISABLED
732 * @description Default CSS class to apply when the toolbar is disabled
735 CLASS_DISABLED: 'yui-toolbar-disabled',
738 * @property CLASS_PREFIX
739 * @description Default prefix for dynamically created class names
742 CLASS_PREFIX: 'yui-toolbar',
745 * @description The Toolbar class's initialization method
747 init: function(p_oElement, p_oAttributes) {
748 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
751 * @method initAttributes
752 * @description Initializes all of the configuration attributes used to create
754 * @param {Object} attr Object literal specifying a set of
755 * configuration attributes used to create the toolbar.
757 initAttributes: function(attr) {
758 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
759 this.addClass(this.CLASS_CONTAINER);
762 * @attribute buttonType
763 * @description The buttonType to use (advanced or basic)
766 this.setAttributeConfig('buttonType', {
767 value: attr.buttonType || 'basic',
769 validator: function(type) {
777 method: function(type) {
778 if (type == 'advanced') {
779 if (YAHOO.widget.Button) {
780 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
782 this.buttonType = YAHOO.widget.ToolbarButton;
785 this.buttonType = YAHOO.widget.ToolbarButton;
793 * @description Object specifying the buttons to include in the toolbar
797 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
798 * { type: 'separator' },
799 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
801 * { text: "Left", value: 'alignleft' },
802 * { text: "Center", value: 'aligncenter' },
803 * { text: "Right", value: 'alignright' }
811 this.setAttributeConfig('buttons', {
814 method: function(data) {
815 var i, button, buttons, len, b;
817 if (Lang.hasOwnProperty(data, i)) {
818 if (data[i].type == 'separator') {
820 } else if (data[i].group !== undefined) {
821 buttons = this.addButtonGroup(data[i]);
823 len = buttons.length;
824 for(b = 0; b < len; b++) {
826 this._configuredButtons[this._configuredButtons.length] = buttons[b].id;
832 button = this.addButton(data[i]);
834 this._configuredButtons[this._configuredButtons.length] = button.id;
843 * @attribute disabled
844 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
848 this.setAttributeConfig('disabled', {
850 method: function(disabled) {
851 if (this.get('disabled') === disabled) {
855 this.addClass(this.CLASS_DISABLED);
856 this.set('draggable', false);
857 this.disableAllButtons();
859 this.removeClass(this.CLASS_DISABLED);
860 if (this._configs.draggable._initialConfig.value) {
861 //Draggable by default, set it back
862 this.set('draggable', true);
864 this.resetAllButtons();
871 * @description The container for the toolbar.
874 this.setAttributeConfig('cont', {
881 * @attribute grouplabels
882 * @description Boolean indicating if the toolbar should show the group label's text string.
886 this.setAttributeConfig('grouplabels', {
887 value: ((attr.grouplabels === false) ? false : true),
888 method: function(grouplabels) {
890 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
892 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
897 * @attribute titlebar
898 * @description Boolean indicating if the toolbar should have a titlebar. If
899 * passed a string, it will use that as the titlebar text
901 * @type Boolean or String
903 this.setAttributeConfig('titlebar', {
905 method: function(titlebar) {
907 if (this._titlebar && this._titlebar.parentNode) {
908 this._titlebar.parentNode.removeChild(this._titlebar);
910 this._titlebar = document.createElement('DIV');
911 this._titlebar.tabIndex = '-1';
912 Event.on(this._titlebar, 'focus', function() {
915 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
916 if (Lang.isString(titlebar)) {
917 var h2 = document.createElement('h2');
919 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
920 this._titlebar.appendChild(h2);
921 Event.on(h2.firstChild, 'click', function(ev) {
924 Event.on([h2, h2.firstChild], 'focus', function() {
928 if (this.get('firstChild')) {
929 this.insertBefore(this._titlebar, this.get('firstChild'));
931 this.appendChild(this._titlebar);
933 if (this.get('collapse')) {
934 this.set('collapse', true);
936 } else if (this._titlebar) {
937 if (this._titlebar && this._titlebar.parentNode) {
938 this._titlebar.parentNode.removeChild(this._titlebar);
946 * @attribute collapse
947 * @description Boolean indicating if the the titlebar should have a collapse button.
948 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
952 this.setAttributeConfig('collapse', {
954 method: function(collapse) {
955 if (this._titlebar) {
956 var collapseEl = null;
957 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
960 //There is already a collapse button
963 collapseEl = document.createElement('SPAN');
964 collapseEl.innerHTML = 'X';
965 collapseEl.title = this.STR_COLLAPSE;
967 Dom.addClass(collapseEl, 'collapse');
968 this._titlebar.appendChild(collapseEl);
969 Event.addListener(collapseEl, 'click', function() {
970 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
971 this.collapse(false); //Expand Toolbar
973 this.collapse(); //Collapse Toolbar
977 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
979 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
980 //We are closed, reopen the titlebar..
981 this.collapse(false); //Expand Toolbar
983 collapseEl[0].parentNode.removeChild(collapseEl[0]);
991 * @attribute draggable
992 * @description Boolean indicating if the toolbar should be draggable.
997 this.setAttributeConfig('draggable', {
998 value: (attr.draggable || false),
999 method: function(draggable) {
1000 if (draggable && !this.get('titlebar')) {
1001 if (!this._dragHandle) {
1002 this._dragHandle = document.createElement('SPAN');
1003 this._dragHandle.innerHTML = '|';
1004 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
1005 this._dragHandle.id = this.get('id') + '_draghandle';
1006 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
1007 if (this.get('cont').hasChildNodes()) {
1008 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
1010 this.get('cont').appendChild(this._dragHandle);
1012 this.dd = new YAHOO.util.DD(this.get('id'));
1013 this.dd.setHandleElId(this._dragHandle.id);
1017 if (this._dragHandle) {
1018 this._dragHandle.parentNode.removeChild(this._dragHandle);
1019 this._dragHandle = null;
1023 if (this._titlebar) {
1025 this.dd = new YAHOO.util.DD(this.get('id'));
1026 this.dd.setHandleElId(this._titlebar);
1027 Dom.addClass(this._titlebar, 'draggable');
1029 Dom.removeClass(this._titlebar, 'draggable');
1037 validator: function(value) {
1039 if (!YAHOO.util.DD) {
1048 * @method addButtonGroup
1049 * @description Add a new button group to the toolbar. (uses addButton)
1050 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1052 addButtonGroup: function(oGroup) {
1053 if (!this.get('element')) {
1054 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1058 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1059 this.addClass(this.CLASS_PREFIX + '-grouped');
1061 var div = document.createElement('DIV');
1062 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1063 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1065 var label = document.createElement('h3');
1066 label.innerHTML = oGroup.label;
1067 div.appendChild(label);
1069 if (!this.get('grouplabels')) {
1070 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1073 this.get('cont').appendChild(div);
1075 //For accessibility, let's put all of the group buttons in an Unordered List
1076 var ul = document.createElement('ul');
1077 div.appendChild(ul);
1079 if (!this._buttonGroupList) {
1080 this._buttonGroupList = {};
1083 this._buttonGroupList[oGroup.group] = ul;
1085 //An array of the button ids added to this group
1086 //This is used for destruction later...
1087 var addedButtons = [],
1091 for (var i = 0; i < oGroup.buttons.length; i++) {
1092 var li = document.createElement('li');
1093 li.className = this.CLASS_PREFIX + '-groupitem';
1095 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1096 this.addSeparator(li);
1098 oGroup.buttons[i].container = li;
1099 button = this.addButton(oGroup.buttons[i]);
1101 addedButtons[addedButtons.length] = button.id;
1105 return addedButtons;
1108 * @method addButtonToGroup
1109 * @description Add a new button to a toolbar group. Buttons supported:
1110 * push, split, menu, select, color, spin
1111 * @param {Object} oButton Object literal reference to the Button's Config
1112 * @param {String} group The Group identifier passed into the initial config
1113 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1115 addButtonToGroup: function(oButton, group, after) {
1116 var groupCont = this._buttonGroupList[group],
1117 li = document.createElement('li');
1119 li.className = this.CLASS_PREFIX + '-groupitem';
1120 oButton.container = li;
1121 this.addButton(oButton, after);
1122 groupCont.appendChild(li);
1126 * @description Add a new button to the toolbar. Buttons supported:
1127 * push, split, menu, select, color, spin
1128 * @param {Object} oButton Object literal reference to the Button's Config
1129 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1131 addButton: function(oButton, after) {
1132 if (!this.get('element')) {
1133 this._queue[this._queue.length] = ['addButton', arguments];
1136 if (!this._buttonList) {
1137 this._buttonList = [];
1139 if (!oButton.container) {
1140 oButton.container = this.get('cont');
1143 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1144 if (Lang.isArray(oButton.menu)) {
1145 for (var i in oButton.menu) {
1146 if (Lang.hasOwnProperty(oButton.menu, i)) {
1148 fn: function(ev, x, oMenu) {
1149 if (!oButton.menucmd) {
1150 oButton.menucmd = oButton.value;
1152 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1156 oButton.menu[i].onclick = funcObject;
1161 var _oButton = {}, skip = false;
1162 for (var o in oButton) {
1163 if (Lang.hasOwnProperty(oButton, o)) {
1164 if (!this._toolbarConfigs[o]) {
1165 _oButton[o] = oButton[o];
1169 if (oButton.type == 'select') {
1170 _oButton.type = 'menu';
1172 if (oButton.type == 'spin') {
1173 _oButton.type = 'push';
1175 if (_oButton.type == 'color') {
1176 if (YAHOO.widget.Overlay) {
1177 _oButton = this._makeColorButton(_oButton);
1182 if (_oButton.menu) {
1183 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1184 oButton.menu.showEvent.subscribe(function() {
1185 this._button = _oButton;
1188 for (var m = 0; m < _oButton.menu.length; m++) {
1189 if (!_oButton.menu[m].value) {
1190 _oButton.menu[m].value = _oButton.menu[m].text;
1193 if (this.browser.webkit) {
1194 _oButton.focusmenu = false;
1201 //Add to .get('buttons') manually
1202 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1204 var tmp = new this.buttonType(_oButton);
1205 tmp.get('element').tabIndex = '-1';
1206 tmp.get('element').setAttribute('role', 'button');
1207 tmp._selected = true;
1209 if (this.get('disabled')) {
1210 //Toolbar is disabled, disable the new button too!
1211 tmp.set('disabled', true);
1214 oButton.id = tmp.get('id');
1218 var el = tmp.get('element');
1221 nextSib = after.get('element').nextSibling;
1222 } else if (after.nextSibling) {
1223 nextSib = after.nextSibling;
1226 nextSib.parentNode.insertBefore(el, nextSib);
1229 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1231 var icon = document.createElement('span');
1232 icon.className = this.CLASS_PREFIX + '-icon';
1233 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1234 if (tmp._button.tagName.toLowerCase() == 'button') {
1235 tmp.get('element').setAttribute('unselectable', 'on');
1236 //Replace the Button HTML Element with an a href if it exists
1237 var a = document.createElement('a');
1238 a.innerHTML = tmp._button.innerHTML;
1241 Event.on(a, 'click', function(ev) {
1242 Event.stopEvent(ev);
1244 tmp._button.parentNode.replaceChild(a, tmp._button);
1248 if (oButton.type == 'select') {
1249 if (tmp._button.tagName.toLowerCase() == 'select') {
1250 icon.parentNode.removeChild(icon);
1251 var iel = tmp._button,
1252 parEl = tmp.get('element');
1253 parEl.parentNode.replaceChild(iel, parEl);
1254 //The 'element' value is currently the orphaned element
1255 //In order for "destroy" to execute we need to get('element') to reference the correct node.
1256 //I'm not sure if there is a direct approach to setting this value.
1257 tmp._configs.element.value = iel;
1259 //Don't put a class on it if it's a real select element
1260 tmp.addClass(this.CLASS_PREFIX + '-select');
1263 if (oButton.type == 'spin') {
1264 if (!Lang.isArray(oButton.range)) {
1265 oButton.range = [ 10, 100 ];
1267 this._makeSpinButton(tmp, oButton);
1269 tmp.get('element').setAttribute('title', tmp.get('label'));
1270 if (oButton.type != 'spin') {
1271 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1272 var showPicker = function(ev) {
1274 if (ev.keyCode && (ev.keyCode == 9)) {
1278 if (this._colorPicker) {
1279 this._colorPicker._button = oButton.value;
1281 var menuEL = tmp.getMenu().element;
1282 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1283 tmp.getMenu().show();
1285 tmp.getMenu().hide();
1288 YAHOO.util.Event.stopEvent(ev);
1290 tmp.on('mousedown', showPicker, oButton, this);
1291 tmp.on('keydown', showPicker, oButton, this);
1293 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1294 tmp.on('keypress', this._buttonClick, oButton, this);
1295 tmp.on('mousedown', function(ev) {
1296 YAHOO.util.Event.stopEvent(ev);
1297 this._buttonClick(ev, oButton);
1299 tmp.on('click', function(ev) {
1300 YAHOO.util.Event.stopEvent(ev);
1303 //Stop the mousedown event so we can trap the selection in the editor!
1304 tmp.on('mousedown', function(ev) {
1305 YAHOO.util.Event.stopEvent(ev);
1307 tmp.on('click', function(ev) {
1308 YAHOO.util.Event.stopEvent(ev);
1310 tmp.on('change', function(ev) {
1312 if (!oButton.menucmd) {
1313 oButton.menucmd = oButton.value;
1315 oButton.value = ev.value;
1316 this._buttonClick(ev, oButton);
1321 //Hijack the mousedown event in the menu and make it fire a button click..
1322 tmp.on('appendTo', function() {
1324 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1325 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1326 var oMenu = args[1];
1327 YAHOO.util.Event.stopEvent(args[0]);
1328 tmp._onMenuClick(args[0], tmp);
1329 if (!oButton.menucmd) {
1330 oButton.menucmd = oButton.value;
1332 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1333 self._buttonClick.call(self, args[1], oButton);
1337 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1338 YAHOO.util.Event.stopEvent(args[0]);
1340 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1341 YAHOO.util.Event.stopEvent(args[0]);
1348 //Stop the mousedown event so we can trap the selection in the editor!
1349 tmp.on('mousedown', function(ev) {
1350 YAHOO.util.Event.stopEvent(ev);
1352 tmp.on('click', function(ev) {
1353 YAHOO.util.Event.stopEvent(ev);
1356 if (this.browser.ie) {
1358 //Add a couple of new events for IE
1359 tmp.DOM_EVENTS.focusin = true;
1360 tmp.DOM_EVENTS.focusout = true;
1362 //Stop them so we don't loose focus in the Editor
1363 tmp.on('focusin', function(ev) {
1364 YAHOO.util.Event.stopEvent(ev);
1367 tmp.on('focusout', function(ev) {
1368 YAHOO.util.Event.stopEvent(ev);
1370 tmp.on('click', function(ev) {
1371 YAHOO.util.Event.stopEvent(ev);
1375 if (this.browser.webkit) {
1376 //This will keep the document from gaining focus and the editor from loosing it..
1377 //Forcefully remove the focus calls in button!
1378 tmp.hasFocus = function() {
1382 this._buttonList[this._buttonList.length] = tmp;
1383 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1384 if (Lang.isArray(oButton.menu)) {
1385 var menu = tmp.getMenu();
1386 if (menu && menu.renderEvent) {
1387 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1388 if (oButton.renderer) {
1389 menu.renderEvent.subscribe(oButton.renderer, tmp);
1398 * @method addSeparator
1399 * @description Add a new button separator to the toolbar.
1400 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1401 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1403 addSeparator: function(cont, after) {
1404 if (!this.get('element')) {
1405 this._queue[this._queue.length] = ['addSeparator', arguments];
1408 var sepCont = ((cont) ? cont : this.get('cont'));
1409 if (!this.get('element')) {
1410 this._queue[this._queue.length] = ['addSeparator', arguments];
1413 if (this._sepCount === null) {
1417 this._sep = document.createElement('SPAN');
1418 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1419 this._sep.innerHTML = '|';
1421 var _sep = this._sep.cloneNode(true);
1423 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1427 nextSib = after.get('element').nextSibling;
1428 } else if (after.nextSibling) {
1429 nextSib = after.nextSibling;
1434 if (nextSib == after) {
1435 nextSib.parentNode.appendChild(_sep);
1437 nextSib.parentNode.insertBefore(_sep, nextSib);
1441 sepCont.appendChild(_sep);
1446 * @method _createColorPicker
1448 * @description Creates the core DOM reference to the color picker menu item.
1449 * @param {String} id the id of the toolbar to prefix this DOM container with.
1451 _createColorPicker: function(id) {
1452 if (Dom.get(id + '_colors')) {
1453 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1455 var picker = document.createElement('div');
1456 picker.className = 'yui-toolbar-colors';
1457 picker.id = id + '_colors';
1458 picker.style.display = 'none';
1459 Event.on(window, 'load', function() {
1460 document.body.appendChild(picker);
1463 this._colorPicker = picker;
1466 for (var i in this._colorData) {
1467 if (Lang.hasOwnProperty(this._colorData, i)) {
1468 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1471 html += '<span><em>X</em><strong></strong></span>';
1472 window.setTimeout(function() {
1473 picker.innerHTML = html;
1476 Event.on(picker, 'mouseover', function(ev) {
1477 var picker = this._colorPicker;
1478 var em = picker.getElementsByTagName('em')[0];
1479 var strong = picker.getElementsByTagName('strong')[0];
1480 var tar = Event.getTarget(ev);
1481 if (tar.tagName.toLowerCase() == 'a') {
1482 em.style.backgroundColor = tar.style.backgroundColor;
1483 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1486 Event.on(picker, 'focus', function(ev) {
1487 Event.stopEvent(ev);
1489 Event.on(picker, 'click', function(ev) {
1490 Event.stopEvent(ev);
1492 Event.on(picker, 'mousedown', function(ev) {
1493 Event.stopEvent(ev);
1494 var tar = Event.getTarget(ev);
1495 if (tar.tagName.toLowerCase() == 'a') {
1496 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1497 if (retVal !== false) {
1499 color: tar.innerHTML,
1500 colorName: this._colorData['#' + tar.innerHTML],
1501 value: this._colorPicker._button
1504 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1506 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1511 * @method _resetColorPicker
1513 * @description Clears the currently selected color or mouseover color in the color picker.
1515 _resetColorPicker: function() {
1516 var em = this._colorPicker.getElementsByTagName('em')[0];
1517 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1518 em.style.backgroundColor = 'transparent';
1519 strong.innerHTML = '';
1522 * @method _makeColorButton
1524 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1525 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1527 _makeColorButton: function(_oButton) {
1528 if (!this._colorPicker) {
1529 this._createColorPicker(this.get('id'));
1531 _oButton.type = 'color';
1532 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1533 _oButton.menu.setBody('');
1534 _oButton.menu.render(this.get('cont'));
1535 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1536 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1537 _oButton.menu.beforeShowEvent.subscribe(function() {
1538 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1539 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1540 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1541 this._resetColorPicker();
1542 var _p = this._colorPicker;
1543 if (_p.parentNode) {
1544 _p.parentNode.removeChild(_p);
1546 _oButton.menu.setBody('');
1547 _oButton.menu.appendToBody(_p);
1548 this._colorPicker.style.display = 'block';
1554 * @method _makeSpinButton
1555 * @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.
1556 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1557 * @param {Object} oButton Object literal containing the buttons initial config
1559 _makeSpinButton: function(_button, oButton) {
1560 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1562 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1563 range = oButton.range,
1564 _b1 = document.createElement('a'),
1565 _b2 = document.createElement('a');
1568 _b1.tabIndex = '-1';
1569 _b2.tabIndex = '-1';
1571 //Setup the up and down arrows
1572 _b1.className = 'up';
1573 _b1.title = this.STR_SPIN_UP;
1574 _b1.innerHTML = this.STR_SPIN_UP;
1575 _b2.className = 'down';
1576 _b2.title = this.STR_SPIN_DOWN;
1577 _b2.innerHTML = this.STR_SPIN_DOWN;
1579 //Append them to the container
1580 _par.appendChild(_b1);
1581 _par.appendChild(_b2);
1583 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1584 _button.set('title', label);
1586 var cleanVal = function(value) {
1587 value = ((value < range[0]) ? range[0] : value);
1588 value = ((value > range[1]) ? range[1] : value);
1592 var br = this.browser;
1594 var strLabel = this.STR_SPIN_LABEL;
1595 if (this._titlebar && this._titlebar.firstChild) {
1596 tbar = this._titlebar.firstChild;
1599 var _intUp = function(ev) {
1600 YAHOO.util.Event.stopEvent(ev);
1601 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1602 var value = parseInt(_button.get('label'), 10);
1604 value = cleanVal(value);
1605 _button.set('label', ''+value);
1606 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1607 _button.set('title', label);
1608 if (!br.webkit && tbar) {
1609 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1612 self._buttonClick(ev, oButton);
1616 var _intDown = function(ev) {
1617 YAHOO.util.Event.stopEvent(ev);
1618 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1619 var value = parseInt(_button.get('label'), 10);
1621 value = cleanVal(value);
1623 _button.set('label', ''+value);
1624 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1625 _button.set('title', label);
1626 if (!br.webkit && tbar) {
1627 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1630 self._buttonClick(ev, oButton);
1634 var _intKeyUp = function(ev) {
1635 if (ev.keyCode == 38) {
1637 } else if (ev.keyCode == 40) {
1639 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1641 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1646 //Handle arrow keys..
1647 _button.on('keydown', _intKeyUp, this, true);
1649 //Listen for the click on the up button and act on it
1650 //Listen for the click on the down button and act on it
1651 Event.on(_b1, 'mousedown',function(ev) {
1652 Event.stopEvent(ev);
1654 Event.on(_b2, 'mousedown', function(ev) {
1655 Event.stopEvent(ev);
1657 Event.on(_b1, 'click', _intUp, this, true);
1658 Event.on(_b2, 'click', _intDown, this, true);
1662 * @method _buttonClick
1663 * @description Click handler for all buttons in the toolbar.
1664 * @param {String} ev The event that was passed in.
1665 * @param {Object} info Object literal of information about the button that was clicked.
1667 _buttonClick: function(ev, info) {
1670 if (ev && ev.type == 'keypress') {
1671 if (ev.keyCode == 9) {
1673 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1680 var fireNextEvent = true,
1683 info.isSelected = this.isSelected(info.id);
1686 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1687 if (retValue === false) {
1688 fireNextEvent = false;
1692 if (info.menucmd && fireNextEvent) {
1693 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1694 if (retValue === false) {
1695 fireNextEvent = false;
1698 if (fireNextEvent) {
1699 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1702 if (info.type == 'select') {
1703 var button = this.getButtonById(info.id);
1704 if (button.buttonType == 'rich') {
1705 var txt = info.value;
1706 for (var i = 0; i < info.menu.length; i++) {
1707 if (info.menu[i].value == info.value) {
1708 txt = info.menu[i].text;
1712 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1713 var _items = button.getMenu().getItems();
1714 for (var m = 0; m < _items.length; m++) {
1715 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1716 _items[m].cfg.setProperty('checked', true);
1718 _items[m].cfg.setProperty('checked', false);
1724 Event.stopEvent(ev);
1731 * @description Flag to determine if the arrow nav listeners have been attached
1737 * @property _navCounter
1738 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1744 * @method _navigateButtons
1745 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1746 * @param {Event} ev The Key Event
1748 _navigateButtons: function(ev) {
1749 switch (ev.keyCode) {
1752 if (ev.keyCode == 37) {
1757 if (this._navCounter > (this._buttonList.length - 1)) {
1758 this._navCounter = 0;
1760 if (this._navCounter < 0) {
1761 this._navCounter = (this._buttonList.length - 1);
1763 if (this._buttonList[this._navCounter]) {
1764 var el = this._buttonList[this._navCounter].get('element');
1765 if (this.browser.ie) {
1766 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1768 if (this._buttonList[this._navCounter].get('disabled')) {
1769 this._navigateButtons(ev);
1779 * @method _handleFocus
1780 * @description Sets up the listeners for the arrow key navigation
1782 _handleFocus: function() {
1783 if (!this._keyNav) {
1784 var ev = 'keypress';
1785 if (this.browser.ie) {
1788 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1789 this._keyNav = true;
1790 this._navCounter = -1;
1794 * @method getButtonById
1795 * @description Gets a button instance from the toolbar by is Dom id.
1796 * @param {String} id The Dom id to query for.
1797 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1799 getButtonById: function(id) {
1800 var len = this._buttonList.length;
1801 for (var i = 0; i < len; i++) {
1802 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1803 return this._buttonList[i];
1809 * @method getButtonByValue
1810 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1811 * @param {String} value The button value to query for.
1812 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1814 getButtonByValue: function(value) {
1815 var _buttons = this.get('buttons');
1819 var len = _buttons.length;
1820 for (var i = 0; i < len; i++) {
1821 if (_buttons[i].group !== undefined) {
1822 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1823 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1824 return this.getButtonById(_buttons[i].buttons[m].id);
1826 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1827 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1828 if (_buttons[i].buttons[m].menu[s].value == value) {
1829 return this.getButtonById(_buttons[i].buttons[m].id);
1835 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1836 return this.getButtonById(_buttons[i].id);
1838 if (_buttons[i].menu) { //Menu Button, loop through the values
1839 for (var j = 0; j < _buttons[i].menu.length; j++) {
1840 if (_buttons[i].menu[j].value == value) {
1841 return this.getButtonById(_buttons[i].id);
1850 * @method getButtonByIndex
1851 * @description Gets a button instance from the toolbar by is index in _buttonList.
1852 * @param {Number} index The index of the button in _buttonList.
1853 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1855 getButtonByIndex: function(index) {
1856 if (this._buttonList[index]) {
1857 return this._buttonList[index];
1863 * @method getButtons
1864 * @description Returns an array of buttons in the current toolbar
1867 getButtons: function() {
1868 return this._buttonList;
1871 * @method disableButton
1872 * @description Disables a button in the toolbar.
1873 * @param {String/Number} id Disable a button by it's id, index or value.
1876 disableButton: function(id) {
1877 var button = getButton.call(this, id);
1879 button.set('disabled', true);
1885 * @method enableButton
1886 * @description Enables a button in the toolbar.
1887 * @param {String/Number} id Enable a button by it's id, index or value.
1890 enableButton: function(id) {
1891 if (this.get('disabled')) {
1894 var button = getButton.call(this, id);
1896 if (button.get('disabled')) {
1897 button.set('disabled', false);
1904 * @method isSelected
1905 * @description Tells if a button is selected or not.
1906 * @param {String/Number} id A button by it's id, index or value.
1909 isSelected: function(id) {
1910 var button = getButton.call(this, id);
1912 return button._selected;
1917 * @method selectButton
1918 * @description Selects a button in the toolbar.
1919 * @param {String/Number} id Select a button by it's id, index or value.
1920 * @param {String} value If this is a Menu Button, check this item in the menu
1923 selectButton: function(id, value) {
1924 var button = getButton.call(this, id);
1926 button.addClass('yui-button-selected');
1927 button.addClass('yui-button-' + button.get('value') + '-selected');
1928 button._selected = true;
1930 if (button.buttonType == 'rich') {
1931 var _items = button.getMenu().getItems();
1932 for (var m = 0; m < _items.length; m++) {
1933 if (_items[m].value == value) {
1934 _items[m].cfg.setProperty('checked', true);
1935 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1937 _items[m].cfg.setProperty('checked', false);
1947 * @method deselectButton
1948 * @description Deselects a button in the toolbar.
1949 * @param {String/Number} id Deselect a button by it's id, index or value.
1952 deselectButton: function(id) {
1953 var button = getButton.call(this, id);
1955 button.removeClass('yui-button-selected');
1956 button.removeClass('yui-button-' + button.get('value') + '-selected');
1957 button.removeClass('yui-button-hover');
1958 button._selected = false;
1964 * @method deselectAllButtons
1965 * @description Deselects all buttons in the toolbar.
1968 deselectAllButtons: function() {
1969 var len = this._buttonList.length;
1970 for (var i = 0; i < len; i++) {
1971 this.deselectButton(this._buttonList[i]);
1975 * @method disableAllButtons
1976 * @description Disables all buttons in the toolbar.
1979 disableAllButtons: function() {
1980 if (this.get('disabled')) {
1983 var len = this._buttonList.length;
1984 for (var i = 0; i < len; i++) {
1985 this.disableButton(this._buttonList[i]);
1989 * @method enableAllButtons
1990 * @description Enables all buttons in the toolbar.
1993 enableAllButtons: function() {
1994 if (this.get('disabled')) {
1997 var len = this._buttonList.length;
1998 for (var i = 0; i < len; i++) {
1999 this.enableButton(this._buttonList[i]);
2003 * @method resetAllButtons
2004 * @description Resets all buttons to their initial state.
2005 * @param {Object} _ex Except these buttons
2008 resetAllButtons: function(_ex) {
2009 if (!Lang.isObject(_ex)) {
2012 if (this.get('disabled') || !this._buttonList) {
2015 var len = this._buttonList.length;
2016 for (var i = 0; i < len; i++) {
2017 var _button = this._buttonList[i];
2019 var disabled = _button._configs.disabled._initialConfig.value;
2020 if (_ex[_button.get('id')]) {
2021 this.enableButton(_button);
2022 this.selectButton(_button);
2025 this.disableButton(_button);
2027 this.enableButton(_button);
2029 this.deselectButton(_button);
2035 * @method destroyButton
2036 * @description Destroy a button in the toolbar.
2037 * @param {String/Number} id Destroy a button by it's id or index.
2040 destroyButton: function(id) {
2041 var button = getButton.call(this, id);
2043 var thisID = button.get('id'),
2044 new_list = [], i = 0,
2045 len = this._buttonList.length;
2049 for (i = 0; i < len; i++) {
2050 if (this._buttonList[i].get('id') != thisID) {
2051 new_list[new_list.length]= this._buttonList[i];
2055 this._buttonList = new_list;
2062 * @description Destroys the toolbar, all of it's elements and objects.
2065 destroy: function() {
2066 var len = this._configuredButtons.length, j, i;
2067 for(b = 0; b < len; b++) {
2068 this.destroyButton(this._configuredButtons[b]);
2071 this._configuredButtons = null;
2073 this.get('element').innerHTML = '';
2074 this.get('element').className = '';
2075 //Brutal Object Destroy
2077 if (Lang.hasOwnProperty(this, i)) {
2085 * @description Programatically collapse the toolbar.
2086 * @param {Boolean} collapse True to collapse, false to expand.
2088 collapse: function(collapse) {
2089 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2090 if (collapse === false) {
2091 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2093 Dom.removeClass(el[0], 'collapsed');
2094 el[0].title = this.STR_COLLAPSE;
2096 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2099 Dom.addClass(el[0], 'collapsed');
2100 el[0].title = this.STR_EXPAND;
2102 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2103 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2108 * @description Returns a string representing the toolbar.
2111 toString: function() {
2112 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2116 * @event buttonClick
2117 * @param {Object} o The object passed to this handler is the button config used to create the button.
2118 * @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.
2119 * @type YAHOO.util.CustomEvent
2123 * @param {Object} o The object passed to this handler is the button config used to create the button.
2124 * @description This is a special dynamic event that is created and dispatched based on the value property
2125 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2129 * { type: 'button', value: 'test', value: 'testButton' }
2132 * With the valueClick event you could subscribe to this buttons click event with this:
2133 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2134 * @type YAHOO.util.CustomEvent
2137 * @event toolbarExpanded
2138 * @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.
2139 * @type YAHOO.util.CustomEvent
2142 * @event toolbarCollapsed
2143 * @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.
2144 * @type YAHOO.util.CustomEvent
2149 * @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>
2150 * @namespace YAHOO.widget
2151 * @requires yahoo, dom, element, event, toolbar
2152 * @optional animation, container_core, resize, dragdrop
2156 var Dom = YAHOO.util.Dom,
2157 Event = YAHOO.util.Event,
2159 Toolbar = YAHOO.widget.Toolbar;
2162 * 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.
2164 * @class SimpleEditor
2165 * @extends YAHOO.util.Element
2166 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2167 * @param {Object} attrs Object liternal containing configuration parameters.
2170 YAHOO.widget.SimpleEditor = function(el, attrs) {
2173 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2174 Lang.augmentObject(o, el); //Break the config reference
2175 el = document.createElement('textarea');
2176 this.DOMReady = true;
2178 var c = Dom.get(o.container);
2181 document.body.appendChild(el);
2185 Lang.augmentObject(o, attrs); //Break the config reference
2194 if (Lang.isString(el)) {
2197 if (oConfig.attributes.id) {
2198 id = oConfig.attributes.id;
2200 this.DOMReady = true;
2201 id = Dom.generateId(el);
2204 oConfig.element = el;
2206 var element_cont = document.createElement('DIV');
2207 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2208 id: id + '_container'
2210 var div = document.createElement('div');
2211 Dom.addClass(div, 'first-child');
2212 oConfig.attributes.element_cont.appendChild(div);
2214 if (!oConfig.attributes.toolbar_cont) {
2215 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2216 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2217 div.appendChild(oConfig.attributes.toolbar_cont);
2219 var editorWrapper = document.createElement('DIV');
2220 div.appendChild(editorWrapper);
2221 oConfig.attributes.editor_wrapper = editorWrapper;
2223 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2227 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2230 * @property _resizeConfig
2231 * @description The default config for the Resize Utility
2243 * @method _setupResize
2244 * @description Creates the Resize instance and binds its events.
2246 _setupResize: function() {
2247 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2248 if (this.get('resize')) {
2250 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2251 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2252 this.resize.on('resize', function(args) {
2253 var anim = this.get('animate');
2254 this.set('animate', false);
2255 this.set('width', args.width + 'px');
2256 var h = args.height,
2257 th = (this.toolbar.get('element').clientHeight + 2),
2260 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2262 var newH = (h - th - dh);
2263 this.set('height', newH + 'px');
2264 this.get('element_cont').setStyle('height', '');
2265 this.set('animate', anim);
2271 * @description A reference to the Resize object
2272 * @type YAHOO.util.Resize
2278 * @description Sets up the DD instance used from the 'drag' config option.
2280 _setupDD: function() {
2281 if (!YAHOO.util.DD) { return false; }
2282 if (this.get('drag')) {
2283 var d = this.get('drag'),
2285 if (d === 'proxy') {
2286 dd = YAHOO.util.DDProxy;
2289 this.dd = new dd(this.get('element_cont').get('element'));
2290 this.toolbar.addClass('draggable');
2291 this.dd.setHandleElId(this.toolbar._titlebar);
2296 * @description A reference to the DragDrop object.
2297 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2302 * @property _lastCommand
2303 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2307 _undoNodeChange: function() {},
2308 _storeUndo: function() {},
2312 * @description Checks a keyMap entry against a key event
2313 * @param {Object} k The _keyMap object
2314 * @param {Event} e The Mouse Event
2317 _checkKey: function(k, e) {
2319 if ((e.keyCode === k.key)) {
2320 if (k.mods && (k.mods.length > 0)) {
2322 for (var i = 0; i < k.mods.length; i++) {
2323 if (this.browser.mac) {
2324 if (k.mods[i] == 'ctrl') {
2328 if (e[k.mods[i] + 'Key'] === true) {
2332 if (val === k.mods.length) {
2344 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2345 * 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.
2346 * @type {Object/Mixed}
2355 mods: ['shift', 'ctrl']
2366 mods: ['shift', 'ctrl']
2370 mods: ['shift', 'ctrl']
2374 mods: ['shift', 'ctrl']
2378 mods: ['shift', 'ctrl']
2382 mods: ['shift', 'ctrl']
2386 mods: ['shift', 'ctrl']
2394 mods: ['shift', 'ctrl']
2398 mods: ['shift', 'ctrl']
2402 mods: ['shift', 'ctrl']
2406 mods: ['shift', 'ctrl']
2411 * @method _cleanClassName
2412 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2413 * @param {String} str The classname to clean up
2416 _cleanClassName: function(str) {
2417 return str.replace(/ /g, '-').toLowerCase();
2420 * @property _textarea
2421 * @description Flag to determine if we are using a textarea or an HTML Node.
2426 * @property _docType
2427 * @description The DOCTYPE to use in the editable container.
2430 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2432 * @property editorDirty
2433 * @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.
2438 * @property _defaultCSS
2439 * @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' }
2442 _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; }',
2444 * @property _defaultToolbar
2446 * @description Default toolbar config.
2449 _defaultToolbar: null,
2451 * @property _lastButton
2453 * @description The last button pressed, so we don't disable it.
2458 * @property _baseHREF
2460 * @description The base location of the editable page (this page) so that relative paths for image work.
2463 _baseHREF: function() {
2464 var href = document.location.href;
2465 if (href.indexOf('?') !== -1) { //Remove the query string
2466 href = href.substring(0, href.indexOf('?'));
2468 href = href.substring(0, href.lastIndexOf('/')) + '/';
2472 * @property _lastImage
2474 * @description Safari reference for the last image selected (for styling as selected).
2479 * @property _blankImageLoaded
2481 * @description Don't load the blank image more than once..
2484 _blankImageLoaded: null,
2486 * @property _fixNodesTimer
2488 * @description Holder for the fixNodes timer
2491 _fixNodesTimer: null,
2493 * @property _nodeChangeTimer
2495 * @description Holds a reference to the nodeChange setTimeout call
2498 _nodeChangeTimer: null,
2500 * @property _nodeChangeDelayTimer
2502 * @description Holds a reference to the nodeChangeDelay setTimeout call
2505 _nodeChangeDelayTimer: null,
2507 * @property _lastNodeChangeEvent
2509 * @description Flag to determine the last event that fired a node change
2512 _lastNodeChangeEvent: null,
2514 * @property _lastNodeChange
2516 * @description Flag to determine when the last node change was fired
2521 * @property _rendered
2523 * @description Flag to determine if editor has been rendered or not
2528 * @property DOMReady
2530 * @description Flag to determine if DOM is ready or not
2535 * @property _selection
2537 * @description Holder for caching iframe selections
2544 * @description DOM Element holder for the editor Mask when disabled
2549 * @property _showingHiddenElements
2551 * @description Status of the hidden elements button
2554 _showingHiddenElements: null,
2556 * @property currentWindow
2557 * @description A reference to the currently open EditorWindow
2560 currentWindow: null,
2562 * @property currentEvent
2563 * @description A reference to the current editor event
2568 * @property operaEvent
2570 * @description setTimeout holder for Opera and Image DoubleClick event..
2575 * @property currentFont
2576 * @description A reference to the last font selected from the Toolbar
2581 * @property currentElement
2582 * @description A reference to the current working element in the editor
2585 currentElement: null,
2588 * @description A reference to the dompath container for writing the current working dom path to.
2593 * @property beforeElement
2594 * @description A reference to the H2 placed before the editor for Accessibilty.
2597 beforeElement: null,
2599 * @property afterElement
2600 * @description A reference to the H2 placed after the editor for Accessibilty.
2605 * @property invalidHTML
2606 * @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.
2624 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2625 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2630 * @property _contentTimer
2631 * @description setTimeout holder for documentReady check
2633 _contentTimer: null,
2636 * @property _contentTimerMax
2637 * @description The number of times the loaded content should be checked before giving up. Default: 500
2639 _contentTimerMax: 500,
2642 * @property _contentTimerCounter
2643 * @description Counter to check the number of times the body is polled for before giving up
2646 _contentTimerCounter: 0,
2649 * @property _disabled
2650 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2653 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2656 * @property _alwaysDisabled
2657 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2660 _alwaysDisabled: { undo: true, redo: true },
2663 * @property _alwaysEnabled
2664 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2667 _alwaysEnabled: { },
2670 * @property _semantic
2671 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2674 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2677 * @property _tag2cmd
2678 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2687 'sup': 'superscript',
2689 'img': 'insertimage',
2691 'ul' : 'insertunorderedlist',
2692 'ol' : 'insertorderedlist'
2696 * @private _createIframe
2697 * @description Creates the DOM and YUI Element for the iFrame editor area.
2698 * @param {String} id The string ID to prefix the iframe with
2699 * @return {Object} iFrame object
2701 _createIframe: function() {
2702 var ifrmDom = document.createElement('iframe');
2703 ifrmDom.id = this.get('id') + '_editor';
2711 allowTransparency: 'true',
2714 if (this.get('autoHeight')) {
2715 config.scrolling = 'no';
2717 for (var i in config) {
2718 if (Lang.hasOwnProperty(config, i)) {
2719 ifrmDom.setAttribute(i, config[i]);
2722 var isrc = 'javascript:;';
2723 if (this.browser.ie) {
2724 //isrc = 'about:blank';
2725 //TODO - Check this, I have changed it before..
2726 isrc = 'javascript:false;';
2728 ifrmDom.setAttribute('src', isrc);
2729 var ifrm = new YAHOO.util.Element(ifrmDom);
2730 ifrm.setStyle('visibility', 'hidden');
2734 * @private _isElement
2735 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2736 * @param {HTMLElement} el The element to check
2737 * @param {String} tag The tag that the element needs to be
2740 _isElement: function(el, tag) {
2741 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2744 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2750 * @private _hasParent
2751 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2752 * @param {HTMLElement} el The element to check
2753 * @param {String} tag The tag that the element needs to be
2754 * @return HTMLElement
2756 _hasParent: function(el, tag) {
2757 if (!el || !el.parentNode) {
2761 while (el.parentNode) {
2762 if (this._isElement(el, tag)) {
2765 if (el.parentNode) {
2776 * @description Get the Document of the IFRAME
2779 _getDoc: function() {
2782 if (this.get('iframe').get('element').contentWindow.document) {
2783 value = this.get('iframe').get('element').contentWindow.document;
2792 * @method _getWindow
2793 * @description Get the Window of the IFRAME
2796 _getWindow: function() {
2797 return this.get('iframe').get('element').contentWindow;
2801 * @description Attempt to set the focus of the iframes window.
2804 this._getWindow().focus();
2808 * @depreciated - This should not be used, moved to this.focus();
2809 * @method _focusWindow
2810 * @description Attempt to set the focus of the iframes window.
2812 _focusWindow: function() {
2817 * @method _hasSelection
2818 * @description Determines if there is a selection in the editor document.
2821 _hasSelection: function() {
2822 var sel = this._getSelection();
2823 var range = this._getRange();
2826 if (!sel || !range) {
2831 if (this.browser.ie || this.browser.opera) {
2839 if (this.browser.webkit) {
2840 if (sel+'' !== '') {
2844 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2853 * @method _getSelection
2854 * @description Handles the different selection objects across the A-Grade list.
2855 * @return {Object} Selection Object
2857 _getSelection: function() {
2859 if (this._getDoc() && this._getWindow()) {
2860 if (this._getDoc().selection) {
2861 _sel = this._getDoc().selection;
2863 _sel = this._getWindow().getSelection();
2865 //Handle Safari's lack of Selection Object
2866 if (this.browser.webkit) {
2867 if (_sel.baseNode) {
2868 this._selection = {};
2869 this._selection.baseNode = _sel.baseNode;
2870 this._selection.baseOffset = _sel.baseOffset;
2871 this._selection.extentNode = _sel.extentNode;
2872 this._selection.extentOffset = _sel.extentOffset;
2873 } else if (this._selection !== null) {
2874 _sel = this._getWindow().getSelection();
2875 _sel.setBaseAndExtent(
2876 this._selection.baseNode,
2877 this._selection.baseOffset,
2878 this._selection.extentNode,
2879 this._selection.extentOffset);
2880 this._selection = null;
2888 * @method _selectNode
2889 * @description Places the highlight around a given node
2890 * @param {HTMLElement} node The node to select
2892 _selectNode: function(node, collapse) {
2896 var sel = this._getSelection(),
2899 if (this.browser.ie) {
2900 try { //IE freaks out here sometimes..
2901 range = this._getDoc().body.createTextRange();
2902 range.moveToElementText(node);
2906 } else if (this.browser.webkit) {
2908 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2910 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2912 } else if (this.browser.opera) {
2913 sel = this._getWindow().getSelection();
2914 range = this._getDoc().createRange();
2915 range.selectNode(node);
2916 sel.removeAllRanges();
2917 sel.addRange(range);
2919 range = this._getDoc().createRange();
2920 range.selectNodeContents(node);
2921 sel.removeAllRanges();
2922 sel.addRange(range);
2924 //TODO - Check Performance
2930 * @description Handles the different range objects across the A-Grade list.
2931 * @return {Object} Range Object
2933 _getRange: function() {
2934 var sel = this._getSelection();
2940 if (this.browser.webkit && !sel.getRangeAt) {
2941 var _range = this._getDoc().createRange();
2943 _range.setStart(sel.anchorNode, sel.anchorOffset);
2944 _range.setEnd(sel.focusNode, sel.focusOffset);
2946 _range = this._getWindow().getSelection()+'';
2951 if (this.browser.ie || this.browser.opera) {
2953 return sel.createRange();
2959 if (sel.rangeCount > 0) {
2960 return sel.getRangeAt(0);
2966 * @method _setDesignMode
2967 * @description Sets the designMode property of the iFrame document's body.
2968 * @param {String} state This should be either on or off
2970 _setDesignMode: function(state) {
2971 if (this.get('setDesignMode')) {
2973 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on');
2979 * @method _toggleDesignMode
2980 * @description Toggles the designMode property of the iFrame document on and off.
2981 * @return {String} The state that it was set to.
2983 _toggleDesignMode: function() {
2984 var _dMode = this._getDoc().designMode,
2985 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on');
2986 this._setDesignMode(_state);
2991 * @property _focused
2992 * @description Holder for trapping focus/blur state and prevent double events
2998 * @method _handleFocus
2999 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
3000 * @param {Event} e The DOM Event
3002 _handleFocus: function(e) {
3003 if (!this._focused) {
3004 this._focused = true;
3005 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
3010 * @method _handleBlur
3011 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
3012 * @param {Event} e The DOM Event
3014 _handleBlur: function(e) {
3015 if (this._focused) {
3016 this._focused = false;
3017 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
3022 * @method _initEditorEvents
3023 * @description This method sets up the listeners on the Editors document.
3025 _initEditorEvents: function() {
3026 //Setup Listeners on iFrame
3027 var doc = this._getDoc(),
3028 win = this._getWindow();
3030 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3031 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3032 Event.on(doc, 'click', this._handleClick, this, true);
3033 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3034 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3035 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3036 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3037 /* TODO -- Everyone but Opera works here..
3038 Event.on(doc, 'paste', function() {
3043 Event.on(win, 'focus', this._handleFocus, this, true);
3044 Event.on(win, 'blur', this._handleBlur, this, true);
3048 * @method _removeEditorEvents
3049 * @description This method removes the listeners on the Editors document (for disabling).
3051 _removeEditorEvents: function() {
3052 //Remove Listeners on iFrame
3053 var doc = this._getDoc(),
3054 win = this._getWindow();
3056 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3057 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3058 Event.removeListener(doc, 'click', this._handleClick, this, true);
3059 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3060 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3061 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3062 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3065 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3066 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3068 _fixWebkitDivs: function() {
3069 if (this.browser.webkit) {
3070 var divs = this._getDoc().body.getElementsByTagName('div');
3071 Dom.addClass(divs, 'yui-wk-div');
3076 * @method _initEditor
3077 * @param {Boolean} raw Don't add events.
3078 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3080 _initEditor: function(raw) {
3081 if (this._editorInit) {
3084 this._editorInit = true;
3085 if (this.browser.ie) {
3086 this._getDoc().body.style.margin = '0';
3088 if (!this.get('disabled')) {
3089 this._setDesignMode('on');
3090 this._contentTimerCounter = 0;
3092 if (!this._getDoc().body) {
3093 this._contentTimerCounter = 0;
3094 this._editorInit = false;
3095 this._checkLoaded();
3100 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3102 if (!this.get('disabled')) {
3103 this._initEditorEvents();
3104 this.toolbar.set('disabled', false);
3108 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this });
3110 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3112 this._fixWebkitDivs();
3113 if (this.get('dompath')) {
3115 setTimeout(function() {
3116 self._writeDomPath.call(self);
3117 self._setupResize.call(self);
3121 for (var i in this.browser) {
3122 if (this.browser[i]) {
3126 if (this.get('ptags')) {
3129 Dom.addClass(this._getDoc().body, br.join(' '));
3130 this.nodeChange(true);
3134 * @method _checkLoaded
3135 * @param {Boolean} raw Don't add events.
3136 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3138 _checkLoaded: function(raw) {
3139 this._editorInit = false;
3140 this._contentTimerCounter++;
3141 if (this._contentTimer) {
3142 clearTimeout(this._contentTimer);
3144 if (this._contentTimerCounter > this._contentTimerMax) {
3149 if (this._getDoc() && this._getDoc().body) {
3150 if (this.browser.ie) {
3151 if (this._getDoc().body.readyState == 'complete') {
3155 if (this._getDoc().body._rteLoaded === true) {
3164 if (init === true) {
3165 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3166 this._initEditor(raw);
3169 this._contentTimer = setTimeout(function() {
3170 self._checkLoaded.call(self, raw);
3176 * @method _setInitialContent
3177 * @param {Boolean} raw Don't add events.
3178 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3180 _setInitialContent: function(raw) {
3182 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3189 var html = Lang.substitute(this.get('html'), {
3190 TITLE: this.STR_TITLE,
3191 CONTENT: this._cleanIncomingHTML(value),
3192 CSS: this.get('css'),
3193 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3194 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3198 html = html.replace(/RIGHT_BRACKET/gi, '{');
3199 html = html.replace(/LEFT_BRACKET/gi, '}');
3201 if (document.compatMode != 'BackCompat') {
3202 html = this._docType + "\n" + html;
3206 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3207 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3210 if (this.browser.air) {
3211 doc = this._getDoc().implementation.createHTMLDocument();
3212 var origDoc = this._getDoc();
3218 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3219 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3220 origDoc.body._rteLoaded = true;
3222 doc = this._getDoc();
3228 //Safari will only be here if we are hidden
3232 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3233 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3235 this.get('iframe').setStyle('visibility', '');
3237 this._checkLoaded(raw);
3242 * @method _setMarkupType
3243 * @param {String} action The action to take. Possible values are: css, default or semantic
3244 * @description This method will turn on/off the useCSS execCommand.
3246 _setMarkupType: function(action) {
3247 switch (this.get('markup')) {
3249 this._setEditorStyle(true);
3252 this._setEditorStyle(false);
3256 if (this._semantic[action]) {
3257 this._setEditorStyle(false);
3259 this._setEditorStyle(true);
3265 * Set the editor to use CSS instead of HTML
3266 * @param {Booleen} stat True/False
3268 _setEditorStyle: function(stat) {
3270 this._getDoc().execCommand('useCSS', false, !stat);
3276 * @method _getSelectedElement
3277 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3278 * @return {HTMLElement} The currently selected element.
3280 _getSelectedElement: function() {
3281 var doc = this._getDoc(),
3287 if (this.browser.ie) {
3288 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3289 range = this._getRange();
3291 elm = range.item ? range.item(0) : range.parentElement();
3292 if (this._hasSelection()) {
3294 //WTF.. Why can't I get an element reference here?!??!
3296 if (elm === doc.body) {
3300 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3301 elm = Event.getTarget(this.currentEvent);
3304 sel = this._getSelection();
3305 range = this._getRange();
3307 if (!sel || !range) {
3311 if (!this._hasSelection() && this.browser.webkit3) {
3314 if (this.browser.gecko) {
3316 if (range.startContainer) {
3317 if (range.startContainer.nodeType === 3) {
3318 elm = range.startContainer.parentNode;
3319 } else if (range.startContainer.nodeType === 1) {
3320 elm = range.startContainer;
3323 if (this.currentEvent) {
3324 var tar = Event.getTarget(this.currentEvent);
3325 if (!this._isElement(tar, 'html')) {
3335 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3336 if (sel.anchorNode.parentNode) { //next check parentNode
3337 elm = sel.anchorNode.parentNode;
3339 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3340 elm = sel.anchorNode.nextSibling;
3343 if (this._isElement(elm, 'br')) {
3347 elm = range.commonAncestorContainer;
3348 if (!range.collapsed) {
3349 if (range.startContainer == range.endContainer) {
3350 if (range.startOffset - range.endOffset < 2) {
3351 if (range.startContainer.hasChildNodes()) {
3352 elm = range.startContainer.childNodes[range.startOffset];
3361 if (this.currentEvent !== null) {
3363 switch (this.currentEvent.type) {
3367 if (this.browser.webkit) {
3368 elm = Event.getTarget(this.currentEvent);
3377 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3378 //TODO is this still needed?
3379 //elm = this.currentElement[0];
3383 if (this.browser.opera || this.browser.webkit) {
3384 if (this.currentEvent && !elm) {
3385 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3388 if (!elm || !elm.tagName) {
3391 if (this._isElement(elm, 'html')) {
3392 //Safari sometimes gives us the HTML node back..
3395 if (this._isElement(elm, 'body')) {
3396 //make sure that body means this body not the parent..
3399 if (elm && !elm.parentNode) { //Not in document
3402 if (elm === undefined) {
3409 * @method _getDomPath
3410 * @description This method will attempt to build the DOM path from the currently selected element.
3411 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3412 * @return {Array} An array of node references that will create the DOM Path.
3414 _getDomPath: function(el) {
3416 el = this._getSelectedElement();
3419 while (el !== null) {
3420 if (el.ownerDocument != this._getDoc()) {
3424 //Check to see if we get el.nodeName and nodeType
3425 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3426 domPath[domPath.length] = el;
3429 if (this._isElement(el, 'body')) {
3435 if (domPath.length === 0) {
3436 if (this._getDoc() && this._getDoc().body) {
3437 domPath[0] = this._getDoc().body;
3440 return domPath.reverse();
3444 * @method _writeDomPath
3445 * @description Write the current DOM path out to the dompath container below the editor.
3447 _writeDomPath: function() {
3448 var path = this._getDomPath(),
3453 for (var i = 0; i < path.length; i++) {
3454 var tag = path[i].tagName.toLowerCase();
3455 if ((tag == 'ol') && (path[i].type)) {
3456 tag += ':' + path[i].type;
3458 if (Dom.hasClass(path[i], 'yui-tag')) {
3459 tag = path[i].getAttribute('tag');
3461 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3463 case 'b': tag = 'strong'; break;
3464 case 'i': tag = 'em'; break;
3467 if (!Dom.hasClass(path[i], 'yui-non')) {
3468 if (Dom.hasClass(path[i], 'yui-tag')) {
3471 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3472 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3475 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3482 if (path[i].getAttribute('href', 2)) {
3483 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3487 var h = path[i].height;
3488 var w = path[i].width;
3489 if (path[i].style.height) {
3490 h = parseInt(path[i].style.height, 10);
3492 if (path[i].style.width) {
3493 w = parseInt(path[i].style.width, 10);
3495 pathStr += '(' + w + 'x' + h + ')';
3499 if (pathStr.length > 10) {
3500 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3502 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3504 pathArr[pathArr.length] = pathStr;
3507 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3508 //Prevent flickering
3509 if (this.dompath.innerHTML != str) {
3510 this.dompath.innerHTML = str;
3516 * @description Fix href and imgs as well as remove invalid HTML.
3518 _fixNodes: function() {
3520 var doc = this._getDoc(),
3523 for (var v in this.invalidHTML) {
3524 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3525 if (v.toLowerCase() != 'span') {
3526 var tags = doc.body.getElementsByTagName(v);
3528 for (var i = 0; i < tags.length; i++) {
3535 for (var h = 0; h < els.length; h++) {
3536 if (els[h].parentNode) {
3537 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3538 this._swapEl(els[h], 'span', function(el) {
3539 el.className = 'yui-non';
3542 els[h].parentNode.removeChild(els[h]);
3546 var imgs = this._getDoc().getElementsByTagName('img');
3547 Dom.addClass(imgs, 'yui-img');
3552 * @method _isNonEditable
3553 * @param Event ev The Dom event being checked
3554 * @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.
3555 * 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
3556 * disable and enable the Editor's toolbar based on the noedit state.
3559 _isNonEditable: function(ev) {
3560 if (this.get('allowNoEdit')) {
3561 var el = Event.getTarget(ev);
3562 if (this._isElement(el, 'html')) {
3565 var path = this._getDomPath(el);
3566 for (var i = (path.length - 1); i > -1; i--) {
3567 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3568 //if (this.toolbar.get('disabled') === false) {
3569 // this.toolbar.set('disabled', true);
3572 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3575 Event.stopEvent(ev);
3579 //if (this.toolbar.get('disabled') === true) {
3580 //Should only happen once..
3581 //this.toolbar.set('disabled', false);
3583 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3591 * @method _setCurrentEvent
3592 * @param {Event} ev The event to cache
3593 * @description Sets the current event property
3595 _setCurrentEvent: function(ev) {
3596 this.currentEvent = ev;
3600 * @method _handleClick
3601 * @param {Event} ev The event we are working on.
3602 * @description Handles all click events inside the iFrame document.
3604 _handleClick: function(ev) {
3605 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3606 if (ret === false) {
3609 if (this._isNonEditable(ev)) {
3612 this._setCurrentEvent(ev);
3613 if (this.currentWindow) {
3616 if (this.currentWindow) {
3619 if (this.browser.webkit) {
3620 var tar =Event.getTarget(ev);
3621 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3622 Event.stopEvent(ev);
3628 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3632 * @method _handleMouseUp
3633 * @param {Event} ev The event we are working on.
3634 * @description Handles all mouseup events inside the iFrame document.
3636 _handleMouseUp: function(ev) {
3637 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3638 if (ret === false) {
3641 if (this._isNonEditable(ev)) {
3644 //Don't set current event for mouseup.
3645 //It get's fired after a menu is closed and gives up a bogus event to work with
3646 //this._setCurrentEvent(ev);
3648 if (this.browser.opera) {
3650 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3652 * @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.
3654 var sel = Event.getTarget(ev);
3655 if (this._isElement(sel, 'img')) {
3657 if (this.operaEvent) {
3658 clearTimeout(this.operaEvent);
3659 this.operaEvent = null;
3660 this._handleDoubleClick(ev);
3662 this.operaEvent = window.setTimeout(function() {
3663 self.operaEvent = false;
3668 //This will stop Safari from selecting the entire document if you select all the text in the editor
3669 if (this.browser.webkit || this.browser.opera) {
3670 if (this.browser.webkit) {
3671 Event.stopEvent(ev);
3675 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3679 * @method _handleMouseDown
3680 * @param {Event} ev The event we are working on.
3681 * @description Handles all mousedown events inside the iFrame document.
3683 _handleMouseDown: function(ev) {
3684 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3685 if (ret === false) {
3688 if (this._isNonEditable(ev)) {
3691 this._setCurrentEvent(ev);
3692 var sel = Event.getTarget(ev);
3693 if (this.browser.webkit && this._hasSelection()) {
3694 var _sel = this._getSelection();
3695 if (!this.browser.webkit3) {
3696 _sel.collapse(true);
3698 _sel.collapseToStart();
3701 if (this.browser.webkit && this._lastImage) {
3702 Dom.removeClass(this._lastImage, 'selected');
3703 this._lastImage = null;
3705 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3706 if (this.browser.webkit) {
3707 Event.stopEvent(ev);
3708 if (this._isElement(sel, 'img')) {
3709 Dom.addClass(sel, 'selected');
3710 this._lastImage = sel;
3713 if (this.currentWindow) {
3718 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3722 * @method _handleDoubleClick
3723 * @param {Event} ev The event we are working on.
3724 * @description Handles all doubleclick events inside the iFrame document.
3726 _handleDoubleClick: function(ev) {
3727 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', 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._isElement(sel, 'img')) {
3737 this.currentElement[0] = sel;
3738 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3739 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3740 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3741 this.currentElement[0] = this._hasParent(sel, 'a');
3742 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3743 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3746 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3750 * @method _handleKeyUp
3751 * @param {Event} ev The event we are working on.
3752 * @description Handles all keyup events inside the iFrame document.
3754 _handleKeyUp: function(ev) {
3755 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3756 if (ret === false) {
3759 if (this._isNonEditable(ev)) {
3763 this._setCurrentEvent(ev);
3764 switch (ev.keyCode) {
3765 case this._keyMap.SELECT_ALL.key:
3766 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3770 case 32: //Space Bar
3773 case 37: //Left Arrow
3775 case 39: //Right Arrow
3776 case 40: //Down Arrow
3777 case 46: //Forward Delete
3779 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3780 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3781 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3785 if (!this.browser.ie) {
3786 if (this._nodeChangeTimer) {
3787 clearTimeout(this._nodeChangeTimer);
3790 this._nodeChangeTimer = setTimeout(function() {
3791 self._nodeChangeTimer = null;
3792 self.nodeChange.call(self);
3797 this.editorDirty = true;
3801 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3805 * @method _handleKeyPress
3806 * @param {Event} ev The event we are working on.
3807 * @description Handles all keypress events inside the iFrame document.
3809 _handleKeyPress: function(ev) {
3810 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3811 if (ret === false) {
3815 if (this.get('allowNoEdit')) {
3816 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3817 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3818 //Forward delete key
3819 Event.stopEvent(ev);
3822 if (this._isNonEditable(ev)) {
3825 this._setCurrentEvent(ev);
3827 if (this.browser.opera) {
3828 if (ev.keyCode === 13) {
3829 var tar = this._getSelectedElement();
3830 if (!this._isElement(tar, 'li')) {
3831 this.execCommand('inserthtml', '<br>');
3832 Event.stopEvent(ev);
3836 if (this.browser.webkit) {
3837 if (!this.browser.webkit3) {
3838 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3839 //This is CMD + z (for undo)
3840 if (this._hasParent(this._getSelectedElement(), 'li')) {
3841 Event.stopEvent(ev);
3847 this._fixListDupIds();
3848 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3852 * @method _handleKeyDown
3853 * @param {Event} ev The event we are working on.
3854 * @description Handles all keydown events inside the iFrame document.
3856 _handleKeyDown: function(ev) {
3857 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3858 if (ret === false) {
3861 var tar = null, _range = null;
3862 if (this._isNonEditable(ev)) {
3865 this._setCurrentEvent(ev);
3866 if (this.currentWindow) {
3869 if (this.currentWindow) {
3878 switch (ev.keyCode) {
3879 case this._keyMap.FOCUS_TOOLBAR.key:
3880 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3881 var h = this.toolbar.getElementsByTagName('h2')[0];
3882 if (h && h.firstChild) {
3883 h.firstChild.focus();
3885 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3886 //Focus After Element - Esc
3887 this.afterElement.focus();
3889 Event.stopEvent(ev);
3893 case this._keyMap.CREATE_LINK.key: //L
3894 if (this._hasSelection()) {
3895 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3896 var makeLink = true;
3897 if (this.get('limitCommands')) {
3898 if (!this.toolbar.getButtonByValue('createlink')) {
3903 this.execCommand('createlink', '');
3904 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3905 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3912 case this._keyMap.UNDO.key:
3913 case this._keyMap.REDO.key:
3914 if (this._checkKey(this._keyMap.REDO, ev)) {
3917 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3923 case this._keyMap.BOLD.key:
3924 if (this._checkKey(this._keyMap.BOLD, ev)) {
3929 case this._keyMap.FONT_SIZE_UP.key:
3930 case this._keyMap.FONT_SIZE_DOWN.key:
3931 var uk = false, dk = false;
3932 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3935 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3939 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3940 label = parseInt(fs_button.get('label'), 10),
3941 newValue = (label + 1);
3944 newValue = (label - 1);
3947 action = 'fontsize';
3948 value = newValue + 'px';
3953 case this._keyMap.ITALIC.key:
3954 if (this._checkKey(this._keyMap.ITALIC, ev)) {
3960 case this._keyMap.UNDERLINE.key:
3961 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3962 action = 'underline';
3967 if (this.browser.ie) {
3968 //Insert a tab in Internet Explorer
3969 _range = this._getRange();
3970 tar = this._getSelectedElement();
3971 if (!this._isElement(tar, 'li')) {
3973 _range.pasteHTML(' ');
3974 _range.collapse(false);
3977 Event.stopEvent(ev);
3981 if (this.browser.gecko > 1.8) {
3982 tar = this._getSelectedElement();
3983 if (this._isElement(tar, 'li')) {
3985 this._getDoc().execCommand('outdent', null, '');
3987 this._getDoc().execCommand('indent', null, '');
3990 } else if (!this._hasSelection()) {
3991 this.execCommand('inserthtml', ' ');
3993 Event.stopEvent(ev);
3997 var p = null, i = 0;
3998 if (this.get('ptags') && !ev.shiftKey) {
3999 if (this.browser.gecko) {
4000 tar = this._getSelectedElement();
4001 if (!this._hasParent(tar, 'li')) {
4002 if (this._hasParent(tar, 'p')) {
4003 p = this._getDoc().createElement('p');
4004 p.innerHTML = ' ';
4005 Dom.insertAfter(p, tar);
4006 this._selectNode(p.firstChild);
4007 } else if (this._isElement(tar, 'body')) {
4008 this.execCommand('insertparagraph', null);
4009 var ps = this._getDoc().body.getElementsByTagName('p');
4010 for (i = 0; i < ps.length; i++) {
4011 if (ps[i].getAttribute('_moz_dirty') !== null) {
4012 p = this._getDoc().createElement('p');
4013 p.innerHTML = ' ';
4014 Dom.insertAfter(p, ps[i]);
4015 this._selectNode(p.firstChild);
4016 ps[i].removeAttribute('_moz_dirty');
4021 action = 'insertparagraph';
4023 Event.stopEvent(ev);
4026 if (this.browser.webkit) {
4027 tar = this._getSelectedElement();
4028 if (!this._hasParent(tar, 'li')) {
4029 this.execCommand('insertparagraph', null);
4030 var divs = this._getDoc().body.getElementsByTagName('div');
4031 for (i = 0; i < divs.length; i++) {
4032 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4033 Dom.addClass(divs[i], 'yui-wk-p');
4036 Event.stopEvent(ev);
4040 if (this.browser.webkit) {
4041 tar = this._getSelectedElement();
4042 if (!this._hasParent(tar, 'li')) {
4043 if (this.browser.webkit4) {
4044 this.execCommand('insertlinebreak');
4046 this.execCommand('inserthtml', '<var id="yui-br"></var>');
4047 var holder = this._getDoc().getElementById('yui-br'),
4048 br = this._getDoc().createElement('br'),
4049 caret = this._getDoc().createElement('span');
4051 holder.parentNode.replaceChild(br, holder);
4052 caret.className = 'yui-non';
4053 caret.innerHTML = ' ';
4054 Dom.insertAfter(caret, br);
4055 this._selectNode(caret);
4057 Event.stopEvent(ev);
4060 if (this.browser.ie) {
4061 //Insert a <br> instead of a <p></p> in Internet Explorer
4062 _range = this._getRange();
4063 tar = this._getSelectedElement();
4064 if (!this._isElement(tar, 'li')) {
4066 _range.pasteHTML('<br>');
4067 _range.collapse(false);
4070 Event.stopEvent(ev);
4076 if (this.browser.ie) {
4079 if (doExec && action) {
4080 this.execCommand(action, value);
4081 Event.stopEvent(ev);
4085 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4089 * @property _fixListRunning
4091 * @description Keeps more than one _fixListDupIds from running at the same time.
4093 _fixListRunning: null,
4096 * @method _fixListDupIds
4097 * @description Some browsers will duplicate the id of an LI when created in designMode.
4098 * This method will fix the duplicate id issue. However it will only preserve the first element
4099 * in the document list with the unique id.
4101 _fixListDupIds: function() {
4102 if (this._fixListRunning) {
4105 if (this._getDoc()) {
4106 this._fixListRunning = true;
4107 var lis = this._getDoc().body.getElementsByTagName('li'),
4109 for (i = 0; i < lis.length; i++) {
4111 if (ids[lis[i].id]) {
4114 ids[lis[i].id] = true;
4117 this._fixListRunning = false;
4123 * @param {Event} ev The event we are working on.
4124 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4126 _listFix: function(ev) {
4127 var testLi = null, par = null, preContent = false, range = null;
4129 if (this.browser.webkit) {
4130 if (ev.keyCode && (ev.keyCode == 13)) {
4131 if (this._hasParent(this._getSelectedElement(), 'li')) {
4132 var tar = this._hasParent(this._getSelectedElement(), 'li');
4133 if (tar.previousSibling) {
4134 if (tar.firstChild && (tar.firstChild.length == 1)) {
4135 this._selectNode(tar);
4142 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4143 testLi = this._getSelectedElement();
4144 if (this._hasParent(testLi, 'li')) {
4145 testLi = this._hasParent(testLi, 'li');
4146 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4147 par = this._hasParent(testLi, 'ul');
4149 par = this._hasParent(testLi, 'ol');
4151 if (this._isElement(par.previousSibling, 'li')) {
4152 par.removeChild(testLi);
4153 par.parentNode.insertBefore(testLi, par.nextSibling);
4154 if (this.browser.ie) {
4155 range = this._getDoc().body.createTextRange();
4156 range.moveToElementText(testLi);
4157 range.collapse(false);
4160 if (this.browser.webkit) {
4161 this._selectNode(testLi.firstChild);
4163 Event.stopEvent(ev);
4169 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4170 var preLi = this._getSelectedElement();
4171 if (this._hasParent(preLi, 'li')) {
4172 preContent = this._hasParent(preLi, 'li').innerHTML;
4174 if (this.browser.webkit) {
4175 this._getDoc().execCommand('inserttext', false, '\t');
4177 testLi = this._getSelectedElement();
4178 if (this._hasParent(testLi, 'li')) {
4179 par = this._hasParent(testLi, 'li');
4180 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4181 if (this.browser.webkit) {
4182 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4183 //Remove the span element that Safari puts in
4185 par.removeChild(span[0]);
4186 par.innerHTML = Lang.trim(par.innerHTML);
4187 //Put the HTML from the LI into this new LI
4189 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4191 par.innerHTML = '<span class="yui-non"> </span> ';
4196 par.innerHTML = preContent + ' ';
4198 par.innerHTML = ' ';
4202 par.parentNode.replaceChild(newUl, par);
4203 newUl.appendChild(par);
4204 if (this.browser.webkit) {
4205 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4206 if (!this.browser.webkit3) {
4207 par.parentNode.parentNode.style.display = 'list-item';
4208 setTimeout(function() {
4209 par.parentNode.parentNode.style.display = 'block';
4212 } else if (this.browser.ie) {
4213 range = this._getDoc().body.createTextRange();
4214 range.moveToElementText(par);
4215 range.collapse(false);
4218 this._selectNode(par);
4220 Event.stopEvent(ev);
4222 if (this.browser.webkit) {
4223 Event.stopEvent(ev);
4229 * @method nodeChange
4230 * @param {Boolean} force Optional paramenter to skip the threshold counter
4231 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4233 nodeChange: function(force) {
4236 if (this.get('nodeChangeDelay')) {
4237 this._nodeChangeDelayTimer = window.setTimeout(function() {
4238 NCself._nodeChangeDelayTimer = null;
4239 NCself._nodeChange.apply(NCself, arguments);
4247 * @method _nodeChange
4248 * @param {Boolean} force Optional paramenter to skip the threshold counter
4249 * @description Fired from nodeChange in a setTimeout.
4251 _nodeChange: function(force) {
4252 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4253 thisNodeChange = Math.round(new Date().getTime() / 1000),
4256 if (force === true) {
4257 this._lastNodeChange = 0;
4260 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4261 if (this._fixNodesTimer === null) {
4262 this._fixNodesTimer = window.setTimeout(function() {
4263 self._fixNodes.call(self);
4264 self._fixNodesTimer = null;
4268 this._lastNodeChange = thisNodeChange;
4269 if (this.currentEvent) {
4271 this._lastNodeChangeEvent = this.currentEvent.type;
4275 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4276 if (beforeNodeChange === false) {
4279 if (this.get('dompath')) {
4280 window.setTimeout(function() {
4281 self._writeDomPath.call(self);
4284 //Check to see if we are disabled before continuing
4285 if (!this.get('disabled')) {
4286 if (this.STOP_NODE_CHANGE) {
4287 //Reset this var for next action
4288 this.STOP_NODE_CHANGE = false;
4291 var sel = this._getSelection(),
4292 range = this._getRange(),
4293 el = this._getSelectedElement(),
4294 fn_button = this.toolbar.getButtonByValue('fontname'),
4295 fs_button = this.toolbar.getButtonByValue('fontsize'),
4296 undo_button = this.toolbar.getButtonByValue('undo'),
4297 redo_button = this.toolbar.getButtonByValue('redo');
4299 //Handle updating the toolbar with active buttons
4301 if (this._lastButton) {
4302 _ex[this._lastButton.id] = true;
4303 //this._lastButton = null;
4305 if (!this._isElement(el, 'body')) {
4307 _ex[fn_button.get('id')] = true;
4310 _ex[fs_button.get('id')] = true;
4314 delete _ex[redo_button.get('id')];
4316 this.toolbar.resetAllButtons(_ex);
4318 //Handle disabled buttons
4319 for (var d = 0; d < this._disabled.length; d++) {
4320 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4321 if (_button && _button.get) {
4322 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4325 if (!this._hasSelection() && !this.get('insert')) {
4326 switch (this._disabled[d]) {
4331 //No Selection - disable
4332 this.toolbar.disableButton(_button);
4335 if (!this._alwaysDisabled[this._disabled[d]]) {
4336 this.toolbar.enableButton(_button);
4339 if (!this._alwaysEnabled[this._disabled[d]]) {
4340 this.toolbar.deselectButton(_button);
4345 var path = this._getDomPath();
4346 var tag = null, cmd = null;
4347 for (var i = 0; i < path.length; i++) {
4348 tag = path[i].tagName.toLowerCase();
4349 if (path[i].getAttribute('tag')) {
4350 tag = path[i].getAttribute('tag').toLowerCase();
4352 cmd = this._tag2cmd[tag];
4353 if (cmd === undefined) {
4356 if (!Lang.isArray(cmd)) {
4360 //Bold and Italic styles
4361 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4362 cmd[cmd.length] = 'bold';
4364 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4365 cmd[cmd.length] = 'italic';
4367 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4368 cmd[cmd.length] = 'underline';
4370 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4371 cmd[cmd.length] = 'strikethrough';
4373 if (cmd.length > 0) {
4374 for (var j = 0; j < cmd.length; j++) {
4375 this.toolbar.selectButton(cmd[j]);
4376 this.toolbar.enableButton(cmd[j]);
4380 switch (path[i].style.textAlign.toLowerCase()) {
4385 var alignType = path[i].style.textAlign.toLowerCase();
4386 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4389 this.toolbar.selectButton('justify' + alignType);
4390 this.toolbar.enableButton('justify' + alignType);
4396 //Reset Font Family and Size to the inital configs
4398 var family = fn_button._configs.label._initialConfig.value;
4399 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4400 this._updateMenuChecked('fontname', family);
4404 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4407 var hd_button = this.toolbar.getButtonByValue('heading');
4409 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4410 this._updateMenuChecked('heading', 'none');
4412 var img_button = this.toolbar.getButtonByValue('insertimage');
4413 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4414 this.toolbar.disableButton(img_button);
4416 if (this._lastButton && this._lastButton.isSelected) {
4417 this.toolbar.deselectButton(this._lastButton.id);
4419 this._undoNodeChange();
4423 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4427 * @method _updateMenuChecked
4428 * @param {Object} button The command identifier of the button you want to check
4429 * @param {String} value The value of the menu item you want to check
4430 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4431 * @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.
4433 _updateMenuChecked: function(button, value, tbar) {
4435 tbar = this.toolbar;
4437 var _button = tbar.getButtonByValue(button);
4438 _button.checkValue(value);
4442 * @method _handleToolbarClick
4443 * @param {Event} ev The event that triggered the button click
4444 * @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.
4446 _handleToolbarClick: function(ev) {
4449 var cmd = ev.button.value;
4450 if (ev.button.menucmd) {
4452 cmd = ev.button.menucmd;
4454 this._lastButton = ev.button;
4455 if (this.STOP_EXEC_COMMAND) {
4456 this.STOP_EXEC_COMMAND = false;
4459 this.execCommand(cmd, value);
4460 if (!this.browser.webkit) {
4462 setTimeout(function() {
4463 Fself.focus.call(Fself);
4467 Event.stopEvent(ev);
4471 * @method _setupAfterElement
4472 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4474 _setupAfterElement: function() {
4475 if (!this.beforeElement) {
4476 this.beforeElement = document.createElement('h2');
4477 this.beforeElement.className = 'yui-editor-skipheader';
4478 this.beforeElement.tabIndex = '-1';
4479 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4480 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4482 if (!this.afterElement) {
4483 this.afterElement = document.createElement('h2');
4484 this.afterElement.className = 'yui-editor-skipheader';
4485 this.afterElement.tabIndex = '-1';
4486 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4487 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4492 * @method _disableEditor
4493 * @param {Boolean} disabled Pass true to disable, false to enable
4494 * @description Creates a mask to place over the Editor.
4496 _disableEditor: function(disabled) {
4497 var iframe, par, html, height;
4498 if (!this.get('disabled_iframe')) {
4499 iframe = this._createIframe();
4500 iframe.set('id', 'disabled_' + this.get('iframe').get('id'));
4501 iframe.setStyle('height', '100%');
4502 iframe.setStyle('display', 'none');
4503 iframe.setStyle('visibility', 'visible');
4504 this.set('disabled_iframe', iframe);
4505 par = this.get('iframe').get('parentNode');
4506 par.appendChild(iframe.get('element'));
4509 iframe = this.get('disabled_iframe');
4512 this._orgIframe = this.get('iframe');
4515 this.toolbar.set('disabled', true);
4518 html = this.getEditorHTML();
4519 height = this.get('iframe').get('offsetHeight');
4520 iframe.setStyle('visibility', '');
4521 iframe.setStyle('position', '');
4522 iframe.setStyle('top', '');
4523 iframe.setStyle('left', '');
4524 this._orgIframe.setStyle('visibility', 'hidden');
4525 this._orgIframe.setStyle('position', 'absolute');
4526 this._orgIframe.setStyle('top', '-99999px');
4527 this._orgIframe.setStyle('left', '-99999px');
4528 this.set('iframe', iframe);
4529 this._setInitialContent(true);
4532 this._mask = document.createElement('DIV');
4533 Dom.addClass(this._mask, 'yui-editor-masked');
4534 if (this.browser.ie) {
4535 this._mask.style.height = height + 'px';
4537 this.get('iframe').get('parentNode').appendChild(this._mask);
4539 this.on('editorContentReloaded', function() {
4540 this._getDoc().body._rteLoaded = false;
4541 this.setEditorHTML(html);
4542 iframe.setStyle('display', 'block');
4543 this.unsubscribeAll('editorContentReloaded');
4547 this._mask.parentNode.removeChild(this._mask);
4550 this.toolbar.set('disabled', false);
4552 iframe.setStyle('visibility', 'hidden');
4553 iframe.setStyle('position', 'absolute');
4554 iframe.setStyle('top', '-99999px');
4555 iframe.setStyle('left', '-99999px');
4556 this._orgIframe.setStyle('visibility', '');
4557 this._orgIframe.setStyle('position', '');
4558 this._orgIframe.setStyle('top', '');
4559 this._orgIframe.setStyle('left', '');
4560 this.set('iframe', this._orgIframe);
4564 window.setTimeout(function() {
4565 self.nodeChange.call(self);
4571 * @property SEP_DOMPATH
4572 * @description The value to place in between the Dom path items
4577 * @property STR_LEAVE_EDITOR
4578 * @description The accessibility string for the element after the iFrame
4581 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4583 * @property STR_BEFORE_EDITOR
4584 * @description The accessibility string for the element before the iFrame
4587 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>',
4589 * @property STR_TITLE
4590 * @description The Title of the HTML document that is created in the iFrame
4593 STR_TITLE: 'Rich Text Area.',
4595 * @property STR_IMAGE_HERE
4596 * @description The text to place in the URL textbox when using the blankimage.
4599 STR_IMAGE_HERE: 'Image URL Here',
4601 * @property STR_IMAGE_URL
4602 * @description The label string for Image URL
4605 STR_IMAGE_URL: 'Image URL',
4607 * @property STR_LINK_URL
4608 * @description The label string for the Link URL.
4611 STR_LINK_URL: 'Link URL',
4614 * @property STOP_EXEC_COMMAND
4615 * @description Set to true when you want the default execCommand function to not process anything
4618 STOP_EXEC_COMMAND: false,
4621 * @property STOP_NODE_CHANGE
4622 * @description Set to true when you want the default nodeChange function to not process anything
4625 STOP_NODE_CHANGE: false,
4628 * @property CLASS_NOEDIT
4629 * @description CSS class applied to elements that are not editable.
4632 CLASS_NOEDIT: 'yui-noedit',
4635 * @property CLASS_CONTAINER
4636 * @description Default CSS class to apply to the editors container element
4639 CLASS_CONTAINER: 'yui-editor-container',
4642 * @property CLASS_EDITABLE
4643 * @description Default CSS class to apply to the editors iframe element
4646 CLASS_EDITABLE: 'yui-editor-editable',
4649 * @property CLASS_EDITABLE_CONT
4650 * @description Default CSS class to apply to the editors iframe's parent element
4653 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4656 * @property CLASS_PREFIX
4657 * @description Default prefix for dynamically created class names
4660 CLASS_PREFIX: 'yui-editor',
4663 * @description Standard browser detection
4666 browser: function() {
4667 var br = YAHOO.env.ua;
4669 if (br.webkit >= 420) {
4670 br.webkit3 = br.webkit;
4674 if (br.webkit >= 530) {
4675 br.webkit4 = br.webkit;
4681 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4689 * @description The Editor class' initialization method
4691 init: function(p_oElement, p_oAttributes) {
4693 if (!this._defaultToolbar) {
4694 this._defaultToolbar = {
4696 titlebar: 'Text Editing Tools',
4699 { group: 'fontstyle', label: 'Font Name and Size',
4701 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4703 { text: 'Arial', checked: true },
4704 { text: 'Arial Black' },
4705 { text: 'Comic Sans MS' },
4706 { text: 'Courier New' },
4707 { text: 'Lucida Console' },
4709 { text: 'Times New Roman' },
4710 { text: 'Trebuchet MS' },
4714 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4717 { type: 'separator' },
4718 { group: 'textstyle', label: 'Font Style',
4720 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4721 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4722 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4723 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4724 { type: 'separator' },
4725 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4726 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4730 { type: 'separator' },
4731 { group: 'indentlist', label: 'Lists',
4733 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4734 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4737 { type: 'separator' },
4738 { group: 'insertitem', label: 'Insert Item',
4740 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4741 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4748 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4749 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4752 this.currentElement = [];
4753 this.on('contentReady', function() {
4754 this.DOMReady = true;
4760 * @method initAttributes
4761 * @description Initializes all of the configuration attributes used to create
4763 * @param {Object} attr Object literal specifying a set of
4764 * configuration attributes used to create the editor.
4766 initAttributes: function(attr) {
4767 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4771 * @config setDesignMode
4772 * @description Should the Editor set designMode on the document. Default: true.
4776 this.setAttributeConfig('setDesignMode', {
4777 value: ((attr.setDesignMode === false) ? false : true)
4780 * @config nodeChangeDelay
4781 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true.
4785 this.setAttributeConfig('nodeChangeDelay', {
4786 value: ((attr.nodeChangeDelay === false) ? false : true)
4790 * @description The max number of undo levels to store.
4794 this.setAttributeConfig('maxUndo', {
4796 value: attr.maxUndo || 30
4801 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4805 this.setAttributeConfig('ptags', {
4807 value: attr.ptags || false
4811 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4815 this.setAttributeConfig('insert', {
4817 value: attr.insert || false,
4818 method: function(insert) {
4826 var tmp = this._defaultToolbar.buttons;
4827 for (var i = 0; i < tmp.length; i++) {
4828 if (tmp[i].buttons) {
4829 for (var a = 0; a < tmp[i].buttons.length; a++) {
4830 if (tmp[i].buttons[a].value) {
4831 if (buttons[tmp[i].buttons[a].value]) {
4832 delete tmp[i].buttons[a].disabled;
4843 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4844 * We will create one and place it in this container. If no container is passed we will append to document.body.
4848 this.setAttributeConfig('container', {
4850 value: attr.container || false
4854 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4858 this.setAttributeConfig('plainText', {
4860 value: attr.plainText || false
4865 * @description Internal config for holding the iframe element.
4869 this.setAttributeConfig('iframe', {
4874 * @config disabled_iframe
4875 * @description Internal config for holding the iframe element used when disabling the Editor.
4879 this.setAttributeConfig('disabled_iframe', {
4884 * @depreciated - No longer used, should use this.get('element')
4886 * @description Internal config for holding the textarea element (replaced with element).
4890 this.setAttributeConfig('textarea', {
4895 * @config nodeChangeThreshold
4896 * @description The number of seconds that need to be in between nodeChange processing
4900 this.setAttributeConfig('nodeChangeThreshold', {
4901 value: attr.nodeChangeThreshold || 3,
4902 validator: YAHOO.lang.isNumber
4905 * @config allowNoEdit
4906 * @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.
4907 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4911 this.setAttributeConfig('allowNoEdit', {
4912 value: attr.allowNoEdit || false,
4913 validator: YAHOO.lang.isBoolean
4916 * @config limitCommands
4917 * @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.
4921 this.setAttributeConfig('limitCommands', {
4922 value: attr.limitCommands || false,
4923 validator: YAHOO.lang.isBoolean
4926 * @config element_cont
4927 * @description Internal config for the editors container
4931 this.setAttributeConfig('element_cont', {
4932 value: attr.element_cont
4936 * @config editor_wrapper
4937 * @description The outter wrapper for the entire editor.
4941 this.setAttributeConfig('editor_wrapper', {
4942 value: attr.editor_wrapper || null,
4947 * @description The height of the editor iframe container, not including the toolbar..
4948 * @default Best guessed size of the textarea, for best results use CSS to style the height of the textarea or pass it in as an argument
4951 this.setAttributeConfig('height', {
4952 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4953 method: function(height) {
4954 if (this._rendered) {
4955 //We have been rendered, change the height
4956 if (this.get('animate')) {
4957 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4959 to: parseInt(height, 10)
4964 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4970 * @config autoHeight
4971 * @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.
4973 * @type Boolean || Number
4975 this.setAttributeConfig('autoHeight', {
4976 value: attr.autoHeight || false,
4977 method: function(a) {
4979 if (this.get('iframe')) {
4980 this.get('iframe').get('element').setAttribute('scrolling', 'no');
4982 this.on('afterNodeChange', this._handleAutoHeight, this, true);
4983 this.on('editorKeyDown', this._handleAutoHeight, this, true);
4984 this.on('editorKeyPress', this._handleAutoHeight, this, true);
4986 if (this.get('iframe')) {
4987 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4989 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4990 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4991 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4997 * @description The width of the editor container.
4998 * @default Best guessed size of the textarea, for best results use CSS to style the width of the textarea or pass it in as an argument
5001 this.setAttributeConfig('width', {
5002 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
5003 method: function(width) {
5004 if (this._rendered) {
5005 //We have been rendered, change the width
5006 if (this.get('animate')) {
5007 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
5009 to: parseInt(width, 10)
5014 this.get('element_cont').setStyle('width', width);
5021 * @attribute blankimage
5022 * @description The URL for the image placeholder to put in when inserting an image.
5023 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
5026 this.setAttributeConfig('blankimage', {
5027 value: attr.blankimage || this._getBlankImage()
5031 * @description The Base CSS used to format the content of the editor
5032 * @default <code><pre>html {
5037 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
5041 text-decoration: underline;
5044 .warning-localfile {
5045 border-bottom: 1px dashed red !important;
5048 cursor: wait !important;
5050 img.selected { //Safari image selection
5051 border: 2px dotted #808080;
5054 cursor: pointer !important;
5060 this.setAttributeConfig('css', {
5061 value: attr.css || this._defaultCSS,
5066 * @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)
5067 * @default This HTML requires a few things if you are to override:
5068 <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>
5069 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
5074 <title>{TITLE}</title>
5075 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5086 <body onload="document.body._rteLoaded = true;">
5094 this.setAttributeConfig('html', {
5095 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>',
5100 * @attribute extracss
5101 * @description Extra user defined css to load after the default SimpleEditor CSS
5105 this.setAttributeConfig('extracss', {
5106 value: attr.extracss || '',
5111 * @attribute handleSubmit
5112 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
5113 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
5114 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
5118 this.setAttributeConfig('handleSubmit', {
5119 value: attr.handleSubmit || false,
5120 method: function(exec) {
5121 if (this.get('element').form) {
5122 if (!this._formButtons) {
5123 this._formButtons = [];
5126 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5127 var i = this.get('element').form.getElementsByTagName('input');
5128 for (var s = 0; s < i.length; s++) {
5129 var type = i[s].getAttribute('type');
5130 if (type && (type.toLowerCase() == 'submit')) {
5131 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5132 this._formButtons[this._formButtons.length] = i[s];
5136 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5137 if (this._formButtons) {
5138 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5145 * @attribute disabled
5146 * @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.
5147 All Toolbar buttons are also disabled so they cannot be used.
5152 this.setAttributeConfig('disabled', {
5154 method: function(disabled) {
5155 if (this._rendered) {
5156 this._disableEditor(disabled);
5162 * @description When save HTML is called, this element will be updated as well as the source of data.
5166 this.setAttributeConfig('saveEl', {
5167 value: this.get('element')
5170 * @config toolbar_cont
5171 * @description Internal config for the toolbars container
5175 this.setAttributeConfig('toolbar_cont', {
5180 * @attribute toolbar
5181 * @description The default toolbar config.
5184 this.setAttributeConfig('toolbar', {
5185 value: attr.toolbar || this._defaultToolbar,
5187 method: function(toolbar) {
5188 if (!toolbar.buttonType) {
5189 toolbar.buttonType = this._defaultToolbar.buttonType;
5191 this._defaultToolbar = toolbar;
5195 * @attribute animate
5196 * @description Should the editor animate window movements
5197 * @default false unless Animation is found, then true
5200 this.setAttributeConfig('animate', {
5201 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5202 validator: function(value) {
5204 if (!YAHOO.util.Anim) {
5212 * @description A reference to the panel we are using for windows.
5216 this.setAttributeConfig('panel', {
5219 validator: function(value) {
5221 if (!YAHOO.widget.Overlay) {
5228 * @attribute focusAtStart
5229 * @description Should we focus the window when the content is ready?
5233 this.setAttributeConfig('focusAtStart', {
5234 value: attr.focusAtStart || false,
5236 method: function(fs) {
5238 this.on('editorContentLoaded', function() {
5240 setTimeout(function() {
5241 self.focus.call(self);
5242 self.editorDirty = false;
5249 * @attribute dompath
5250 * @description Toggle the display of the current Dom path below the editor
5254 this.setAttributeConfig('dompath', {
5255 value: attr.dompath || false,
5256 method: function(dompath) {
5257 if (dompath && !this.dompath) {
5258 this.dompath = document.createElement('DIV');
5259 this.dompath.id = this.get('id') + '_dompath';
5260 Dom.addClass(this.dompath, 'dompath');
5261 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5262 if (this.get('iframe')) {
5263 this._writeDomPath();
5265 } else if (!dompath && this.dompath) {
5266 this.dompath.parentNode.removeChild(this.dompath);
5267 this.dompath = null;
5273 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5274 * @default "semantic"
5277 this.setAttributeConfig('markup', {
5278 value: attr.markup || 'semantic',
5279 validator: function(markup) {
5280 switch (markup.toLowerCase()) {
5291 * @attribute removeLineBreaks
5292 * @description Should we remove linebreaks and extra spaces on cleanup
5296 this.setAttributeConfig('removeLineBreaks', {
5297 value: attr.removeLineBreaks || false,
5298 validator: YAHOO.lang.isBoolean
5303 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5304 * @type {Boolean/String}
5306 this.setAttributeConfig('drag', {
5308 value: attr.drag || false
5313 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5314 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5317 this.setAttributeConfig('resize', {
5319 value: attr.resize || false
5323 * @config filterWord
5324 * @description Attempt to filter out MS Word HTML from the Editor's output.
5327 this.setAttributeConfig('filterWord', {
5328 value: attr.filterWord || false,
5329 validator: YAHOO.lang.isBoolean
5335 * @method _getBlankImage
5336 * @description Retrieves the full url of the image to use as the blank image.
5337 * @return {String} The URL to the blank image
5339 _getBlankImage: function() {
5340 if (!this.DOMReady) {
5341 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5345 if (!this._blankImageLoaded) {
5346 if (YAHOO.widget.EditorInfo.blankImage) {
5347 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5348 this._blankImageLoaded = true;
5350 var div = document.createElement('div');
5351 div.style.position = 'absolute';
5352 div.style.top = '-9999px';
5353 div.style.left = '-9999px';
5354 div.className = this.CLASS_PREFIX + '-blankimage';
5355 document.body.appendChild(div);
5356 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5357 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5359 img = img.replace('app:/', '');
5360 this.set('blankimage', img);
5361 this._blankImageLoaded = true;
5362 div.parentNode.removeChild(div);
5363 YAHOO.widget.EditorInfo.blankImage = img;
5366 img = this.get('blankimage');
5372 * @method _handleAutoHeight
5373 * @description Handles resizing the editor's height based on the content
5375 _handleAutoHeight: function() {
5376 var doc = this._getDoc(),
5378 docEl = doc.documentElement;
5380 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5381 var newHeight = body.scrollHeight;
5382 if (this.browser.webkit) {
5383 newHeight = docEl.scrollHeight;
5385 if (newHeight < parseInt(this.get('height'), 10)) {
5386 newHeight = parseInt(this.get('height'), 10);
5388 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5389 var anim = this.get('animate');
5390 this.set('animate', false);
5391 this.set('height', newHeight + 'px');
5392 this.set('animate', anim);
5393 if (this.browser.ie) {
5394 //Internet Explorer needs this
5395 this.get('iframe').setStyle('height', '99%');
5396 this.get('iframe').setStyle('zoom', '1');
5398 window.setTimeout(function() {
5399 self.get('iframe').setStyle('height', '100%');
5406 * @property _formButtons
5407 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5413 * @property _formButtonClicked
5414 * @description The form button that was clicked to submit the form.
5417 _formButtonClicked: null,
5420 * @method _handleFormButtonClick
5421 * @description The click listener assigned to each submit button in the Editor's parent form.
5422 * @param {Event} ev The click event
5424 _handleFormButtonClick: function(ev) {
5425 var tar = Event.getTarget(ev);
5426 this._formButtonClicked = tar;
5430 * @method _handleFormSubmit
5431 * @description Handles the form submission.
5432 * @param {Object} ev The Form Submit Event
5434 _handleFormSubmit: function(ev) {
5437 var form = this.get('element').form,
5438 tar = this._formButtonClicked || false;
5440 Event.removeListener(form, 'submit', this._handleFormSubmit);
5441 if (YAHOO.env.ua.ie) {
5442 //form.fireEvent("onsubmit");
5443 if (tar && !tar.disabled) {
5446 } else { // Gecko, Opera, and Safari
5447 if (tar && !tar.disabled) {
5450 var oEvent = document.createEvent("HTMLEvents");
5451 oEvent.initEvent("submit", true, true);
5452 form.dispatchEvent(oEvent);
5453 if (YAHOO.env.ua.webkit) {
5454 if (YAHOO.lang.isFunction(form.submit)) {
5460 //Removed this, not need since removing Safari 2.x
5461 //Event.stopEvent(ev);
5465 * @method _handleFontSize
5466 * @description Handles the font size button in the toolbar.
5467 * @param {Object} o Object returned from Toolbar's buttonClick Event
5469 _handleFontSize: function(o) {
5470 var button = this.toolbar.getButtonById(o.button.id);
5471 var value = button.get('label') + 'px';
5472 this.execCommand('fontsize', value);
5477 * @description Handles the colorpicker buttons in the toolbar.
5478 * @param {Object} o Object returned from Toolbar's buttonClick Event
5480 _handleColorPicker: function(o) {
5482 var value = '#' + o.color;
5483 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5484 this.execCommand(cmd, value);
5489 * @method _handleAlign
5490 * @description Handles the alignment buttons in the toolbar.
5491 * @param {Object} o Object returned from Toolbar's buttonClick Event
5493 _handleAlign: function(o) {
5495 for (var i = 0; i < o.button.menu.length; i++) {
5496 if (o.button.menu[i].value == o.button.value) {
5497 cmd = o.button.menu[i].value;
5500 var value = this._getSelection();
5502 this.execCommand(cmd, value);
5507 * @method _handleAfterNodeChange
5508 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5510 _handleAfterNodeChange: function() {
5511 var path = this._getDomPath(),
5516 fn_button = this.toolbar.getButtonByValue('fontname'),
5517 fs_button = this.toolbar.getButtonByValue('fontsize'),
5518 hd_button = this.toolbar.getButtonByValue('heading');
5520 for (var i = 0; i < path.length; i++) {
5523 var tag = elm.tagName.toLowerCase();
5526 if (elm.getAttribute('tag')) {
5527 tag = elm.getAttribute('tag');
5530 family = elm.getAttribute('face');
5531 if (Dom.getStyle(elm, 'font-family')) {
5532 family = Dom.getStyle(elm, 'font-family');
5534 family = family.replace(/'/g, '');
5537 if (tag.substring(0, 1) == 'h') {
5539 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5540 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5541 hd_button.set('label', hd_button._configs.menu.value[h].text);
5544 this._updateMenuChecked('heading', tag);
5550 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5551 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5553 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5557 family = fn_button._configs.label._initialConfig.value;
5559 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5560 if (fn_button.get('label') != familyLabel) {
5561 fn_button.set('label', familyLabel);
5562 this._updateMenuChecked('fontname', family);
5567 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5568 if ((fontsize === null) || isNaN(fontsize)) {
5569 fontsize = fs_button._configs.label._initialConfig.value;
5571 fs_button.set('label', ''+fontsize);
5574 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5575 this.toolbar.enableButton(fn_button);
5576 this.toolbar.enableButton(fs_button);
5577 this.toolbar.enableButton('forecolor');
5578 this.toolbar.enableButton('backcolor');
5580 if (this._isElement(elm, 'img')) {
5581 if (YAHOO.widget.Overlay) {
5582 this.toolbar.enableButton('createlink');
5585 if (this._hasParent(elm, 'blockquote')) {
5586 this.toolbar.selectButton('indent');
5587 this.toolbar.disableButton('indent');
5588 this.toolbar.enableButton('outdent');
5590 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5591 this.toolbar.disableButton('indent');
5593 this._lastButton = null;
5598 * @method _handleInsertImageClick
5599 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5601 _handleInsertImageClick: function() {
5602 if (this.get('limitCommands')) {
5603 if (!this.toolbar.getButtonByValue('insertimage')) {
5608 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5609 var _handleAEC = function() {
5610 var el = this.currentElement[0],
5613 el = this._getSelectedElement();
5616 if (el.getAttribute('src')) {
5617 src = el.getAttribute('src', 2);
5618 if (src.indexOf(this.get('blankimage')) != -1) {
5619 src = this.STR_IMAGE_HERE;
5623 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5624 if ((str !== '') && (str !== null)) {
5625 el.setAttribute('src', str);
5626 } else if (str === '') {
5627 el.parentNode.removeChild(el);
5628 this.currentElement = [];
5630 } else if ((str === null)) {
5631 src = el.getAttribute('src', 2);
5632 if (src.indexOf(this.get('blankimage')) != -1) {
5633 el.parentNode.removeChild(el);
5634 this.currentElement = [];
5639 this.toolbar.set('disabled', false);
5640 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5642 this.on('afterExecCommand', _handleAEC, this, true);
5646 * @method _handleInsertImageWindowClose
5647 * @description Handles the closing of the Image Properties Window.
5649 _handleInsertImageWindowClose: function() {
5654 * @method _isLocalFile
5655 * @param {String} url THe url/string to check
5656 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5658 _isLocalFile: function(url) {
5659 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5666 * @method _handleCreateLinkClick
5667 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5669 _handleCreateLinkClick: function() {
5670 if (this.get('limitCommands')) {
5671 if (!this.toolbar.getButtonByValue('createlink')) {
5676 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5678 var _handleAEC = function() {
5679 var el = this.currentElement[0],
5683 if (el.getAttribute('href', 2) !== null) {
5684 url = el.getAttribute('href', 2);
5687 var str = prompt(this.STR_LINK_URL + ': ', url);
5688 if ((str !== '') && (str !== null)) {
5690 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5691 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5692 //Found an @ sign, prefix with mailto:
5693 urlValue = 'mailto:' + urlValue;
5695 /* :// not found adding */
5696 if (urlValue.substring(0, 1) != '#') {
5697 //urlValue = 'http:/'+'/' + urlValue;
5701 el.setAttribute('href', urlValue);
5702 } else if (str !== null) {
5703 var _span = this._getDoc().createElement('span');
5704 _span.innerHTML = el.innerHTML;
5705 Dom.addClass(_span, 'yui-non');
5706 el.parentNode.replaceChild(_span, el);
5709 this.toolbar.set('disabled', false);
5710 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5712 this.on('afterExecCommand', _handleAEC, this);
5717 * @method _handleCreateLinkWindowClose
5718 * @description Handles the closing of the Link Properties Window.
5720 _handleCreateLinkWindowClose: function() {
5722 this.currentElement = [];
5726 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5728 render: function() {
5729 if (this._rendered) {
5732 if (!this.DOMReady) {
5733 this._queue[this._queue.length] = ['render', arguments];
5736 if (this.get('element')) {
5737 if (this.get('element').tagName) {
5738 this._textarea = true;
5739 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5740 this._textarea = false;
5748 this._rendered = true;
5750 window.setTimeout(function() {
5751 self._render.call(self);
5757 * @description Causes the toolbar and the editor to render and replace the textarea.
5759 _render: function() {
5761 this.set('textarea', this.get('element'));
5763 this.get('element_cont').setStyle('display', 'none');
5764 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5766 this.set('iframe', this._createIframe());
5768 window.setTimeout(function() {
5769 self._setInitialContent.call(self);
5772 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5774 if (this.get('disabled')) {
5775 this._disableEditor(true);
5778 var tbarConf = this.get('toolbar');
5779 //Create Toolbar instance
5780 if (tbarConf instanceof Toolbar) {
5781 this.toolbar = tbarConf;
5782 //Set the toolbar to disabled until content is loaded
5783 this.toolbar.set('disabled', true);
5785 //Set the toolbar to disabled until content is loaded
5786 tbarConf.disabled = true;
5787 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5790 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5793 this.toolbar.on('toolbarCollapsed', function() {
5794 if (this.currentWindow) {
5798 this.toolbar.on('toolbarExpanded', function() {
5799 if (this.currentWindow) {
5803 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5805 this.toolbar.on('colorPickerClicked', function(o) {
5806 this._handleColorPicker(o);
5807 return false; //Stop the buttonClick event
5810 this.toolbar.on('alignClick', this._handleAlign, this, true);
5811 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5812 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5813 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5814 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5815 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5818 //Replace Textarea with editable area
5819 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5822 this.setStyle('visibility', 'hidden');
5823 this.setStyle('position', 'absolute');
5824 this.setStyle('top', '-9999px');
5825 this.setStyle('left', '-9999px');
5826 this.get('element_cont').appendChild(this.get('element'));
5827 this.get('element_cont').setStyle('display', 'block');
5830 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5831 this.get('iframe').addClass(this.CLASS_EDITABLE);
5833 //Set height and width of editor container
5834 this.get('element_cont').setStyle('width', this.get('width'));
5835 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5837 this.get('iframe').setStyle('width', '100%'); //WIDTH
5838 this.get('iframe').setStyle('height', '100%');
5842 window.setTimeout(function() {
5843 self._setupAfterElement.call(self);
5845 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5848 * @method execCommand
5849 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5850 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5851 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5853 execCommand: function(action, value) {
5854 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5855 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5856 this.STOP_EXEC_COMMAND = false;
5859 this._lastCommand = action;
5860 this._setMarkupType(action);
5861 if (this.browser.ie) {
5862 this._getWindow().focus();
5866 if (this.get('limitCommands')) {
5867 if (!this.toolbar.getButtonByValue(action)) {
5872 this.editorDirty = true;
5874 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5875 var retValue = this['cmd_' + action.toLowerCase()](value);
5878 action = retValue[1];
5881 value = retValue[2];
5886 this._getDoc().execCommand(action, false, value);
5891 this.on('afterExecCommand', function() {
5892 this.unsubscribeAll('afterExecCommand');
5895 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5898 /* {{{ Command Overrides */
5902 * @param value Value passed from the execCommand method
5903 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5905 cmd_bold: function(value) {
5906 if (!this.browser.webkit) {
5907 var el = this._getSelectedElement();
5908 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5909 if (el.style.fontWeight == 'bold') {
5910 el.style.fontWeight = '';
5911 var b = this._getDoc().createElement('b'),
5912 par = el.parentNode;
5913 par.replaceChild(b, el);
5921 * @method cmd_italic
5922 * @param value Value passed from the execCommand method
5923 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5926 cmd_italic: function(value) {
5927 if (!this.browser.webkit) {
5928 var el = this._getSelectedElement();
5929 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5930 if (el.style.fontStyle == 'italic') {
5931 el.style.fontStyle = '';
5932 var i = this._getDoc().createElement('i'),
5933 par = el.parentNode;
5934 par.replaceChild(i, el);
5944 * @method cmd_underline
5945 * @param value Value passed from the execCommand method
5946 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5948 cmd_underline: function(value) {
5949 if (!this.browser.webkit) {
5950 var el = this._getSelectedElement();
5951 if (el && this._isElement(el, 'span')) {
5952 if (el.style.textDecoration == 'underline') {
5953 el.style.textDecoration = 'none';
5955 el.style.textDecoration = 'underline';
5963 * @method cmd_backcolor
5964 * @param value Value passed from the execCommand method
5965 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5967 cmd_backcolor: function(value) {
5969 el = this._getSelectedElement(),
5970 action = 'backcolor';
5972 if (this.browser.gecko || this.browser.opera) {
5973 this._setEditorStyle(true);
5974 action = 'hilitecolor';
5977 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5978 el.style.backgroundColor = value;
5979 this._selectNode(el);
5982 if (this.get('insert')) {
5983 el = this._createInsertElement({ backgroundColor: value });
5985 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
5986 this._selectNode(this.currentElement[0]);
5991 return [exec, action];
5994 * @method cmd_forecolor
5995 * @param value Value passed from the execCommand method
5996 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
5998 cmd_forecolor: function(value) {
6000 el = this._getSelectedElement();
6002 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6003 Dom.setStyle(el, 'color', value);
6004 this._selectNode(el);
6007 if (this.get('insert')) {
6008 el = this._createInsertElement({ color: value });
6010 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
6011 this._selectNode(this.currentElement[0]);
6018 * @method cmd_unlink
6019 * @param value Value passed from the execCommand method
6020 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
6022 cmd_unlink: function(value) {
6023 this._swapEl(this.currentElement[0], 'span', function(el) {
6024 el.className = 'yui-non';
6029 * @method cmd_createlink
6030 * @param value Value passed from the execCommand method
6031 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
6033 cmd_createlink: function(value) {
6034 var el = this._getSelectedElement(), _a = null;
6035 if (this._hasParent(el, 'a')) {
6036 this.currentElement[0] = this._hasParent(el, 'a');
6037 } else if (this._isElement(el, 'li')) {
6038 _a = this._getDoc().createElement('a');
6039 _a.innerHTML = el.innerHTML;
6042 this.currentElement[0] = _a;
6043 } else if (!this._isElement(el, 'a')) {
6044 this._createCurrentElement('a');
6045 _a = this._swapEl(this.currentElement[0], 'a');
6046 this.currentElement[0] = _a;
6048 this.currentElement[0] = el;
6053 * @method cmd_insertimage
6054 * @param value Value passed from the execCommand method
6055 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
6057 cmd_insertimage: function(value) {
6058 var exec = true, _img = null, action = 'insertimage',
6059 el = this._getSelectedElement();
6062 value = this.get('blankimage');
6066 * @knownissue Safari Cursor Position
6067 * @browser Safari 2.x
6068 * @description The issue here is that we have no way of knowing where the cursor position is
6069 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6072 if (this._isElement(el, 'img')) {
6073 this.currentElement[0] = el;
6076 if (this._getDoc().queryCommandEnabled(action)) {
6077 this._getDoc().execCommand(action, false, value);
6078 var imgs = this._getDoc().getElementsByTagName('img');
6079 for (var i = 0; i < imgs.length; i++) {
6080 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
6081 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
6082 this.currentElement[0] = imgs[i];
6087 if (el == this._getDoc().body) {
6088 _img = this._getDoc().createElement('img');
6089 _img.setAttribute('src', value);
6090 YAHOO.util.Dom.addClass(_img, 'yui-img');
6091 this._getDoc().body.appendChild(_img);
6093 this._createCurrentElement('img');
6094 _img = this._getDoc().createElement('img');
6095 _img.setAttribute('src', value);
6096 YAHOO.util.Dom.addClass(_img, 'yui-img');
6097 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
6099 this.currentElement[0] = _img;
6106 * @method cmd_inserthtml
6107 * @param value Value passed from the execCommand method
6108 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6110 cmd_inserthtml: function(value) {
6111 var exec = true, action = 'inserthtml', _span = null, _range = null;
6113 * @knownissue Safari cursor position
6114 * @browser Safari 2.x
6115 * @description The issue here is that we have no way of knowing where the cursor position is
6116 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6118 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6119 this._createCurrentElement('img');
6120 _span = this._getDoc().createElement('span');
6121 _span.innerHTML = value;
6122 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6124 } else if (this.browser.ie) {
6125 _range = this._getRange();
6127 _range.item(0).outerHTML = value;
6129 _range.pasteHTML(value);
6137 * @param tag The tag of the list you want to create (eg, ul or ol)
6138 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6140 cmd_list: function(tag) {
6141 var exec = true, list = null, li = 0, el = null, str = '',
6142 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6144 action = 'insertunorderedlist';
6147 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6148 * @browser Safari 2.x
6149 * The issue with this workaround is that when applied to a set of text
6150 * that has BR's in it, Safari may or may not pick up the individual items as
6151 * list items. This is fixed in WebKit (Safari 3)
6152 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6154 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6155 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) {
6156 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6157 el = selEl.parentNode;
6158 list = this._getDoc().createElement('span');
6159 YAHOO.util.Dom.addClass(list, 'yui-non');
6161 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div');
6162 for (li = 0; li < lis.length; li++) {
6163 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>';
6165 list.innerHTML = str;
6166 this.currentElement[0] = el;
6167 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6169 this._createCurrentElement(tag.toLowerCase());
6170 list = this._getDoc().createElement(tag);
6171 for (li = 0; li < this.currentElement.length; li++) {
6172 var newli = this._getDoc().createElement('li');
6173 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
6174 list.appendChild(newli);
6176 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6179 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'),
6180 items = list.firstChild.innerHTML.split(b_tag), i, item;
6181 if (items.length > 0) {
6182 list.innerHTML = '';
6183 for (i = 0; i < items.length; i++) {
6184 item = this._getDoc().createElement('li');
6185 item.innerHTML = items[i];
6186 list.appendChild(item);
6190 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6191 this.currentElement[0] = list;
6192 var _h = this.currentElement[0].firstChild;
6193 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6194 if (this.browser.webkit) {
6195 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6200 el = this._getSelectedElement();
6201 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..
6202 if (this.browser.ie) {
6203 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6204 el = el.getElementsByTagName('li')[0];
6207 var lis2 = el.parentNode.getElementsByTagName('li');
6208 for (var j = 0; j < lis2.length; j++) {
6209 str += lis2[j].innerHTML + '<br>';
6211 var newEl = this._getDoc().createElement('span');
6212 newEl.innerHTML = str;
6213 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6216 this._getDoc().execCommand(action, '', el.parentNode);
6221 if (this.browser.opera) {
6223 window.setTimeout(function() {
6224 var liso = self._getDoc().getElementsByTagName('li');
6225 for (var i = 0; i < liso.length; i++) {
6226 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6227 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6232 if (this.browser.ie && exec) {
6234 if (this._getRange().html) {
6235 html = '<li>' + this._getRange().html+ '</li>';
6237 var t = this._getRange().text.split('\n');
6240 for (var ie = 0; ie < t.length; ie++) {
6241 html += '<li>' + t[ie] + '</li>';
6244 var txt = this._getRange().text;
6246 html = '<li id="new_list_item">' + txt + '</li>';
6248 html = '<li>' + txt + '</li>';
6252 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6253 var new_item = this._getDoc().getElementById('new_list_item');
6255 var range = this._getDoc().body.createTextRange();
6256 range.moveToElementText(new_item);
6257 range.collapse(false);
6267 * @method cmd_insertorderedlist
6268 * @param value Value passed from the execCommand method
6269 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6271 cmd_insertorderedlist: function(value) {
6272 return [this.cmd_list('ol')];
6275 * @method cmd_insertunorderedlist
6276 * @param value Value passed from the execCommand method
6277 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6279 cmd_insertunorderedlist: function(value) {
6280 return [this.cmd_list('ul')];
6283 * @method cmd_fontname
6284 * @param value Value passed from the execCommand method
6285 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6287 cmd_fontname: function(value) {
6289 selEl = this._getSelectedElement();
6291 this.currentFont = value;
6292 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6293 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6295 } else if (this.get('insert') && !this._hasSelection()) {
6296 var el = this._createInsertElement({ fontFamily: value });
6302 * @method cmd_fontsize
6303 * @param value Value passed from the execCommand method
6304 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6306 cmd_fontsize: function(value) {
6307 var el = null, go = true;
6308 el = this._getSelectedElement();
6309 if (this.browser.webkit) {
6310 if (this.currentElement[0]) {
6311 if (el == this.currentElement[0]) {
6313 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6314 this._selectNode(el);
6315 this.currentElement[0] = el;
6320 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6321 el = this._getSelectedElement();
6322 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6323 if (this.get('insert') && this.browser.ie) {
6324 var r = this._getRange();
6328 this._selectNode(el);
6330 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6331 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6333 if (this.get('insert') && !this._hasSelection()) {
6334 el = this._createInsertElement({ fontSize: value });
6335 this.currentElement[0] = el;
6336 this._selectNode(this.currentElement[0]);
6338 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6339 this._selectNode(this.currentElement[0]);
6349 * @param {HTMLElement} el The element to swap with
6350 * @param {String} tagName The tagname of the element that you wish to create
6351 * @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.
6352 * @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.
6354 _swapEl: function(el, tagName, callback) {
6355 var _el = this._getDoc().createElement(tagName);
6357 _el.innerHTML = el.innerHTML;
6359 if (typeof callback == 'function') {
6360 callback.call(this, _el);
6363 el.parentNode.replaceChild(_el, el);
6369 * @method _createInsertElement
6370 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6371 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6372 * @return {HTMLElement}
6374 _createInsertElement: function(css) {
6375 this._createCurrentElement('span', css);
6376 var el = this.currentElement[0];
6377 if (this.browser.webkit) {
6378 //Little Safari Hackery here..
6379 el.innerHTML = '<span class="yui-non"> </span>';
6381 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6382 } else if (this.browser.ie || this.browser.opera) {
6383 el.innerHTML = ' ';
6386 this._selectNode(el, true);
6391 * @method _createCurrentElement
6392 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6393 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6394 * @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.
6395 * 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
6396 * <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.
6398 _createCurrentElement: function(tagName, tagStyle) {
6399 tagName = ((tagName) ? tagName : 'a');
6402 _doc = this._getDoc();
6404 if (this.currentFont) {
6408 tagStyle.fontFamily = this.currentFont;
6409 this.currentFont = null;
6411 this.currentElement = [];
6413 var _elCreate = function(tagName, tagStyle) {
6415 tagName = ((tagName) ? tagName : 'span');
6416 tagName = tagName.toLowerCase();
6424 el = _doc.createElement(tagName);
6427 el = _doc.createElement(tagName);
6428 if (tagName === 'span') {
6429 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6430 YAHOO.util.Dom.addClass(el, 'yui-tag');
6431 el.setAttribute('tag', tagName);
6434 for (var k in tagStyle) {
6435 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6436 el.style[k] = tagStyle[k];
6444 if (!this._hasSelection()) {
6445 if (this._getDoc().queryCommandEnabled('insertimage')) {
6446 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6447 var imgs = this._getDoc().getElementsByTagName('img');
6448 for (var j = 0; j < imgs.length; j++) {
6449 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6450 el = _elCreate(tagName, tagStyle);
6451 imgs[j].parentNode.replaceChild(el, imgs[j]);
6452 this.currentElement[this.currentElement.length] = el;
6456 if (this.currentEvent) {
6457 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6460 tar = this._getDoc().body;
6465 * @knownissue Safari Cursor Position
6466 * @browser Safari 2.x
6467 * @description The issue here is that we have no way of knowing where the cursor position is
6468 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6470 el = _elCreate(tagName, tagStyle);
6471 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6472 if (this._isElement(tar, 'html')) {
6473 tar = this._getDoc().body;
6475 tar.appendChild(el);
6476 } else if (tar.nextSibling) {
6477 tar.parentNode.insertBefore(el, tar.nextSibling);
6479 tar.parentNode.appendChild(el);
6481 //this.currentElement = el;
6482 this.currentElement[this.currentElement.length] = el;
6483 this.currentEvent = null;
6484 if (this.browser.webkit) {
6485 //Force Safari to focus the new element
6486 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6487 if (this.browser.webkit3) {
6488 this._getSelection().collapseToStart();
6490 this._getSelection().collapse(true);
6495 //Force CSS Styling for this action...
6496 this._setEditorStyle(true);
6497 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6498 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6500 if (!this._isElement(this._getSelectedElement(), 'body')) {
6501 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6502 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6504 for (var _els = 0; _els < __els.length; _els++) {
6505 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6506 for (var e = 0; e < _tmp1.length; e++) {
6507 _tmp[_tmp.length] = _tmp1[e];
6512 for (var i = 0; i < _tmp.length; i++) {
6513 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6514 if (tagName !== 'span') {
6515 el = _elCreate(tagName, tagStyle);
6517 el = _elCreate(_tmp[i].tagName, tagStyle);
6519 el.innerHTML = _tmp[i].innerHTML;
6520 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6521 var fc = _tmp[i].getElementsByTagName('li')[0];
6522 _tmp[i].style.fontFamily = 'inherit';
6523 fc.style.fontFamily = 'inherit';
6524 el.innerHTML = fc.innerHTML;
6527 this.currentElement[this.currentElement.length] = el;
6528 } else if (this._isElement(_tmp[i], 'li')) {
6529 _tmp[i].innerHTML = '';
6530 _tmp[i].appendChild(el);
6531 _tmp[i].style.fontFamily = 'inherit';
6532 this.currentElement[this.currentElement.length] = el;
6534 if (_tmp[i].parentNode) {
6535 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6536 this.currentElement[this.currentElement.length] = el;
6537 this.currentEvent = null;
6538 if (this.browser.webkit) {
6539 //Force Safari to focus the new element
6540 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6541 if (this.browser.webkit3) {
6542 this._getSelection().collapseToStart();
6544 this._getSelection().collapse(true);
6547 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6548 this._getSelection().empty();
6550 if (this.browser.gecko) {
6551 this._getSelection().collapseToStart();
6557 var len = this.currentElement.length;
6558 for (var o = 0; o < len; o++) {
6559 if ((o + 1) != len) { //Skip the last one in the list
6560 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6561 if (this._isElement(this.currentElement[o], 'br')) {
6562 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6571 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6574 saveHTML: function() {
6575 var html = this.cleanHTML();
6576 if (this._textarea) {
6577 this.get('element').value = html;
6579 this.get('element').innerHTML = html;
6581 if (this.get('saveEl') !== this.get('element')) {
6582 var out = this.get('saveEl');
6583 if (Lang.isString(out)) {
6587 if (out.tagName.toLowerCase() === 'textarea') {
6590 out.innerHTML = html;
6597 * @method setEditorHTML
6598 * @param {String} incomingHTML The html content to load into the editor
6599 * @description Loads HTML into the editors body
6601 setEditorHTML: function(incomingHTML) {
6602 var html = this._cleanIncomingHTML(incomingHTML);
6603 html = html.replace(/RIGHT_BRACKET/gi, '{');
6604 html = html.replace(/LEFT_BRACKET/gi, '}');
6605 this._getDoc().body.innerHTML = html;
6609 * @method getEditorHTML
6610 * @description Gets the unprocessed/unfiltered HTML from the editor
6612 getEditorHTML: function() {
6614 var b = this._getDoc().body;
6618 return this._getDoc().body.innerHTML;
6625 * @description This method needs to be called if the Editor was hidden (like in a TabView or Panel). It is used to reset the editor after being in a container that was set to display none.
6628 if (this.browser.gecko) {
6629 this._setDesignMode('on');
6632 if (this.browser.webkit) {
6634 window.setTimeout(function() {
6635 self._setInitialContent.call(self);
6638 //Adding this will close all other Editor window's when showing this one.
6639 if (this.currentWindow) {
6642 //Put the iframe back in place
6643 this.get('iframe').setStyle('position', 'static');
6644 this.get('iframe').setStyle('left', '');
6648 * @description This method needs to be called if the Editor is to be hidden (like in a TabView or Panel). It should be called to clear timeouts and close open editor windows.
6651 //Adding this will close all other Editor window's.
6652 if (this.currentWindow) {
6655 if (this._fixNodesTimer) {
6656 clearTimeout(this._fixNodesTimer);
6657 this._fixNodesTimer = null;
6659 if (this._nodeChangeTimer) {
6660 clearTimeout(this._nodeChangeTimer);
6661 this._nodeChangeTimer = null;
6663 this._lastNodeChange = 0;
6664 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6665 this.get('iframe').setStyle('position', 'absolute');
6666 this.get('iframe').setStyle('left', '-9999px');
6669 * @method _cleanIncomingHTML
6670 * @param {String} html The unfiltered HTML
6671 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6672 * @return {String} The filtered HTML
6674 _cleanIncomingHTML: function(html) {
6675 html = html.replace(/{/gi, 'RIGHT_BRACKET');
6676 html = html.replace(/}/gi, 'LEFT_BRACKET');
6678 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6679 html = html.replace(/<\/strong>/gi, '</b>');
6681 //replace embed before em check
6682 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6683 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6685 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6686 html = html.replace(/<\/em>/gi, '</i>');
6687 html = html.replace(/_moz_dirty=""/gi, '');
6689 //Put embed tags back in..
6690 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6691 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6692 if (this.get('plainText')) {
6693 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6694 html = html.replace(/ /gi, ' '); //Replace all double spaces
6695 html = html.replace(/\t/gi, ' '); //Replace all tabs
6697 //Removing Script Tags from the Editor
6698 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6699 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6700 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6701 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6702 //Replace the line feeds
6703 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6705 //Remove Bad HTML elements (used to be script nodes)
6706 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6707 //Replace the lines feeds
6708 html = html.replace(/<YUI_LF>/g, '\n');
6713 * @param {String} html The unfiltered HTML
6714 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6715 * @return {String} The filtered HTML
6717 cleanHTML: function(html) {
6718 //Start Filtering Output
6721 html = this.getEditorHTML();
6723 var markup = this.get('markup');
6724 //Make some backups...
6725 html = this.pre_filter_linebreaks(html, markup);
6728 html = this.filter_msword(html);
6730 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6731 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6733 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6734 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6736 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6737 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6738 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6739 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6741 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6742 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6744 //Convert b and i tags to strong and em tags
6745 if ((markup == 'semantic') || (markup == 'xhtml')) {
6746 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6747 html = html.replace(/<\/i>/gi, '</em>');
6748 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6749 html = html.replace(/<\/b>/gi, '</strong>');
6752 html = html.replace(/_moz_dirty=""/gi, '');
6754 //normalize strikethrough
6755 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6756 html = html.replace(/\/strike>/gi, '/span>');
6760 if (this.browser.ie) {
6761 html = html.replace(/text-decoration/gi, 'text-decoration');
6762 html = html.replace(/font-weight/gi, 'font-weight');
6763 html = html.replace(/_width="([^>]*)"/gi, '');
6764 html = html.replace(/_height="([^>]*)"/gi, '');
6765 //Cleanup Image URL's
6766 var url = this._baseHREF.replace(/\//gi, '\\/'),
6767 re = new RegExp('src="' + url, 'gi');
6768 html = html.replace(re, 'src="');
6770 html = html.replace(/<font/gi, '<font');
6771 html = html.replace(/<\/font>/gi, '</font>');
6772 html = html.replace(/<span/gi, '<span');
6773 html = html.replace(/<\/span>/gi, '</span>');
6774 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6775 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6776 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6777 if (this.browser.webkit) {
6778 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6779 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6781 html = html.replace(/\/u>/gi, '/span>');
6782 if (markup == 'css') {
6783 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6784 html = html.replace(/<\/em>/gi, '</i>');
6785 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6786 html = html.replace(/<\/strong>/gi, '</b>');
6787 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6788 html = html.replace(/\/b>/gi, '/span>');
6789 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6790 html = html.replace(/\/i>/gi, '/span>');
6792 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6794 html = html.replace(/<u/gi, '<u');
6795 html = html.replace(/\/u>/gi, '/u>');
6797 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6798 html = html.replace(/\/ol>/gi, '/ol>');
6799 html = html.replace(/<li/gi, '<li');
6800 html = html.replace(/\/li>/gi, '/li>');
6801 html = this.filter_safari(html);
6803 html = this.filter_internals(html);
6805 html = this.filter_all_rgb(html);
6807 //Replace our backups with the real thing
6808 html = this.post_filter_linebreaks(html, markup);
6810 if (markup == 'xhtml') {
6811 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6812 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6814 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6815 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6817 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6818 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6820 html = this.filter_invalid_lists(html);
6822 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6823 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6825 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6826 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6828 //This should fix &'s in URL's
6829 html = html.replace(/ & /gi, ' YUI_AMP ');
6830 html = html.replace(/ &/gi, ' YUI_AMP_F ');
6831 html = html.replace(/& /gi, ' YUI_AMP_R ');
6832 html = html.replace(/&/gi, '&');
6833 html = html.replace(/ YUI_AMP /gi, ' & ');
6834 html = html.replace(/ YUI_AMP_F /gi, ' &');
6835 html = html.replace(/ YUI_AMP_R /gi, '& ');
6837 //Trim the output, removing whitespace from the beginning and end
6838 html = YAHOO.lang.trim(html);
6840 if (this.get('removeLineBreaks')) {
6841 html = html.replace(/\n/g, '').replace(/\r/g, '');
6842 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6845 for (var v in this.invalidHTML) {
6846 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6847 if (Lang.isObject(v) && v.keepContents) {
6848 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6850 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6855 /* LATER -- Add DOM manipulation
6857 var frag = document.createDocumentFragment();
6858 frag.innerHTML = html;
6860 var ps = frag.getElementsByTagName('p'),
6862 for (var i = 0; i < len; i++) {
6863 var ps2 = ps[i].getElementsByTagName('p');
6869 html = frag.innerHTML;
6873 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6878 * @method filter_msword
6879 * @param String html The HTML string to filter
6880 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6882 filter_msword: function(html) {
6883 if (!this.get('filterWord')) {
6886 //Remove the ms o: tags
6887 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6888 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6890 //Remove the ms w: tags
6891 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6893 //Remove mso-? styles.
6894 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6896 //Remove more bogus MS styles.
6897 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6898 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6899 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6900 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6901 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6902 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6903 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6904 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6906 //Remove XML declarations
6907 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6910 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6912 //Remove language tags
6913 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6915 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6916 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6917 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
6922 * @method filter_invalid_lists
6923 * @param String html The HTML string to filter
6924 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6926 filter_invalid_lists: function(html) {
6927 html = html.replace(/<\/li>\n/gi, '</li>');
6929 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6930 html = html.replace(/<\/ol>/gi, '</ol></li>');
6931 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
6933 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6934 html = html.replace(/<\/ul>/gi, '</ul></li>');
6935 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
6937 html = html.replace(/<\/li>/gi, "</li>");
6938 html = html.replace(/<\/ol>/gi, "</ol>");
6939 html = html.replace(/<ol>/gi, "<ol>");
6940 html = html.replace(/<ul>/gi, "<ul>");
6944 * @method filter_safari
6945 * @param String html The HTML string to filter
6946 * @description Filters strings specific to Safari
6949 filter_safari: function(html) {
6950 if (this.browser.webkit) {
6951 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6952 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
6953 html = html.replace(/Apple-style-span/gi, '');
6954 html = html.replace(/style="line-height: normal;"/gi, '');
6955 html = html.replace(/yui-wk-div/gi, '');
6956 html = html.replace(/yui-wk-p/gi, '');
6960 html = html.replace(/<li><\/li>/gi, '');
6961 html = html.replace(/<li> <\/li>/gi, '');
6962 html = html.replace(/<li> <\/li>/gi, '');
6963 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6964 if (this.get('ptags')) {
6965 html = html.replace(/<div([^>]*)>/g, '<p$1>');
6966 html = html.replace(/<\/div>/gi, '</p>');
6968 //html = html.replace(/<div>/gi, '<br>');
6969 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>');
6970 html = html.replace(/<\/div>/gi, '');
6976 * @method filter_internals
6977 * @param String html The HTML string to filter
6978 * @description Filters internal RTE strings and bogus attrs we don't want
6981 filter_internals: function(html) {
6982 html = html.replace(/\r/g, '');
6983 //Fix stuff we don't want
6984 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6986 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6988 html = html.replace(/yui-tag-span/gi, '');
6989 html = html.replace(/yui-tag/gi, '');
6990 html = html.replace(/yui-non/gi, '');
6991 html = html.replace(/yui-img/gi, '');
6992 html = html.replace(/ tag="span"/gi, '');
6993 html = html.replace(/ class=""/gi, '');
6994 html = html.replace(/ style=""/gi, '');
6995 html = html.replace(/ class=" "/gi, '');
6996 html = html.replace(/ class=" "/gi, '');
6997 html = html.replace(/ target=""/gi, '');
6998 html = html.replace(/ title=""/gi, '');
7000 if (this.browser.ie) {
7001 html = html.replace(/ class= /gi, '');
7002 html = html.replace(/ class= >/gi, '');
7008 * @method filter_all_rgb
7009 * @param String str The HTML string to filter
7010 * @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"
7013 filter_all_rgb: function(str) {
7014 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
7015 var arr = str.match(exp);
7016 if (Lang.isArray(arr)) {
7017 for (var i = 0; i < arr.length; i++) {
7018 var color = this.filter_rgb(arr[i]);
7019 str = str.replace(arr[i].toString(), color);
7026 * @method filter_rgb
7027 * @param String css The CSS string containing rgb(#,#,#);
7028 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
7031 filter_rgb: function(css) {
7032 if (css.toLowerCase().indexOf('rgb') != -1) {
7033 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
7034 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
7036 if (rgb.length == 5) {
7037 var r = parseInt(rgb[1], 10).toString(16);
7038 var g = parseInt(rgb[2], 10).toString(16);
7039 var b = parseInt(rgb[3], 10).toString(16);
7041 r = r.length == 1 ? '0' + r : r;
7042 g = g.length == 1 ? '0' + g : g;
7043 b = b.length == 1 ? '0' + b : b;
7045 css = "#" + r + g + b;
7051 * @method pre_filter_linebreaks
7052 * @param String html The HTML to filter
7053 * @param String markup The markup type to filter to
7054 * @description HTML Pre Filter
7057 pre_filter_linebreaks: function(html, markup) {
7058 if (this.browser.webkit) {
7059 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
7060 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
7062 html = html.replace(/<br>/gi, '<YUI_BR>');
7063 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
7064 html = html.replace(/<br\/>/gi, '<YUI_BR>');
7065 html = html.replace(/<br \/>/gi, '<YUI_BR>');
7066 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
7067 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
7068 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
7069 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
7071 html = html.replace(/<YUI_BR>$/, '');
7073 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
7074 if (this.browser.ie) {
7075 html = html.replace(/ /g, '\t');
7080 * @method post_filter_linebreaks
7081 * @param String html The HTML to filter
7082 * @param String markup The markup type to filter to
7083 * @description HTML Pre Filter
7086 post_filter_linebreaks: function(html, markup) {
7087 if (markup == 'xhtml') {
7088 html = html.replace(/<YUI_BR>/g, '<br />');
7090 html = html.replace(/<YUI_BR>/g, '<br>');
7095 * @method clearEditorDoc
7096 * @description Clear the doc of the Editor
7098 clearEditorDoc: function() {
7099 this._getDoc().body.innerHTML = ' ';
7102 * @method openWindow
7103 * @description Override Method for Advanced Editor
7105 openWindow: function(win) {
7108 * @method moveWindow
7109 * @description Override Method for Advanced Editor
7111 moveWindow: function() {
7115 * @method _closeWindow
7116 * @description Override Method for Advanced Editor
7118 _closeWindow: function() {
7121 * @method closeWindow
7122 * @description Override Method for Advanced Editor
7124 closeWindow: function() {
7125 //this.unsubscribeAll('afterExecCommand');
7126 this.toolbar.resetAllButtons();
7131 * @description Destroys the editor, all of it's elements and objects.
7134 destroy: function() {
7135 if (this._nodeChangeDelayTimer) {
7136 clearTimeout(this._nodeChangeDelayTimer);
7141 this.resize.destroy();
7146 if (this.get('panel')) {
7147 this.get('panel').destroy();
7150 this.toolbar.destroy();
7151 this.setStyle('visibility', 'visible');
7152 this.setStyle('position', 'static');
7153 this.setStyle('top', '');
7154 this.setStyle('left', '');
7155 var textArea = this.get('element');
7156 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7157 this.get('element_cont').get('element').innerHTML = '';
7158 this.set('handleSubmit', false); //Remove the submit handler
7163 * @description Returns a string representing the editor.
7166 toString: function() {
7167 var str = 'SimpleEditor';
7168 if (this.get && this.get('element_cont')) {
7169 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7176 * @event toolbarLoaded
7177 * @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.
7178 * @type YAHOO.util.CustomEvent
7182 * @description Event is fired after the cleanHTML method is called.
7183 * @type YAHOO.util.CustomEvent
7186 * @event afterRender
7187 * @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.
7188 * @type YAHOO.util.CustomEvent
7191 * @event editorContentLoaded
7192 * @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.
7193 * @type YAHOO.util.CustomEvent
7196 * @event beforeNodeChange
7197 * @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.
7198 * @type YAHOO.util.CustomEvent
7201 * @event afterNodeChange
7202 * @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.
7203 * @type YAHOO.util.CustomEvent
7206 * @event beforeExecCommand
7207 * @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.
7208 * @type YAHOO.util.CustomEvent
7211 * @event afterExecCommand
7212 * @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.
7213 * @type YAHOO.util.CustomEvent
7216 * @event editorMouseUp
7217 * @param {Event} ev The DOM Event that occured
7218 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7219 * @type YAHOO.util.CustomEvent
7222 * @event editorMouseDown
7223 * @param {Event} ev The DOM Event that occured
7224 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7225 * @type YAHOO.util.CustomEvent
7228 * @event editorDoubleClick
7229 * @param {Event} ev The DOM Event that occured
7230 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7231 * @type YAHOO.util.CustomEvent
7234 * @event editorClick
7235 * @param {Event} ev The DOM Event that occured
7236 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7237 * @type YAHOO.util.CustomEvent
7240 * @event editorKeyUp
7241 * @param {Event} ev The DOM Event that occured
7242 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7243 * @type YAHOO.util.CustomEvent
7246 * @event editorKeyPress
7247 * @param {Event} ev The DOM Event that occured
7248 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7249 * @type YAHOO.util.CustomEvent
7252 * @event editorKeyDown
7253 * @param {Event} ev The DOM Event that occured
7254 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7255 * @type YAHOO.util.CustomEvent
7258 * @event beforeEditorMouseUp
7259 * @param {Event} ev The DOM Event that occured
7260 * @description Fires before editor event, returning false will stop the internal processing.
7261 * @type YAHOO.util.CustomEvent
7264 * @event beforeEditorMouseDown
7265 * @param {Event} ev The DOM Event that occured
7266 * @description Fires before editor event, returning false will stop the internal processing.
7267 * @type YAHOO.util.CustomEvent
7270 * @event beforeEditorDoubleClick
7271 * @param {Event} ev The DOM Event that occured
7272 * @description Fires before editor event, returning false will stop the internal processing.
7273 * @type YAHOO.util.CustomEvent
7276 * @event beforeEditorClick
7277 * @param {Event} ev The DOM Event that occured
7278 * @description Fires before editor event, returning false will stop the internal processing.
7279 * @type YAHOO.util.CustomEvent
7282 * @event beforeEditorKeyUp
7283 * @param {Event} ev The DOM Event that occured
7284 * @description Fires before editor event, returning false will stop the internal processing.
7285 * @type YAHOO.util.CustomEvent
7288 * @event beforeEditorKeyPress
7289 * @param {Event} ev The DOM Event that occured
7290 * @description Fires before editor event, returning false will stop the internal processing.
7291 * @type YAHOO.util.CustomEvent
7294 * @event beforeEditorKeyDown
7295 * @param {Event} ev The DOM Event that occured
7296 * @description Fires before editor event, returning false will stop the internal processing.
7297 * @type YAHOO.util.CustomEvent
7301 * @event editorWindowFocus
7302 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7303 * @type YAHOO.util.CustomEvent
7306 * @event editorWindowBlur
7307 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7308 * @type YAHOO.util.CustomEvent
7313 * @description Singleton object used to track the open window objects and panels across the various open editors
7317 YAHOO.widget.EditorInfo = {
7320 * @property _instances
7321 * @description A reference to all editors on the page.
7327 * @property blankImage
7328 * @description A reference to the blankImage url
7335 * @description A reference to the currently open window object in any editor on the page.
7336 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7342 * @description A reference to the currently open panel in any editor on the page.
7343 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7347 * @method getEditorById
7348 * @description Returns a reference to the Editor object associated with the given textarea
7349 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7350 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7352 getEditorById: function(id) {
7353 if (!YAHOO.lang.isString(id)) {
7354 //Not a string, assume a node Reference
7357 if (this._instances[id]) {
7358 return this._instances[id];
7364 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved.
7365 * @param {HTMLElement} form The form to check if this Editor instance belongs to
7367 saveAll: function(form) {
7368 var i, e, items = YAHOO.widget.EditorInfo._instances;
7371 if (Lang.hasOwnProperty(items, i)) {
7373 if (e.get('element').form && (e.get('element').form == form)) {
7380 if (Lang.hasOwnProperty(items, i)) {
7381 items[i].saveHTML();
7388 * @description Returns a string representing the EditorInfo.
7391 toString: function() {
7393 for (var i in this._instances) {
7394 if (Lang.hasOwnProperty(this._instances, i)) {
7398 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7408 * @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>
7409 * @namespace YAHOO.widget
7410 * @requires yahoo, dom, element, event, container_core, simpleeditor
7411 * @optional dragdrop, animation, menu, button, resize
7415 var Dom = YAHOO.util.Dom,
7416 Event = YAHOO.util.Event,
7418 Toolbar = YAHOO.widget.Toolbar;
7421 * 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.
7424 * @extends YAHOO.widget.SimpleEditor
7425 * @param {String/HTMLElement} el The textarea element to turn into an editor.
7426 * @param {Object} attrs Object liternal containing configuration parameters.
7429 YAHOO.widget.Editor = function(el, attrs) {
7430 YAHOO.widget.Editor.superclass.constructor.call(this, el, attrs);
7433 YAHOO.extend(YAHOO.widget.Editor, YAHOO.widget.SimpleEditor, {
7436 * @property _undoCache
7437 * @description An Array hash of the Undo Levels.
7443 * @property _undoLevel
7444 * @description The index of the current undo state.
7450 * @method _hasUndoLevel
7451 * @description Checks to see if we have an undo level available
7454 _hasUndoLevel: function() {
7455 return ((this._undoCache.length > 1) && this._undoLevel);
7459 * @method _undoNodeChange
7460 * @description nodeChange listener for undo processing
7462 _undoNodeChange: function() {
7463 var undo_button = this.toolbar.getButtonByValue('undo'),
7464 redo_button = this.toolbar.getButtonByValue('redo');
7465 if (undo_button && redo_button) {
7466 if (this._hasUndoLevel()) {
7467 this.toolbar.enableButton(undo_button);
7469 if (this._undoLevel < this._undoCache.length) {
7470 this.toolbar.enableButton(redo_button);
7473 this._lastCommand = null;
7477 * @method _checkUndo
7478 * @description Prunes the undo cache when it reaches the maxUndo config
7480 _checkUndo: function() {
7481 var len = this._undoCache.length,
7483 if (len >= this.get('maxUndo')) {
7484 for (var i = (len - this.get('maxUndo')); i < len; i++) {
7485 tmp.push(this._undoCache[i]);
7487 this._undoCache = tmp;
7493 * @description Puts the content of the Editor into the _undoCache.
7494 * //TODO Convert the hash to a series of TEXTAREAS to store state in.
7495 * @param {String} str The content of the Editor
7497 _putUndo: function(str) {
7498 if (this._undoLevel === this._undoCache.length) {
7499 this._undoCache.push(str);
7500 this._undoLevel = this._undoCache.length;
7502 var str = this.getEditorHTML();
7503 var last = this._undoCache[this._undoLevel];
7506 this._undoCache = [];
7507 this._undoLevel = 0;
7515 * @description Get's a level from the undo cache.
7516 * @param {Number} index The index of the undo level we want to get.
7519 _getUndo: function(index) {
7520 this._undoLevel = index;
7521 return this._undoCache[index];
7525 * @method _storeUndo
7526 * @description Method to call when you want to store an undo state. Currently called from nodeChange and _handleKeyUp
7528 _storeUndo: function() {
7529 if (this._lastCommand === 'undo' || this._lastCommand === 'redo') {
7532 if (!this._undoCache) {
7533 this._undoCache = [];
7534 this._undoLevel = 0;
7537 var str = this.getEditorHTML();
7538 //var last = this._undoCache[this._undoCache.length - 1];
7539 var last = this._undoCache[this._undoLevel - 1];
7547 this._undoNodeChange();
7550 * @property STR_BEFORE_EDITOR
7551 * @description The accessibility string for the element before the iFrame
7554 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Control + Shift + T to place focus on the toolbar and navigate between option heading names. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift [ aligns text left</li> <li>Control Shift | centers text</li> <li>Control Shift ] aligns text right</li> <li>Control Shift L adds an HTML link</li> <li>To exit this text editor use the keyboard shortcut Control + Shift + ESC.</li></ul>',
7556 * @property STR_CLOSE_WINDOW
7557 * @description The Title of the close button in the Editor Window
7560 STR_CLOSE_WINDOW: 'Close Window',
7562 * @property STR_CLOSE_WINDOW_NOTE
7563 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
7566 STR_CLOSE_WINDOW_NOTE: 'To close this window use the Control + Shift + W key',
7568 * @property STR_IMAGE_PROP_TITLE
7569 * @description The title for the Image Property Editor Window
7572 STR_IMAGE_PROP_TITLE: 'Image Options',
7574 * @property STR_IMAGE_TITLE
7575 * @description The label string for Image Description
7578 STR_IMAGE_TITLE: 'Description',
7580 * @property STR_IMAGE_SIZE
7581 * @description The label string for Image Size
7584 STR_IMAGE_SIZE: 'Size',
7586 * @property STR_IMAGE_ORIG_SIZE
7587 * @description The label string for Original Image Size
7590 STR_IMAGE_ORIG_SIZE: 'Original Size',
7592 * @property STR_IMAGE_COPY
7593 * @description The label string for the image copy and paste message for Opera and Safari
7596 STR_IMAGE_COPY: '<span class="tip"><span class="icon icon-info"></span><strong>Note:</strong>To move this image just highlight it, cut, and paste where ever you\'d like.</span>',
7598 * @property STR_IMAGE_PADDING
7599 * @description The label string for the image padding.
7602 STR_IMAGE_PADDING: 'Padding',
7604 * @property STR_IMAGE_BORDER
7605 * @description The label string for the image border.
7608 STR_IMAGE_BORDER: 'Border',
7610 * @property STR_IMAGE_BORDER_SIZE
7611 * @description The label string for the image border size.
7614 STR_IMAGE_BORDER_SIZE: 'Border Size',
7616 * @property STR_IMAGE_BORDER_TYPE
7617 * @description The label string for the image border type.
7620 STR_IMAGE_BORDER_TYPE: 'Border Type',
7622 * @property STR_IMAGE_TEXTFLOW
7623 * @description The label string for the image text flow.
7626 STR_IMAGE_TEXTFLOW: 'Text Flow',
7628 * @property STR_LOCAL_FILE_WARNING
7629 * @description The label string for the local file warning.
7632 STR_LOCAL_FILE_WARNING: '<span class="tip"><span class="icon icon-warn"></span><strong>Note:</strong>This image/link points to a file on your computer and will not be accessible to others on the internet.</span>',
7634 * @property STR_LINK_PROP_TITLE
7635 * @description The label string for the Link Property Editor Window.
7638 STR_LINK_PROP_TITLE: 'Link Options',
7640 * @property STR_LINK_PROP_REMOVE
7641 * @description The label string for the Remove link from text link inside the property editor.
7644 STR_LINK_PROP_REMOVE: 'Remove link from text',
7646 * @property STR_LINK_NEW_WINDOW
7647 * @description The string for the open in a new window label.
7650 STR_LINK_NEW_WINDOW: 'Open in a new window.',
7652 * @property STR_LINK_TITLE
7653 * @description The string for the link description.
7656 STR_LINK_TITLE: 'Description',
7658 * @property STR_NONE
7659 * @description The string for the word none.
7665 * @property CLASS_LOCAL_FILE
7666 * @description CSS class applied to an element when it's found to have a local url.
7669 CLASS_LOCAL_FILE: 'warning-localfile',
7672 * @property CLASS_HIDDEN
7673 * @description CSS class applied to the body when the hiddenelements button is pressed.
7676 CLASS_HIDDEN: 'yui-hidden',
7679 * @description The Editor class' initialization method
7681 init: function(p_oElement, p_oAttributes) {
7684 if (!this._defaultToolbar) {
7685 this._defaultToolbar = {
7687 titlebar: 'Text Editing Tools',
7689 buttonType: 'advanced',
7691 { group: 'fontstyle', label: 'Font Name and Size',
7693 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
7695 { text: 'Arial', checked: true },
7696 { text: 'Arial Black' },
7697 { text: 'Comic Sans MS' },
7698 { text: 'Courier New' },
7699 { text: 'Lucida Console' },
7701 { text: 'Times New Roman' },
7702 { text: 'Trebuchet MS' },
7706 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
7709 { type: 'separator' },
7710 { group: 'textstyle', label: 'Font Style',
7712 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
7713 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
7714 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
7715 { type: 'separator' },
7716 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true },
7717 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true }
7720 { type: 'separator' },
7721 { group: 'textstyle2', label: ' ',
7723 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
7724 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true },
7725 { type: 'separator' },
7726 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true },
7727 { type: 'push', label: 'Show/Hide Hidden Elements', value: 'hiddenelements' }
7730 { type: 'separator' },
7731 { group: 'undoredo', label: 'Undo/Redo',
7733 { type: 'push', label: 'Undo', value: 'undo', disabled: true },
7734 { type: 'push', label: 'Redo', value: 'redo', disabled: true }
7738 { type: 'separator' },
7739 { group: 'alignment', label: 'Alignment',
7741 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
7742 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
7743 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
7744 { type: 'push', label: 'Justify', value: 'justifyfull' }
7747 { type: 'separator' },
7748 { group: 'parastyle', label: 'Paragraph Style',
7750 { type: 'select', label: 'Normal', value: 'heading', disabled: true,
7752 { text: 'Normal', value: 'none', checked: true },
7753 { text: 'Header 1', value: 'h1' },
7754 { text: 'Header 2', value: 'h2' },
7755 { text: 'Header 3', value: 'h3' },
7756 { text: 'Header 4', value: 'h4' },
7757 { text: 'Header 5', value: 'h5' },
7758 { text: 'Header 6', value: 'h6' }
7763 { type: 'separator' },
7765 { group: 'indentlist2', label: 'Indenting and Lists',
7767 { type: 'push', label: 'Indent', value: 'indent', disabled: true },
7768 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
7769 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
7770 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
7773 { type: 'separator' },
7774 { group: 'insertitem', label: 'Insert Item',
7776 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
7777 { type: 'push', label: 'Insert Image', value: 'insertimage' }
7784 if (!this._defaultImageToolbarConfig) {
7785 this._defaultImageToolbarConfig = {
7786 buttonType: this._defaultToolbar.buttonType,
7788 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
7790 { type: 'push', label: 'Left', value: 'left' },
7791 { type: 'push', label: 'Inline', value: 'inline' },
7792 { type: 'push', label: 'Block', value: 'block' },
7793 { type: 'push', label: 'Right', value: 'right' }
7796 { type: 'separator' },
7797 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
7799 { type: 'spin', label: '0', value: 'padding', range: [0, 50] }
7802 { type: 'separator' },
7803 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
7805 { type: 'select', label: this.STR_IMAGE_BORDER_SIZE, value: 'bordersize',
7807 { text: 'none', value: '0', checked: true },
7808 { text: '1px', value: '1' },
7809 { text: '2px', value: '2' },
7810 { text: '3px', value: '3' },
7811 { text: '4px', value: '4' },
7812 { text: '5px', value: '5' }
7815 { type: 'select', label: this.STR_IMAGE_BORDER_TYPE, value: 'bordertype', disabled: true,
7817 { text: 'Solid', value: 'solid', checked: true },
7818 { text: 'Dashed', value: 'dashed' },
7819 { text: 'Dotted', value: 'dotted' }
7822 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
7829 YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes);
7831 _render: function() {
7832 YAHOO.widget.Editor.superclass._render.apply(this, arguments);
7834 //Render the panel in another thread and delay it a little..
7835 window.setTimeout(function() {
7836 self._renderPanel.call(self);
7840 * @method initAttributes
7841 * @description Initializes all of the configuration attributes used to create
7843 * @param {Object} attr Object literal specifying a set of
7844 * configuration attributes used to create the editor.
7846 initAttributes: function(attr) {
7847 YAHOO.widget.Editor.superclass.initAttributes.call(this, attr);
7850 * @attribute localFileWarning
7851 * @description Should we throw the warning if we detect a file that is local to their machine?
7855 this.setAttributeConfig('localFileWarning', {
7856 value: attr.locaFileWarning || true
7860 * @attribute hiddencss
7861 * @description The CSS used to show/hide hidden elements on the page, these rules must be prefixed with the class provided in <code>this.CLASS_HIDDEN</code>
7862 * @default <code><pre>
7863 .yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u,
7864 .yui-hidden div, .yui-hidden p, .yui-hidden span, .yui-hidden img, .yui-hidden ul, .yui-hidden ol,
7865 .yui-hidden li, .yui-hidden table {
7866 border: 1px dotted #ccc;
7868 .yui-hidden .yui-non {
7876 this.setAttributeConfig('hiddencss', {
7877 value: attr.hiddencss || '.yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u, .yui-hidden div,.yui-hidden p,.yui-hidden span,.yui-hidden img, .yui-hidden ul, .yui-hidden ol, .yui-hidden li, .yui-hidden table { border: 1px dotted #ccc; } .yui-hidden .yui-non { border: none; } .yui-hidden img { padding: 2px; }',
7885 * @description A reference to the HTML elements used for the body of Editor Windows.
7890 * @method _defaultImageToolbar
7891 * @description A reference to the Toolbar Object inside Image Editor Window.
7893 _defaultImageToolbar: null,
7896 * @method _defaultImageToolbarConfig
7897 * @description Config to be used for the default Image Editor Window.
7899 _defaultImageToolbarConfig: null,
7903 * @description Fix href and imgs as well as remove invalid HTML.
7905 _fixNodes: function() {
7906 YAHOO.widget.Editor.superclass._fixNodes.call(this);
7910 var imgs = this._getDoc().getElementsByTagName('img');
7911 for (var im = 0; im < imgs.length; im++) {
7912 if (imgs[im].getAttribute('href', 2)) {
7913 url = imgs[im].getAttribute('src', 2);
7914 if (this._isLocalFile(url)) {
7915 Dom.addClass(imgs[im], this.CLASS_LOCAL_FILE);
7917 Dom.removeClass(imgs[im], this.CLASS_LOCAL_FILE);
7921 var fakeAs = this._getDoc().body.getElementsByTagName('a');
7922 for (var a = 0; a < fakeAs.length; a++) {
7923 if (fakeAs[a].getAttribute('href', 2)) {
7924 url = fakeAs[a].getAttribute('href', 2);
7925 if (this._isLocalFile(url)) {
7926 Dom.addClass(fakeAs[a], this.CLASS_LOCAL_FILE);
7928 Dom.removeClass(fakeAs[a], this.CLASS_LOCAL_FILE);
7936 * @property _disabled
7937 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
7940 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent' ],
7943 * @property _alwaysDisabled
7944 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
7947 _alwaysDisabled: { 'outdent': true },
7950 * @property _alwaysEnabled
7951 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
7954 _alwaysEnabled: { hiddenelements: true },
7957 * @method _handleKeyDown
7958 * @param {Event} ev The event we are working on.
7959 * @description Override method that handles some new keydown events inside the iFrame document.
7961 _handleKeyDown: function(ev) {
7962 YAHOO.widget.Editor.superclass._handleKeyDown.call(this, ev);
7967 switch (ev.keyCode) {
7969 case this._keyMap.JUSTIFY_LEFT.key: //Left
7970 if (this._checkKey(this._keyMap.JUSTIFY_LEFT, ev)) {
7971 action = 'justifyleft';
7975 //case 220: //Center
7976 case this._keyMap.JUSTIFY_CENTER.key:
7977 if (this._checkKey(this._keyMap.JUSTIFY_CENTER, ev)) {
7978 action = 'justifycenter';
7983 case this._keyMap.JUSTIFY_RIGHT.key:
7984 if (this._checkKey(this._keyMap.JUSTIFY_RIGHT, ev)) {
7985 action = 'justifyright';
7990 if (doExec && action) {
7991 this.execCommand(action, null);
7992 Event.stopEvent(ev);
7998 * @method _renderCreateLinkWindow
7999 * @description Pre renders the CreateLink window so we get faster window opening.
8001 _renderCreateLinkWindow: function() {
8002 var str = '<label for="' + this.get('id') + '_createlink_url"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_url" id="' + this.get('id') + '_createlink_url" value=""></label>';
8003 str += '<label for="' + this.get('id') + '_createlink_target"><strong> </strong><input type="checkbox" name="' + this.get('id') + '_createlink_target" id="' + this.get('id') + '_createlink_target" value="_blank" class="createlink_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>';
8004 str += '<label for="' + this.get('id') + '_createlink_title"><strong>' + this.STR_LINK_TITLE + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_title" id="' + this.get('id') + '_createlink_title" value=""></label>';
8006 var body = document.createElement('div');
8007 body.innerHTML = str;
8009 var unlinkCont = document.createElement('div');
8010 unlinkCont.className = 'removeLink';
8011 var unlink = document.createElement('a');
8013 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
8014 unlink.title = this.STR_LINK_PROP_REMOVE;
8015 Event.on(unlink, 'click', function(ev) {
8016 Event.stopEvent(ev);
8017 this.unsubscribeAll('afterExecCommand');
8018 this.execCommand('unlink');
8021 unlinkCont.appendChild(unlink);
8022 body.appendChild(unlinkCont);
8024 this._windows.createlink = {};
8025 this._windows.createlink.body = body;
8026 //body.style.display = 'none';
8027 Event.on(body, 'keyup', function(e) {
8028 Event.stopPropagation(e);
8030 this.get('panel').editor_form.appendChild(body);
8031 this.fireEvent('windowCreateLinkRender', { type: 'windowCreateLinkRender', panel: this.get('panel'), body: body });
8034 _handleCreateLinkClick: function() {
8035 var el = this._getSelectedElement();
8036 if (this._isElement(el, 'img')) {
8037 this.STOP_EXEC_COMMAND = true;
8038 this.currentElement[0] = el;
8039 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
8040 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
8043 if (this.get('limitCommands')) {
8044 if (!this.toolbar.getButtonByValue('createlink')) {
8049 this.on('afterExecCommand', function() {
8050 var win = new YAHOO.widget.EditorWindow('createlink', {
8054 var el = this.currentElement[0],
8061 if (el.getAttribute('href', 2) !== null) {
8062 url = el.getAttribute('href', 2);
8063 if (this._isLocalFile(url)) {
8064 //Local File throw Warning
8065 win.setFooter(this.STR_LOCAL_FILE_WARNING);
8071 if (el.getAttribute('title') !== null) {
8072 title = el.getAttribute('title');
8074 if (el.getAttribute('target') !== null) {
8075 target = el.getAttribute('target');
8079 if (this._windows.createlink && this._windows.createlink.body) {
8080 body = this._windows.createlink.body;
8082 body = this._renderCreateLinkWindow();
8085 win.setHeader(this.STR_LINK_PROP_TITLE);
8088 Event.purgeElement(this.get('id') + '_createlink_url');
8090 Dom.get(this.get('id') + '_createlink_url').value = url;
8091 Dom.get(this.get('id') + '_createlink_title').value = title;
8092 Dom.get(this.get('id') + '_createlink_target').checked = ((target) ? true : false);
8095 Event.onAvailable(this.get('id') + '_createlink_url', function() {
8096 var id = this.get('id');
8097 window.setTimeout(function() {
8099 YAHOO.util.Dom.get(id + '_createlink_url').focus();
8103 if (this._isLocalFile(url)) {
8104 //Local File throw Warning
8105 Dom.addClass(this.get('id') + '_createlink_url', 'warning');
8106 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8108 Dom.removeClass(this.get('id') + '_createlink_url', 'warning');
8109 this.get('panel').setFooter(' ');
8111 Event.on(this.get('id') + '_createlink_url', 'blur', function() {
8112 var url = Dom.get(this.get('id') + '_createlink_url');
8113 if (this._isLocalFile(url.value)) {
8114 //Local File throw Warning
8115 Dom.addClass(url, 'warning');
8116 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8118 Dom.removeClass(url, 'warning');
8119 this.get('panel').setFooter(' ');
8124 this.openWindow(win);
8130 * @method _handleCreateLinkWindowClose
8131 * @description Handles the closing of the Link Properties Window.
8133 _handleCreateLinkWindowClose: function() {
8135 var url = Dom.get(this.get('id') + '_createlink_url'),
8136 target = Dom.get(this.get('id') + '_createlink_target'),
8137 title = Dom.get(this.get('id') + '_createlink_title'),
8138 el = arguments[0].win.el,
8141 if (url && url.value) {
8142 var urlValue = url.value;
8143 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8144 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8145 //Found an @ sign, prefix with mailto:
8146 urlValue = 'mailto:' + urlValue;
8148 // :// not found adding
8149 if (urlValue.substring(0, 1) != '#') {
8150 urlValue = 'http:/'+'/' + urlValue;
8155 el.setAttribute('href', urlValue);
8156 if (target.checked) {
8157 el.setAttribute('target', target.value);
8159 el.setAttribute('target', '');
8161 el.setAttribute('title', ((title.value) ? title.value : ''));
8164 var _span = this._getDoc().createElement('span');
8165 _span.innerHTML = el.innerHTML;
8166 Dom.addClass(_span, 'yui-non');
8167 el.parentNode.replaceChild(_span, el);
8169 Dom.removeClass(url, 'warning');
8170 Dom.get(this.get('id') + '_createlink_url').value = '';
8171 Dom.get(this.get('id') + '_createlink_title').value = '';
8172 Dom.get(this.get('id') + '_createlink_target').checked = false;
8174 this.currentElement = [];
8179 * @method _renderInsertImageWindow
8180 * @description Pre renders the InsertImage window so we get faster window opening.
8182 _renderInsertImageWindow: function() {
8183 var el = this.currentElement[0];
8184 var str = '<label for="' + this.get('id') + '_insertimage_url"><strong>' + this.STR_IMAGE_URL + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_url" value="" size="40"></label>';
8185 var body = document.createElement('div');
8186 body.innerHTML = str;
8188 var tbarCont = document.createElement('div');
8189 tbarCont.id = this.get('id') + '_img_toolbar';
8190 body.appendChild(tbarCont);
8192 var str2 = '<label for="' + this.get('id') + '_insertimage_title"><strong>' + this.STR_IMAGE_TITLE + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_title" value="" size="40"></label>';
8193 str2 += '<label for="' + this.get('id') + '_insertimage_link"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_insertimage_link" id="' + this.get('id') + '_insertimage_link" value=""></label>';
8194 str2 += '<label for="' + this.get('id') + '_insertimage_target"><strong> </strong><input type="checkbox" name="' + this.get('id') + '_insertimage_target_" id="' + this.get('id') + '_insertimage_target" value="_blank" class="insertimage_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>';
8195 var div = document.createElement('div');
8196 div.innerHTML = str2;
8197 body.appendChild(div);
8200 Lang.augmentObject(o, this._defaultImageToolbarConfig); //Break the config reference
8202 var tbar = new YAHOO.widget.Toolbar(tbarCont, o);
8203 tbar.editor_el = el;
8204 this._defaultImageToolbar = tbar;
8206 var cont = tbar.get('cont');
8207 var hw = document.createElement('div');
8208 hw.className = 'yui-toolbar-group yui-toolbar-group-height-width height-width';
8209 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
8210 hw.innerHTML += '<span tabIndex="-1"><input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_width"> x <input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_height"></span>';
8211 cont.insertBefore(hw, cont.firstChild);
8213 Event.onAvailable(this.get('id') + '_insertimage_width', function() {
8214 Event.on(this.get('id') + '_insertimage_width', 'blur', function() {
8215 var value = parseInt(Dom.get(this.get('id') + '_insertimage_width').value, 10);
8217 this._defaultImageToolbar.editor_el.style.width = value + 'px';
8218 //Removed moveWindow call so the window doesn't jump
8219 //this.moveWindow();
8223 Event.onAvailable(this.get('id') + '_insertimage_height', function() {
8224 Event.on(this.get('id') + '_insertimage_height', 'blur', function() {
8225 var value = parseInt(Dom.get(this.get('id') + '_insertimage_height').value, 10);
8227 this._defaultImageToolbar.editor_el.style.height = value + 'px';
8228 //Removed moveWindow call so the window doesn't jump
8229 //this.moveWindow();
8235 tbar.on('colorPickerClicked', function(o) {
8236 var size = '1', type = 'solid', color = 'black', el = this._defaultImageToolbar.editor_el;
8238 if (el.style.borderLeftWidth) {
8239 size = parseInt(el.style.borderLeftWidth, 10);
8241 if (el.style.borderLeftStyle) {
8242 type = el.style.borderLeftStyle;
8244 if (el.style.borderLeftColor) {
8245 color = el.style.borderLeftColor;
8247 var borderString = size + 'px ' + type + ' #' + o.color;
8248 el.style.border = borderString;
8251 tbar.on('buttonClick', function(o) {
8252 var value = o.button.value,
8253 el = this._defaultImageToolbar.editor_el,
8255 if (o.button.menucmd) {
8256 value = o.button.menucmd;
8258 var size = '1', type = 'solid', color = 'black';
8260 /* All border calcs are done on the left border
8261 since our default interface only supports
8262 one border size/type and color */
8263 if (el.style.borderLeftWidth) {
8264 size = parseInt(el.style.borderLeftWidth, 10);
8266 if (el.style.borderLeftStyle) {
8267 type = el.style.borderLeftStyle;
8269 if (el.style.borderLeftColor) {
8270 color = el.style.borderLeftColor;
8274 if (this.browser.webkit && this._lastImage) {
8275 Dom.removeClass(this._lastImage, 'selected');
8276 this._lastImage = null;
8279 borderString = parseInt(o.button.value, 10) + 'px ' + type + ' ' + color;
8280 el.style.border = borderString;
8281 if (parseInt(o.button.value, 10) > 0) {
8282 tbar.enableButton('bordertype');
8283 tbar.enableButton('bordercolor');
8285 tbar.disableButton('bordertype');
8286 tbar.disableButton('bordercolor');
8290 if (this.browser.webkit && this._lastImage) {
8291 Dom.removeClass(this._lastImage, 'selected');
8292 this._lastImage = null;
8294 borderString = size + 'px ' + o.button.value + ' ' + color;
8295 el.style.border = borderString;
8299 tbar.deselectAllButtons();
8300 el.style.display = '';
8301 el.align = o.button.value;
8304 tbar.deselectAllButtons();
8305 el.style.display = '';
8309 tbar.deselectAllButtons();
8310 el.style.display = 'block';
8311 el.align = 'center';
8314 var _button = tbar.getButtonById(o.button.id);
8315 el.style.margin = _button.get('label') + 'px';
8318 tbar.selectButton(o.button.value);
8319 if (value !== 'padding') {
8326 if (this.get('localFileWarning')) {
8327 Event.on(this.get('id') + '_insertimage_link', 'blur', function() {
8328 var url = Dom.get(this.get('id') + '_insertimage_link');
8329 if (this._isLocalFile(url.value)) {
8330 //Local File throw Warning
8331 Dom.addClass(url, 'warning');
8332 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8334 Dom.removeClass(url, 'warning');
8335 this.get('panel').setFooter(' ');
8337 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8338 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8344 Event.on(this.get('id') + '_insertimage_url', 'blur', function() {
8345 var url = Dom.get(this.get('id') + '_insertimage_url'),
8346 el = this.currentElement[0];
8348 if (url.value && el) {
8349 if (url.value == el.getAttribute('src', 2)) {
8353 if (this._isLocalFile(url.value)) {
8354 //Local File throw Warning
8355 Dom.addClass(url, 'warning');
8356 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8357 } else if (this.currentElement[0]) {
8358 Dom.removeClass(url, 'warning');
8359 this.get('panel').setFooter(' ');
8361 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8362 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8365 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8366 this.currentElement[0].setAttribute('src', url.value);
8370 img.onerror = function() {
8371 url.value = self.STR_IMAGE_HERE;
8372 img.setAttribute('src', self.get('blankimage'));
8373 self.currentElement[0].setAttribute('src', self.get('blankimage'));
8374 YAHOO.util.Dom.get(self.get('id') + '_insertimage_height').value = img.height;
8375 YAHOO.util.Dom.get(self.get('id') + '_insertimage_width').value = img.width;
8377 var id = this.get('id');
8378 window.setTimeout(function() {
8379 YAHOO.util.Dom.get(id + '_insertimage_height').value = img.height;
8380 YAHOO.util.Dom.get(id + '_insertimage_width').value = img.width;
8381 if (self.currentElement && self.currentElement[0]) {
8382 if (!self.currentElement[0]._height) {
8383 self.currentElement[0]._height = img.height;
8385 if (!self.currentElement[0]._width) {
8386 self.currentElement[0]._width = img.width;
8389 //Removed moveWindow call so the window doesn't jump
8390 //self.moveWindow();
8391 }, 800); //Bumped the timeout up to account for larger images..
8393 if (url.value != this.STR_IMAGE_HERE) {
8394 img.src = url.value;
8402 this._windows.insertimage = {};
8403 this._windows.insertimage.body = body;
8404 //body.style.display = 'none';
8405 this.get('panel').editor_form.appendChild(body);
8406 this.fireEvent('windowInsertImageRender', { type: 'windowInsertImageRender', panel: this.get('panel'), body: body, toolbar: tbar });
8411 * @method _handleInsertImageClick
8412 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
8414 _handleInsertImageClick: function() {
8415 if (this.get('limitCommands')) {
8416 if (!this.toolbar.getButtonByValue('insertimage')) {
8420 this.on('afterExecCommand', function() {
8421 var el = this.currentElement[0],
8435 win = new YAHOO.widget.EditorWindow('insertimage', {
8440 el = this._getSelectedElement();
8444 if (el.getAttribute('src')) {
8445 src = el.getAttribute('src', 2);
8446 if (src.indexOf(this.get('blankimage')) != -1) {
8447 src = this.STR_IMAGE_HERE;
8451 if (el.getAttribute('alt', 2)) {
8452 title = el.getAttribute('alt', 2);
8454 if (el.getAttribute('title', 2)) {
8455 title = el.getAttribute('title', 2);
8458 if (el.parentNode && this._isElement(el.parentNode, 'a')) {
8459 link = el.parentNode.getAttribute('href', 2);
8460 if (el.parentNode.getAttribute('target') !== null) {
8461 target = el.parentNode.getAttribute('target');
8464 height = parseInt(el.height, 10);
8465 width = parseInt(el.width, 10);
8466 if (el.style.height) {
8467 height = parseInt(el.style.height, 10);
8469 if (el.style.width) {
8470 width = parseInt(el.style.width, 10);
8472 if (el.style.margin) {
8473 padding = parseInt(el.style.margin, 10);
8477 el._height = height;
8482 oheight = el._height;
8486 if (this._windows.insertimage && this._windows.insertimage.body) {
8487 body = this._windows.insertimage.body;
8488 this._defaultImageToolbar.resetAllButtons();
8490 body = this._renderInsertImageWindow();
8493 tbar = this._defaultImageToolbar;
8494 tbar.editor_el = el;
8500 if (el.style.borderLeftWidth) {
8501 bsize = parseInt(el.style.borderLeftWidth, 10);
8503 if (el.style.borderLeftStyle) {
8504 btype = el.style.borderLeftStyle;
8506 var bs_button = tbar.getButtonByValue('bordersize'),
8507 bSizeStr = ((parseInt(bsize, 10) > 0) ? '' : this.STR_NONE);
8508 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">' + bSizeStr + '</span>');
8509 this._updateMenuChecked('bordersize', bsize, tbar);
8511 var bt_button = tbar.getButtonByValue('bordertype');
8512 bt_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">asdfa</span>');
8513 this._updateMenuChecked('bordertype', btype, tbar);
8514 if (parseInt(bsize, 10) > 0) {
8515 tbar.enableButton(bt_button);
8516 tbar.enableButton(bs_button);
8517 tbar.enableButton('bordercolor');
8520 if ((el.align == 'right') || (el.align == 'left')) {
8521 tbar.selectButton(el.align);
8522 } else if (el.style.display == 'block') {
8523 tbar.selectButton('block');
8525 tbar.selectButton('inline');
8527 if (parseInt(el.style.marginLeft, 10) > 0) {
8528 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft, 10));
8530 if (el.style.borderSize) {
8531 tbar.selectButton('bordersize');
8532 tbar.selectButton(parseInt(el.style.borderSize, 10));
8534 tbar.getButtonByValue('padding').set('label', ''+padding);
8538 win.setHeader(this.STR_IMAGE_PROP_TITLE);
8541 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8542 win.setFooter(this.STR_IMAGE_COPY);
8544 this.openWindow(win);
8545 Dom.get(this.get('id') + '_insertimage_url').value = src;
8546 Dom.get(this.get('id') + '_insertimage_title').value = title;
8547 Dom.get(this.get('id') + '_insertimage_link').value = link;
8548 Dom.get(this.get('id') + '_insertimage_target').checked = ((target) ? true : false);
8549 Dom.get(this.get('id') + '_insertimage_width').value = width;
8550 Dom.get(this.get('id') + '_insertimage_height').value = height;
8553 if (((height != oheight) || (width != owidth)) && (!blankimage)) {
8554 var s = document.createElement('span');
8555 s.className = 'info';
8556 s.innerHTML = this.STR_IMAGE_ORIG_SIZE + ': ('+ owidth +' x ' + oheight + ')';
8557 if (Dom.get(this.get('id') + '_insertimage_height').nextSibling) {
8558 var old = Dom.get(this.get('id') + '_insertimage_height').nextSibling;
8559 old.parentNode.removeChild(old);
8561 Dom.get(this.get('id') + '_insertimage_height').parentNode.appendChild(s);
8564 this.toolbar.selectButton('insertimage');
8565 var id = this.get('id');
8566 window.setTimeout(function() {
8568 YAHOO.util.Dom.get(id + '_insertimage_url').focus();
8570 YAHOO.util.Dom.get(id + '_insertimage_url').select();
8579 * @method _handleInsertImageWindowClose
8580 * @description Handles the closing of the Image Properties Window.
8582 _handleInsertImageWindowClose: function() {
8583 var url = Dom.get(this.get('id') + '_insertimage_url'),
8584 title = Dom.get(this.get('id') + '_insertimage_title'),
8585 link = Dom.get(this.get('id') + '_insertimage_link'),
8586 target = Dom.get(this.get('id') + '_insertimage_target'),
8587 el = arguments[0].win.el;
8589 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8590 el.setAttribute('src', url.value);
8591 el.setAttribute('title', title.value);
8592 el.setAttribute('alt', title.value);
8593 var par = el.parentNode;
8595 var urlValue = link.value;
8596 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8597 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8598 //Found an @ sign, prefix with mailto:
8599 urlValue = 'mailto:' + urlValue;
8601 // :// not found adding
8602 urlValue = 'http:/'+'/' + urlValue;
8605 if (par && this._isElement(par, 'a')) {
8606 par.setAttribute('href', urlValue);
8607 if (target.checked) {
8608 par.setAttribute('target', target.value);
8610 par.setAttribute('target', '');
8613 var _a = this._getDoc().createElement('a');
8614 _a.setAttribute('href', urlValue);
8615 if (target.checked) {
8616 _a.setAttribute('target', target.value);
8618 _a.setAttribute('target', '');
8620 el.parentNode.replaceChild(_a, el);
8624 if (par && this._isElement(par, 'a')) {
8625 par.parentNode.replaceChild(el, par);
8629 //No url/src given, remove the node from the document
8630 el.parentNode.removeChild(el);
8632 Dom.get(this.get('id') + '_insertimage_url').value = '';
8633 Dom.get(this.get('id') + '_insertimage_title').value = '';
8634 Dom.get(this.get('id') + '_insertimage_link').value = '';
8635 Dom.get(this.get('id') + '_insertimage_target').checked = false;
8636 Dom.get(this.get('id') + '_insertimage_width').value = 0;
8637 Dom.get(this.get('id') + '_insertimage_height').value = 0;
8638 this._defaultImageToolbar.resetAllButtons();
8639 this.currentElement = [];
8643 * @property EDITOR_PANEL_ID
8644 * @description HTML id to give the properties window in the DOM.
8647 EDITOR_PANEL_ID: '-panel',
8650 * @method _renderPanel
8651 * @description Renders the panel used for Editor Windows to the document so we can start using it..
8652 * @return {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>}
8654 _renderPanel: function() {
8655 var panelEl = document.createElement('div');
8656 Dom.addClass(panelEl, 'yui-editor-panel');
8657 panelEl.id = this.get('id') + this.EDITOR_PANEL_ID;
8658 panelEl.style.position = 'absolute';
8659 panelEl.style.top = '-9999px';
8660 panelEl.style.left = '-9999px';
8661 document.body.appendChild(panelEl);
8662 this.get('element_cont').insertBefore(panelEl, this.get('element_cont').get('firstChild'));
8666 var panel = new YAHOO.widget.Overlay(this.get('id') + this.EDITOR_PANEL_ID, {
8674 this.set('panel', panel);
8676 panel.setBody('---');
8677 panel.setHeader(' ');
8678 panel.setFooter(' ');
8681 var body = document.createElement('div');
8682 body.className = this.CLASS_PREFIX + '-body-cont';
8683 for (var b in this.browser) {
8684 if (this.browser[b]) {
8685 Dom.addClass(body, b);
8689 Dom.addClass(body, ((YAHOO.widget.Button && (this._defaultToolbar.buttonType == 'advanced')) ? 'good-button' : 'no-button'));
8691 var _note = document.createElement('h3');
8692 _note.className = 'yui-editor-skipheader';
8693 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
8694 body.appendChild(_note);
8695 var form = document.createElement('fieldset');
8696 panel.editor_form = form;
8698 body.appendChild(form);
8699 var _close = document.createElement('span');
8700 _close.innerHTML = 'X';
8701 _close.title = this.STR_CLOSE_WINDOW;
8702 _close.className = 'close';
8704 Event.on(_close, 'click', this.closeWindow, this, true);
8706 var _knob = document.createElement('span');
8707 _knob.innerHTML = '^';
8708 _knob.className = 'knob';
8709 panel.editor_knob = _knob;
8711 var _header = document.createElement('h3');
8712 panel.editor_header = _header;
8713 _header.innerHTML = '<span></span>';
8715 panel.setHeader(' '); //Clear the current header
8716 panel.appendToHeader(_header);
8717 _header.appendChild(_close);
8718 _header.appendChild(_knob);
8719 panel.setBody(' '); //Clear the current body
8720 panel.setFooter(' '); //Clear the current footer
8721 panel.appendToBody(body); //Append the new DOM node to it
8723 Event.on(panel.element, 'click', function(ev) {
8724 Event.stopPropagation(ev);
8727 var fireShowEvent = function() {
8729 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
8730 this._handleWindowInputs(false);
8732 panel.showEvent.subscribe(fireShowEvent, this, true);
8733 panel.hideEvent.subscribe(function() {
8734 this._handleWindowInputs(true);
8736 panel.renderEvent.subscribe(function() {
8737 this._renderInsertImageWindow();
8738 this._renderCreateLinkWindow();
8739 this.fireEvent('windowRender', { type: 'windowRender', panel: panel });
8740 this._handleWindowInputs(true);
8743 if (this.DOMReady) {
8744 this.get('panel').render();
8746 Event.onDOMReady(function() {
8747 this.get('panel').render();
8750 return this.get('panel');
8753 * @method _handleWindowInputs
8754 * @param {Boolean} disable The state to set all inputs in all Editor windows to. Defaults to: false.
8755 * @description Disables/Enables all fields inside Editor windows. Used in show/hide events to keep window fields from submitting when the parent form is submitted.
8757 _handleWindowInputs: function(disable) {
8758 if (!Lang.isBoolean(disable)) {
8761 var inputs = this.get('panel').element.getElementsByTagName('input');
8762 for (var i = 0; i < inputs.length; i++) {
8764 inputs[i].disabled = disable;
8769 * @method openWindow
8770 * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance
8771 * @description Opens a new "window/panel"
8773 openWindow: function(win) {
8776 window.setTimeout(function() {
8777 self.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
8779 Event.on(document, 'keydown', this._closeWindow, this, true);
8781 if (this.currentWindow) {
8785 var xy = Dom.getXY(this.currentElement[0]),
8786 elXY = Dom.getXY(this.get('iframe').get('element')),
8787 panel = this.get('panel'),
8788 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8789 wWidth = (parseInt(win.attrs.width, 10) / 2),
8793 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
8795 var form = panel.editor_form;
8797 var wins = this._windows;
8798 for (var b in wins) {
8799 if (Lang.hasOwnProperty(wins, b)) {
8800 if (wins[b] && wins[b].body) {
8801 if (b == win.name) {
8802 Dom.setStyle(wins[b].body, 'display', 'block');
8804 Dom.setStyle(wins[b].body, 'display', 'none');
8810 if (this._windows[win.name].body) {
8811 Dom.setStyle(this._windows[win.name].body, 'display', 'block');
8812 form.appendChild(this._windows[win.name].body);
8814 if (Lang.isObject(win.body)) { //Assume it's a reference
8815 form.appendChild(win.body);
8816 } else { //Assume it's a string
8817 var _tmp = document.createElement('div');
8818 _tmp.innerHTML = win.body;
8819 form.appendChild(_tmp);
8822 panel.editor_header.firstChild.innerHTML = win.header;
8823 if (win.footer !== null) {
8824 panel.setFooter(win.footer);
8826 panel.cfg.setProperty('width', win.attrs.width);
8828 this.currentWindow = win;
8829 this.moveWindow(true);
8831 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
8834 * @method moveWindow
8835 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
8836 * @description Realign the window with the currentElement and reposition the knob above the panel.
8838 moveWindow: function(force) {
8839 if (!this.currentWindow) {
8842 var win = this.currentWindow,
8843 xy = Dom.getXY(this.currentElement[0]),
8844 elXY = Dom.getXY(this.get('iframe').get('element')),
8845 panel = this.get('panel'),
8846 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8847 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
8848 wWidth = (parseInt(win.attrs.width, 10) / 2),
8850 orgXY = panel.cfg.getProperty('xy') || [0,0],
8851 _knob = panel.editor_knob,
8857 newXY[0] = ((newXY[0] - wWidth) + 20);
8858 //Account for the Scroll bars in a scrolled editor window.
8859 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
8860 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
8862 if (this._isElement(this.currentElement[0], 'img')) {
8863 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
8864 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
8865 newXY[1] = (newXY[1] + 75); //Placeholder sizea
8867 var w = parseInt(this.currentElement[0].width, 10);
8868 var h = parseInt(this.currentElement[0].height, 10);
8869 newXY[0] = (newXY[0] + (w / 2));
8870 newXY[1] = (newXY[1] + h);
8872 newXY[1] = newXY[1] + 15;
8874 var fs = Dom.getStyle(this.currentElement[0], 'fontSize');
8875 if (fs && fs.indexOf && fs.indexOf('px') != -1) {
8876 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize'), 10) + 5;
8878 newXY[1] = newXY[1] + 20;
8881 if (newXY[0] < elXY[0]) {
8882 newXY[0] = elXY[0] + 5;
8886 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10))) {
8887 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10)) - (wWidth * 2) - 5);
8892 xDiff = (newXY[0] - orgXY[0]);
8893 yDiff = (newXY[1] - orgXY[1]);
8897 var iTop = elXY[1] + parseInt(this.get('height'), 10);
8898 var iLeft = elXY[0] + parseInt(this.get('width'), 10);
8899 if (newXY[1] > iTop) {
8902 if (newXY[0] > iLeft) {
8903 newXY[0] = (iLeft / 2);
8906 //Convert negative numbers to positive so we can get the difference in distance
8907 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
8908 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
8910 if (((xDiff > 10) || (yDiff > 10)) || force) { //Only move the window if it's supposed to move more than 10px or force was passed (new window)
8914 if (this.currentElement[0].width) {
8915 elW = (parseInt(this.currentElement[0].width, 10) / 2);
8918 var leftOffset = xy[0] + elXY[0] + elW;
8919 _knobLeft = leftOffset - newXY[0];
8920 //Check to see if the knob will go off either side & reposition it
8921 if (_knobLeft > (parseInt(win.attrs.width, 10) - 1)) {
8922 _knobLeft = ((parseInt(win.attrs.width, 10) - 30) - 1);
8923 } else if (_knobLeft < 40) {
8926 if (isNaN(_knobLeft)) {
8931 _knob.style.left = _knobLeft + 'px';
8933 //Removed Animation from a forced move..
8934 panel.cfg.setProperty('xy', newXY);
8936 if (this.get('animate')) {
8937 anim = new YAHOO.util.Anim(panel.element, {}, 0.5, YAHOO.util.Easing.easeOut);
8946 anim.onComplete.subscribe(function() {
8947 panel.cfg.setProperty('xy', newXY);
8949 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
8950 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, 0.5, YAHOO.util.Easing.easeOut);
8952 var _knobAnim = new YAHOO.util.Anim(_knob, {
8956 }, 0.6, YAHOO.util.Easing.easeOut);
8958 iframeAnim.animate();
8959 _knobAnim.animate();
8961 _knob.style.left = _knobLeft + 'px';
8962 panel.cfg.setProperty('xy', newXY);
8969 * @method _closeWindow
8970 * @description Close the currently open EditorWindow with the Escape key.
8971 * @param {Event} ev The keypress Event that we are trapping
8973 _closeWindow: function(ev) {
8974 //if ((ev.charCode == 87) && ev.shiftKey && ev.ctrlKey) {
8975 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
8976 if (this.currentWindow) {
8982 * @method closeWindow
8983 * @description Close the currently open EditorWindow.
8985 closeWindow: function(keepOpen) {
8986 this.fireEvent('window' + this.currentWindow.name + 'Close', { type: 'window' + this.currentWindow.name + 'Close', win: this.currentWindow, el: this.currentElement[0] });
8987 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
8988 this.currentWindow = null;
8989 this.get('panel').hide();
8990 this.get('panel').cfg.setProperty('xy', [-900,-900]);
8991 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
8992 this.unsubscribeAll('afterExecCommand');
8993 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
8994 this.toolbar.resetAllButtons();
8996 Event.removeListener(document, 'keydown', this._closeWindow);
8999 /* {{{ Command Overrides - These commands are only over written when we are using the advanced version */
9003 * @description Pulls an item from the Undo stack and updates the Editor
9004 * @param value Value passed from the execCommand method
9006 cmd_undo: function(value) {
9007 if (this._hasUndoLevel()) {
9008 var c_html = this.getEditorHTML(), html;
9009 if (!this._undoLevel) {
9010 this._undoLevel = this._undoCache.length;
9012 this._undoLevel = (this._undoLevel - 1);
9013 if (this._undoCache[this._undoLevel]) {
9014 html = this._getUndo(this._undoLevel);
9015 if (html != c_html) {
9016 this.setEditorHTML(html);
9018 this._undoLevel = (this._undoLevel - 1);
9019 html = this._getUndo(this._undoLevel);
9020 if (html != c_html) {
9021 this.setEditorHTML(html);
9025 this._undoLevel = 0;
9026 this.toolbar.disableButton('undo');
9034 * @description Pulls an item from the Undo stack and updates the Editor
9035 * @param value Value passed from the execCommand method
9037 cmd_redo: function(value) {
9038 this._undoLevel = this._undoLevel + 1;
9039 if (this._undoLevel >= this._undoCache.length) {
9040 this._undoLevel = this._undoCache.length;
9042 if (this._undoCache[this._undoLevel]) {
9043 var html = this._getUndo(this._undoLevel);
9044 this.setEditorHTML(html);
9046 this.toolbar.disableButton('redo');
9052 * @method cmd_heading
9053 * @param value Value passed from the execCommand method
9054 * @description This is an execCommand override method. It is called from execCommand when the execCommand('heading') is used.
9056 cmd_heading: function(value) {
9060 _sel = this._getSelection(),
9061 _selEl = this._getSelectedElement();
9067 if (this.browser.ie) {
9068 action = 'formatblock';
9070 if (value == this.STR_NONE) {
9071 if ((_sel && _sel.tagName && (_sel.tagName.toLowerCase().substring(0,1) == 'h')) || (_sel && _sel.parentNode && _sel.parentNode.tagName && (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h'))) {
9072 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
9073 _sel = _sel.parentNode;
9075 if (this._isElement(_sel, 'html')) {
9078 el = this._swapEl(_selEl, 'span', function(el) {
9079 el.className = 'yui-non';
9081 this._selectNode(el);
9082 this.currentElement[0] = el;
9086 if (this._isElement(_selEl, 'h1') || this._isElement(_selEl, 'h2') || this._isElement(_selEl, 'h3') || this._isElement(_selEl, 'h4') || this._isElement(_selEl, 'h5') || this._isElement(_selEl, 'h6')) {
9087 el = this._swapEl(_selEl, value);
9088 this._selectNode(el);
9089 this.currentElement[0] = el;
9091 this._createCurrentElement(value);
9092 this._selectNode(this.currentElement[0]);
9096 return [exec, action];
9099 * @method cmd_hiddenelements
9100 * @param value Value passed from the execCommand method
9101 * @description This is an execCommand override method. It is called from execCommand when the execCommand('hiddenelements') is used.
9103 cmd_hiddenelements: function(value) {
9104 if (this._showingHiddenElements) {
9105 //Don't auto highlight the hidden button
9106 this._lastButton = null;
9107 this._showingHiddenElements = false;
9108 this.toolbar.deselectButton('hiddenelements');
9109 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN);
9111 this._showingHiddenElements = true;
9112 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN);
9113 this.toolbar.selectButton('hiddenelements');
9118 * @method cmd_removeformat
9119 * @param value Value passed from the execCommand method
9120 * @description This is an execCommand override method. It is called from execCommand when the execCommand('removeformat') is used.
9122 cmd_removeformat: function(value) {
9125 * @knownissue Remove Format issue
9126 * @browser Safari 2.x
9127 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
9128 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
9129 * So here we are making the best possible guess and acting on it.
9131 if (this.browser.webkit && !this._getDoc().queryCommandEnabled('removeformat')) {
9132 var _txt = this._getSelection()+'';
9133 this._createCurrentElement('span');
9134 this.currentElement[0].className = 'yui-non';
9135 this.currentElement[0].innerHTML = _txt;
9136 for (var i = 1; i < this.currentElement.length; i++) {
9137 this.currentElement[i].parentNode.removeChild(this.currentElement[i]);
9145 * @method cmd_script
9146 * @param action action passed from the execCommand method
9147 * @param value Value passed from the execCommand method
9148 * @description This is a combined execCommand override method. It is called from the cmd_superscript and cmd_subscript methods.
9150 cmd_script: function(action, value) {
9151 var exec = true, tag = action.toLowerCase().substring(0, 3),
9152 _span = null, _selEl = this._getSelectedElement();
9154 if (this.browser.webkit) {
9155 if (this._isElement(_selEl, tag)) {
9156 _span = this._swapEl(this.currentElement[0], 'span', function(el) {
9157 el.className = 'yui-non';
9159 this._selectNode(_span);
9161 this._createCurrentElement(tag);
9162 var _sub = this._swapEl(this.currentElement[0], tag);
9163 this._selectNode(_sub);
9164 this.currentElement[0] = _sub;
9171 * @method cmd_superscript
9172 * @param value Value passed from the execCommand method
9173 * @description This is an execCommand override method. It is called from execCommand when the execCommand('superscript') is used.
9175 cmd_superscript: function(value) {
9176 return [this.cmd_script('superscript', value)];
9179 * @method cmd_subscript
9180 * @param value Value passed from the execCommand method
9181 * @description This is an execCommand override method. It is called from execCommand when the execCommand('subscript') is used.
9183 cmd_subscript: function(value) {
9184 return [this.cmd_script('subscript', value)];
9187 * @method cmd_indent
9188 * @param value Value passed from the execCommand method
9189 * @description This is an execCommand override method. It is called from execCommand when the execCommand('indent') is used.
9191 cmd_indent: function(value) {
9192 var exec = true, selEl = this._getSelectedElement(), _bq = null;
9194 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9195 //if (this.browser.webkit || this.browser.ie) {
9196 if (this.browser.ie) {
9197 if (this._isElement(selEl, 'blockquote')) {
9198 _bq = this._getDoc().createElement('blockquote');
9199 _bq.innerHTML = selEl.innerHTML;
9200 selEl.innerHTML = '';
9201 selEl.appendChild(_bq);
9202 this._selectNode(_bq);
9204 _bq = this._getDoc().createElement('blockquote');
9205 var html = this._getRange().htmlText;
9206 _bq.innerHTML = html;
9207 this._createCurrentElement('blockquote');
9209 for (var i = 0; i < this.currentElement.length; i++) {
9210 _bq = this._getDoc().createElement('blockquote');
9211 _bq.innerHTML = this.currentElement[i].innerHTML;
9212 this.currentElement[i].parentNode.replaceChild(_bq, this.currentElement[i]);
9213 this.currentElement[i] = _bq;
9216 this.currentElement[0].parentNode.replaceChild(_bq, this.currentElement[0]);
9217 this.currentElement[0] = _bq;
9218 this._selectNode(this.currentElement[0]);
9222 value = 'blockquote';
9224 return [exec, 'formatblock', value];
9227 * @method cmd_outdent
9228 * @param value Value passed from the execCommand method
9229 * @description This is an execCommand override method. It is called from execCommand when the execCommand('outdent') is used.
9231 cmd_outdent: function(value) {
9232 var exec = true, selEl = this._getSelectedElement(), _bq = null, _span = null;
9233 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9234 if (this.browser.webkit || this.browser.ie) {
9235 //if (this.browser.ie) {
9236 selEl = this._getSelectedElement();
9237 if (this._isElement(selEl, 'blockquote')) {
9238 var par = selEl.parentNode;
9239 if (this._isElement(selEl.parentNode, 'blockquote')) {
9240 par.innerHTML = selEl.innerHTML;
9241 this._selectNode(par);
9243 _span = this._getDoc().createElement('span');
9244 _span.innerHTML = selEl.innerHTML;
9245 YAHOO.util.Dom.addClass(_span, 'yui-non');
9246 par.replaceChild(_span, selEl);
9247 this._selectNode(_span);
9255 return [exec, 'outdent', value];
9258 * @method cmd_justify
9259 * @param dir The direction to justify
9260 * @description This is a factory method for the justify family of commands.
9262 cmd_justify: function(dir) {
9263 if (this.browser.ie) {
9264 if (this._hasSelection()) {
9265 this._createCurrentElement('span');
9266 this._swapEl(this.currentElement[0], 'div', function(el) {
9267 el.style.textAlign = dir;
9273 return [true, 'justify' + dir, ''];
9276 * @method cmd_justifycenter
9277 * @param value Value passed from the execCommand method
9278 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifycenter') is used.
9280 cmd_justifycenter: function() {
9281 return [this.cmd_justify('center')];
9284 * @method cmd_justifyleft
9285 * @param value Value passed from the execCommand method
9286 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyleft') is used.
9288 cmd_justifyleft: function() {
9289 return [this.cmd_justify('left')];
9292 * @method cmd_justifyright
9293 * @param value Value passed from the execCommand method
9294 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyright') is used.
9296 cmd_justifyright: function() {
9297 return [this.cmd_justify('right')];
9302 * @description Returns a string representing the editor.
9305 toString: function() {
9307 if (this.get && this.get('element_cont')) {
9308 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
9314 * @event beforeOpenWindow
9315 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9316 * @param {Overlay} panel The Overlay object that is used to create the window.
9317 * @description Event fires before an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9318 * @type YAHOO.util.CustomEvent
9321 * @event afterOpenWindow
9322 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9323 * @param {Overlay} panel The Overlay object that is used to create the window.
9324 * @description Event fires after an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9325 * @type YAHOO.util.CustomEvent
9328 * @event closeWindow
9329 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9330 * @description Event fires after an Editor Window is closed. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9331 * @type YAHOO.util.CustomEvent
9334 * @event windowCMDOpen
9335 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9336 * @param {Overlay} panel The Overlay object that is used to create the window.
9337 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is opened.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkOpen event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9338 * @type YAHOO.util.CustomEvent
9341 * @event windowCMDClose
9342 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9343 * @param {Overlay} panel The Overlay object that is used to create the window.
9344 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is closed.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkClose event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9345 * @type YAHOO.util.CustomEvent
9348 * @event windowRender
9349 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9350 * @param {Overlay} panel The Overlay object that is used to create the window.
9351 * @description Event fired when the initial Overlay is rendered. Can be used to manipulate the content of the panel.
9352 * @type YAHOO.util.CustomEvent
9355 * @event windowInsertImageRender
9356 * @param {Overlay} panel The Overlay object that is used to create the window.
9357 * @param {HTMLElement} body The HTML element used as the body of the window..
9358 * @param {Toolbar} toolbar A reference to the toolbar object used inside this window.
9359 * @description Event fired when the pre render of the Insert Image window has finished.
9360 * @type YAHOO.util.CustomEvent
9363 * @event windowCreateLinkRender
9364 * @param {Overlay} panel The Overlay object that is used to create the window.
9365 * @param {HTMLElement} body The HTML element used as the body of the window..
9366 * @description Event fired when the pre render of the Create Link window has finished.
9367 * @type YAHOO.util.CustomEvent
9373 * @description Class to hold Window information between uses. We use the same panel to show the windows, so using this will allow you to configure a window before it is shown.
9374 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
9375 * @class EditorWindow
9376 * @param {String} name The name of the window.
9377 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
9379 YAHOO.widget.EditorWindow = function(name, attrs) {
9383 * @description A unique name for the window
9385 this.name = name.replace(' ', '_');
9389 * @description The window attributes
9394 YAHOO.widget.EditorWindow.prototype = {
9398 * @description Holder for the header of the window, used in Editor.openWindow
9404 * @description Holder for the body of the window, used in Editor.openWindow
9410 * @description Holder for the footer of the window, used in Editor.openWindow
9415 * @description Sets the header for the window.
9416 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
9418 setHeader: function(str) {
9423 * @description Sets the body for the window.
9424 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
9426 setBody: function(str) {
9431 * @description Sets the footer for the window.
9432 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
9434 setFooter: function(str) {
9439 * @description Returns a string representing the EditorWindow.
9442 toString: function() {
9443 return 'Editor Window (' + this.name + ')';
9447 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.8.0r4", build: "2449"});