2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
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;
131 for (var i = 0; i < opts.length; i++) {
132 if (opts[i].value == value) {
133 opts.selectedIndex = i;
141 * @description The ToolbarButton class's initialization method
143 init: function(p_oElement, p_oAttributes) {
144 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
146 this.on('mouseover', this._handleMouseOver, this, true);
147 this.on('mouseout', this._handleMouseOut, this, true);
148 this.on('click', function(ev) {
154 * @method initAttributes
155 * @description Initializes all of the configuration attributes used to create
157 * @param {Object} attr Object literal specifying a set of
158 * configuration attributes used to create the toolbar.
160 initAttributes: function(attr) {
161 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
164 * @description The value of the button
167 this.setAttributeConfig('value', {
172 * @description The menu attribute, see YAHOO.widget.Button
175 this.setAttributeConfig('menu', {
176 value: attr.menu || false
180 * @description The type of button to create: push, menu, color, select, spin
183 this.setAttributeConfig('type', {
186 method: function(type) {
189 this._button = this.get('element').getElementsByTagName('a')[0];
194 el = document.createElement('select');
195 el.id = this.get('id');
196 var menu = this.get('menu');
197 for (var i = 0; i < menu.length; i++) {
198 opt = document.createElement('option');
199 opt.innerHTML = menu[i].text;
200 opt.value = menu[i].value;
201 if (menu[i].checked) {
206 this._button.parentNode.replaceChild(el, this._button);
207 Event.on(el, 'change', this._handleSelect, this, true);
215 * @attribute disabled
216 * @description Set the button into a disabled state
219 this.setAttributeConfig('disabled', {
220 value: attr.disabled || false,
221 method: function(disabled) {
223 this.addClass('yui-button-disabled');
224 this.addClass('yui-' + this.get('type') + '-button-disabled');
226 this.removeClass('yui-button-disabled');
227 this.removeClass('yui-' + this.get('type') + '-button-disabled');
229 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) {
230 this._button.disabled = disabled;
237 * @description The text label for the button
240 this.setAttributeConfig('label', {
242 method: function(label) {
244 this._button = this.get('element').getElementsByTagName('a')[0];
246 if (this.get('type') == 'push') {
247 this._button.innerHTML = label;
254 * @description The title of the button
257 this.setAttributeConfig('title', {
263 * @description The container that the button is rendered to, handled by Toolbar
266 this.setAttributeConfig('container', {
269 method: function(cont) {
277 * @method _handleSelect
278 * @description The event fired when a change event gets fired on a select element
279 * @param {Event} ev The change event.
281 _handleSelect: function(ev) {
282 var tar = Event.getTarget(ev);
283 var value = tar.options[tar.selectedIndex].value;
284 this.fireEvent('change', {type: 'change', value: value });
288 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
290 getMenu: function() {
291 return this.get('menu');
295 * @description Destroy the button
297 destroy: function() {
298 Event.purgeElement(this.get('element'), true);
299 this.get('element').parentNode.removeChild(this.get('element'));
300 //Brutal Object Destroy
301 for (var i in this) {
302 if (Lang.hasOwnProperty(this, i)) {
309 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
311 fireEvent: function(p_sType, p_aArgs) {
312 // Disabled buttons should not respond to DOM events
313 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
314 Event.stopEvent(p_aArgs);
318 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
322 * @description Returns a string representing the toolbar.
325 toString: function() {
326 return 'ToolbarButton (' + this.get('id') + ')';
333 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
334 * @namespace YAHOO.widget
335 * @requires yahoo, dom, element, event, toolbarbutton
336 * @optional container_core, dragdrop
339 var Dom = YAHOO.util.Dom,
340 Event = YAHOO.util.Event,
343 var getButton = function(id) {
345 if (Lang.isString(id)) {
346 button = this.getButtonById(id);
348 if (Lang.isNumber(id)) {
349 button = this.getButtonByIndex(id);
351 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
352 button = this.getButtonByValue(id);
354 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
361 * Provides a rich toolbar widget based on the button and menu widgets
364 * @extends YAHOO.util.Element
365 * @param {String/HTMLElement} el The element to turn into a toolbar.
366 * @param {Object} attrs Object liternal containing configuration parameters.
368 YAHOO.widget.Toolbar = function(el, attrs) {
370 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
373 var local_attrs = {};
375 Lang.augmentObject(local_attrs, attrs); //Break the config reference
381 attributes: local_attrs
385 if (Lang.isString(el) && Dom.get(el)) {
386 oConfig.element = Dom.get(el);
387 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
388 oConfig.element = Dom.get(el);
392 if (!oConfig.element) {
393 oConfig.element = document.createElement('DIV');
394 oConfig.element.id = Dom.generateId();
396 if (local_attrs.container && Dom.get(local_attrs.container)) {
397 Dom.get(local_attrs.container).appendChild(oConfig.element);
402 if (!oConfig.element.id) {
403 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
406 var fs = document.createElement('fieldset');
407 var lg = document.createElement('legend');
408 lg.innerHTML = 'Toolbar';
411 var cont = document.createElement('DIV');
412 oConfig.attributes.cont = cont;
413 Dom.addClass(cont, 'yui-toolbar-subcont');
414 fs.appendChild(cont);
415 oConfig.element.appendChild(fs);
417 oConfig.element.tabIndex = -1;
420 oConfig.attributes.element = oConfig.element;
421 oConfig.attributes.id = oConfig.element.id;
423 this._configuredButtons = [];
425 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
429 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
432 * @property _configuredButtons
435 _configuredButtons: null,
437 * @method _addMenuClasses
439 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
440 * @param {String} ev The event that fired.
441 * @param {Array} na Array of event information.
442 * @param {Object} o Button config object.
444 _addMenuClasses: function(ev, na, o) {
445 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
446 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
447 Dom.addClass(this.element, 'yui-toolbar-select-menu');
449 var items = this.getItems();
450 for (var i = 0; i < items.length; i++) {
451 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()));
452 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
456 * @property buttonType
457 * @description The default button to use
460 buttonType: YAHOO.widget.ToolbarButton,
463 * @description The DragDrop instance associated with the Toolbar
468 * @property _colorData
469 * @description Object reference containing colors hex and text values.
474 '#111111': 'Obsidian',
475 '#2D2D2D': 'Dark Gray',
479 '#8B8B8B': 'Concrete',
481 '#B9B9B9': 'Titanium',
483 '#D0D0D0': 'Light Gray',
486 '#BFBF00': 'Pumpkin',
489 '#FFFF80': 'Pale Yellow',
491 '#525330': 'Raw Siena',
494 '#7F7F00': 'Paprika',
499 '#80FF00': 'Chartreuse',
501 '#C0FF80': 'Pale Lime',
502 '#DFFFBF': 'Light Mint',
504 '#668F5A': 'Lime Gray',
507 '#8A9B55': 'Pistachio',
508 '#B7C296': 'Light Jade',
509 '#E6EBD5': 'Breakwater',
510 '#00BF00': 'Spring Frost',
511 '#00FF80': 'Pastel Green',
512 '#40FFA0': 'Light Emerald',
513 '#80FFC0': 'Sea Foam',
514 '#BFFFDF': 'Sea Mist',
515 '#033D21': 'Dark Forrest',
517 '#7FA37C': 'Medium Green',
519 '#8DAE94': 'Yellow Gray Green',
520 '#ACC6B5': 'Aqua Lung',
521 '#DDEBE2': 'Sea Vapor',
524 '#40FFFF': 'Turquoise Blue',
525 '#80FFFF': 'Light Aqua',
526 '#BFFFFF': 'Pale Cyan',
527 '#033D3D': 'Dark Teal',
528 '#347D7E': 'Gray Turquoise',
529 '#609A9F': 'Green Blue',
530 '#007F7F': 'Seaweed',
531 '#96BDC4': 'Green Gray',
532 '#B5D1D7': 'Soapstone',
533 '#E2F1F4': 'Light Turquoise',
534 '#0060BF': 'Summer Sky',
535 '#0080FF': 'Sky Blue',
536 '#40A0FF': 'Electric Blue',
537 '#80C0FF': 'Light Azure',
538 '#BFDFFF': 'Ice Blue',
541 '#57708F': 'Dusty Blue',
542 '#00407F': 'Sea Blue',
543 '#7792AC': 'Sky Blue Gray',
544 '#A8BED1': 'Morning Sky',
546 '#0000BF': 'Deep Blue',
548 '#4040FF': 'Cerulean Blue',
549 '#8080FF': 'Evening Blue',
550 '#BFBFFF': 'Light Blue',
551 '#212143': 'Deep Indigo',
552 '#373E68': 'Sea Blue',
553 '#444F75': 'Night Blue',
554 '#00007F': 'Indigo Blue',
555 '#585E82': 'Dockside',
556 '#8687A4': 'Blue Gray',
557 '#D2D1E1': 'Light Blue Gray',
558 '#6000BF': 'Neon Violet',
559 '#8000FF': 'Blue Violet',
560 '#A040FF': 'Violet Purple',
561 '#C080FF': 'Violet Dusk',
562 '#DFBFFF': 'Pale Lavender',
563 '#302449': 'Cool Shale',
564 '#54466F': 'Dark Indigo',
565 '#655A7F': 'Dark Violet',
567 '#726284': 'Smoky Violet',
568 '#9E8FA9': 'Slate Gray',
569 '#DCD1DF': 'Violet White',
570 '#BF00BF': 'Royal Violet',
571 '#FF00FF': 'Fuchsia',
572 '#FF40FF': 'Magenta',
574 '#FFBFFF': 'Pale Magenta',
575 '#4A234A': 'Dark Purple',
576 '#794A72': 'Medium Purple',
577 '#936386': 'Cool Granite',
579 '#9D7292': 'Purple Moon',
580 '#C0A0B6': 'Pale Purple',
581 '#ECDAE5': 'Pink Cloud',
582 '#BF005F': 'Hot Pink',
583 '#FF007F': 'Deep Pink',
585 '#FF80BF': 'Electric Pink',
587 '#451528': 'Purple Red',
588 '#823857': 'Purple Dino',
589 '#A94A76': 'Purple Gray',
591 '#BC6F95': 'Antique Mauve',
592 '#D8A5BB': 'Cool Marble',
593 '#F7DDE9': 'Pink Granite',
595 '#FF0000': 'Fire Truck',
596 '#FF4040': 'Pale Red',
598 '#FFC0C0': 'Warm Pink',
602 '#800000': 'Brick Red',
604 '#D8A3A4': 'Shrimp Pink',
605 '#F8DDDD': 'Shell Pink',
606 '#BF5F00': 'Dark Orange',
608 '#FF9F40': 'Grapefruit',
609 '#FFBF80': 'Canteloupe',
611 '#482C1B': 'Dark Brick',
615 '#C49B71': 'Mustard',
616 '#E1C4A8': 'Pale Tan',
621 * @property _colorPicker
622 * @description The HTML Element containing the colorPicker
627 * @property STR_COLLAPSE
628 * @description String for Toolbar Collapse Button
631 STR_COLLAPSE: 'Collapse Toolbar',
633 * @property STR_EXPAND
634 * @description String for Toolbar Collapse Button - Expand
637 STR_EXPAND: 'Expand Toolbar',
639 * @property STR_SPIN_LABEL
640 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
643 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.',
645 * @property STR_SPIN_UP
646 * @description String for spinbutton up
649 STR_SPIN_UP: 'Click to increase the value of this input',
651 * @property STR_SPIN_DOWN
652 * @description String for spinbutton down
655 STR_SPIN_DOWN: 'Click to decrease the value of this input',
657 * @property _titlebar
658 * @description Object reference to the titlebar
664 * @description Standard browser detection
667 browser: YAHOO.env.ua,
670 * @property _buttonList
671 * @description Internal property list of current buttons in the toolbar
677 * @property _buttonGroupList
678 * @description Internal property list of current button groups in the toolbar
681 _buttonGroupList: null,
685 * @description Internal reference to the separator HTML Element for cloning
691 * @property _sepCount
692 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
698 * @property draghandle
704 * @property _toolbarConfigs
712 * @property CLASS_CONTAINER
713 * @description Default CSS class to apply to the toolbar container element
716 CLASS_CONTAINER: 'yui-toolbar-container',
719 * @property CLASS_DRAGHANDLE
720 * @description Default CSS class to apply to the toolbar's drag handle element
723 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
726 * @property CLASS_SEPARATOR
727 * @description Default CSS class to apply to all separators in the toolbar
730 CLASS_SEPARATOR: 'yui-toolbar-separator',
733 * @property CLASS_DISABLED
734 * @description Default CSS class to apply when the toolbar is disabled
737 CLASS_DISABLED: 'yui-toolbar-disabled',
740 * @property CLASS_PREFIX
741 * @description Default prefix for dynamically created class names
744 CLASS_PREFIX: 'yui-toolbar',
747 * @description The Toolbar class's initialization method
749 init: function(p_oElement, p_oAttributes) {
750 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
753 * @method initAttributes
754 * @description Initializes all of the configuration attributes used to create
756 * @param {Object} attr Object literal specifying a set of
757 * configuration attributes used to create the toolbar.
759 initAttributes: function(attr) {
760 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
761 this.addClass(this.CLASS_CONTAINER);
764 * @attribute buttonType
765 * @description The buttonType to use (advanced or basic)
768 this.setAttributeConfig('buttonType', {
769 value: attr.buttonType || 'basic',
771 validator: function(type) {
779 method: function(type) {
780 if (type == 'advanced') {
781 if (YAHOO.widget.Button) {
782 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
784 this.buttonType = YAHOO.widget.ToolbarButton;
787 this.buttonType = YAHOO.widget.ToolbarButton;
795 * @description Object specifying the buttons to include in the toolbar
799 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
800 * { type: 'separator' },
801 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
803 * { text: "Left", value: 'alignleft' },
804 * { text: "Center", value: 'aligncenter' },
805 * { text: "Right", value: 'alignright' }
813 this.setAttributeConfig('buttons', {
816 method: function(data) {
817 var i, button, buttons, len, b;
819 if (Lang.hasOwnProperty(data, i)) {
820 if (data[i].type == 'separator') {
822 } else if (data[i].group !== undefined) {
823 buttons = this.addButtonGroup(data[i]);
825 len = buttons.length;
826 for(b = 0; b < len; b++) {
828 this._configuredButtons[this._configuredButtons.length] = buttons[b].id;
834 button = this.addButton(data[i]);
836 this._configuredButtons[this._configuredButtons.length] = button.id;
845 * @attribute disabled
846 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
850 this.setAttributeConfig('disabled', {
852 method: function(disabled) {
853 if (this.get('disabled') === disabled) {
857 this.addClass(this.CLASS_DISABLED);
858 this.set('draggable', false);
859 this.disableAllButtons();
861 this.removeClass(this.CLASS_DISABLED);
862 if (this._configs.draggable._initialConfig.value) {
863 //Draggable by default, set it back
864 this.set('draggable', true);
866 this.resetAllButtons();
873 * @description The container for the toolbar.
876 this.setAttributeConfig('cont', {
883 * @attribute grouplabels
884 * @description Boolean indicating if the toolbar should show the group label's text string.
888 this.setAttributeConfig('grouplabels', {
889 value: ((attr.grouplabels === false) ? false : true),
890 method: function(grouplabels) {
892 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
894 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
899 * @attribute titlebar
900 * @description Boolean indicating if the toolbar should have a titlebar. If
901 * passed a string, it will use that as the titlebar text
903 * @type Boolean or String
905 this.setAttributeConfig('titlebar', {
907 method: function(titlebar) {
909 if (this._titlebar && this._titlebar.parentNode) {
910 this._titlebar.parentNode.removeChild(this._titlebar);
912 this._titlebar = document.createElement('DIV');
913 this._titlebar.tabIndex = '-1';
914 Event.on(this._titlebar, 'focus', function() {
917 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
918 if (Lang.isString(titlebar)) {
919 var h2 = document.createElement('h2');
921 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
922 this._titlebar.appendChild(h2);
923 Event.on(h2.firstChild, 'click', function(ev) {
926 Event.on([h2, h2.firstChild], 'focus', function() {
930 if (this.get('firstChild')) {
931 this.insertBefore(this._titlebar, this.get('firstChild'));
933 this.appendChild(this._titlebar);
935 if (this.get('collapse')) {
936 this.set('collapse', true);
938 } else if (this._titlebar) {
939 if (this._titlebar && this._titlebar.parentNode) {
940 this._titlebar.parentNode.removeChild(this._titlebar);
948 * @attribute collapse
949 * @description Boolean indicating if the the titlebar should have a collapse button.
950 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
954 this.setAttributeConfig('collapse', {
956 method: function(collapse) {
957 if (this._titlebar) {
958 var collapseEl = null;
959 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
962 //There is already a collapse button
965 collapseEl = document.createElement('SPAN');
966 collapseEl.innerHTML = 'X';
967 collapseEl.title = this.STR_COLLAPSE;
969 Dom.addClass(collapseEl, 'collapse');
970 this._titlebar.appendChild(collapseEl);
971 Event.addListener(collapseEl, 'click', function() {
972 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
973 this.collapse(false); //Expand Toolbar
975 this.collapse(); //Collapse Toolbar
979 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
981 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
982 //We are closed, reopen the titlebar..
983 this.collapse(false); //Expand Toolbar
985 collapseEl[0].parentNode.removeChild(collapseEl[0]);
993 * @attribute draggable
994 * @description Boolean indicating if the toolbar should be draggable.
999 this.setAttributeConfig('draggable', {
1000 value: (attr.draggable || false),
1001 method: function(draggable) {
1002 if (draggable && !this.get('titlebar')) {
1003 if (!this._dragHandle) {
1004 this._dragHandle = document.createElement('SPAN');
1005 this._dragHandle.innerHTML = '|';
1006 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
1007 this._dragHandle.id = this.get('id') + '_draghandle';
1008 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
1009 if (this.get('cont').hasChildNodes()) {
1010 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
1012 this.get('cont').appendChild(this._dragHandle);
1014 this.dd = new YAHOO.util.DD(this.get('id'));
1015 this.dd.setHandleElId(this._dragHandle.id);
1019 if (this._dragHandle) {
1020 this._dragHandle.parentNode.removeChild(this._dragHandle);
1021 this._dragHandle = null;
1025 if (this._titlebar) {
1027 this.dd = new YAHOO.util.DD(this.get('id'));
1028 this.dd.setHandleElId(this._titlebar);
1029 Dom.addClass(this._titlebar, 'draggable');
1031 Dom.removeClass(this._titlebar, 'draggable');
1039 validator: function(value) {
1041 if (!YAHOO.util.DD) {
1050 * @method addButtonGroup
1051 * @description Add a new button group to the toolbar. (uses addButton)
1052 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1054 addButtonGroup: function(oGroup) {
1055 if (!this.get('element')) {
1056 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1060 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1061 this.addClass(this.CLASS_PREFIX + '-grouped');
1063 var div = document.createElement('DIV');
1064 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1065 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1067 var label = document.createElement('h3');
1068 label.innerHTML = oGroup.label;
1069 div.appendChild(label);
1071 if (!this.get('grouplabels')) {
1072 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1075 this.get('cont').appendChild(div);
1077 //For accessibility, let's put all of the group buttons in an Unordered List
1078 var ul = document.createElement('ul');
1079 div.appendChild(ul);
1081 if (!this._buttonGroupList) {
1082 this._buttonGroupList = {};
1085 this._buttonGroupList[oGroup.group] = ul;
1087 //An array of the button ids added to this group
1088 //This is used for destruction later...
1089 var addedButtons = [],
1093 for (var i = 0; i < oGroup.buttons.length; i++) {
1094 var li = document.createElement('li');
1095 li.className = this.CLASS_PREFIX + '-groupitem';
1097 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1098 this.addSeparator(li);
1100 oGroup.buttons[i].container = li;
1101 button = this.addButton(oGroup.buttons[i]);
1103 addedButtons[addedButtons.length] = button.id;
1107 return addedButtons;
1110 * @method addButtonToGroup
1111 * @description Add a new button to a toolbar group. Buttons supported:
1112 * push, split, menu, select, color, spin
1113 * @param {Object} oButton Object literal reference to the Button's Config
1114 * @param {String} group The Group identifier passed into the initial config
1115 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1117 addButtonToGroup: function(oButton, group, after) {
1118 var groupCont = this._buttonGroupList[group],
1119 li = document.createElement('li');
1121 li.className = this.CLASS_PREFIX + '-groupitem';
1122 oButton.container = li;
1123 this.addButton(oButton, after);
1124 groupCont.appendChild(li);
1128 * @description Add a new button to the toolbar. Buttons supported:
1129 * push, split, menu, select, color, spin
1130 * @param {Object} oButton Object literal reference to the Button's Config
1131 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1133 addButton: function(oButton, after) {
1134 if (!this.get('element')) {
1135 this._queue[this._queue.length] = ['addButton', arguments];
1138 if (!this._buttonList) {
1139 this._buttonList = [];
1141 if (!oButton.container) {
1142 oButton.container = this.get('cont');
1145 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1146 if (Lang.isArray(oButton.menu)) {
1147 for (var i in oButton.menu) {
1148 if (Lang.hasOwnProperty(oButton.menu, i)) {
1150 fn: function(ev, x, oMenu) {
1151 if (!oButton.menucmd) {
1152 oButton.menucmd = oButton.value;
1154 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1158 oButton.menu[i].onclick = funcObject;
1163 var _oButton = {}, skip = false;
1164 for (var o in oButton) {
1165 if (Lang.hasOwnProperty(oButton, o)) {
1166 if (!this._toolbarConfigs[o]) {
1167 _oButton[o] = oButton[o];
1171 if (oButton.type == 'select') {
1172 _oButton.type = 'menu';
1174 if (oButton.type == 'spin') {
1175 _oButton.type = 'push';
1177 if (_oButton.type == 'color') {
1178 if (YAHOO.widget.Overlay) {
1179 _oButton = this._makeColorButton(_oButton);
1184 if (_oButton.menu) {
1185 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1186 oButton.menu.showEvent.subscribe(function() {
1187 this._button = _oButton;
1190 for (var m = 0; m < _oButton.menu.length; m++) {
1191 if (!_oButton.menu[m].value) {
1192 _oButton.menu[m].value = _oButton.menu[m].text;
1195 if (this.browser.webkit) {
1196 _oButton.focusmenu = false;
1203 //Add to .get('buttons') manually
1204 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1206 var tmp = new this.buttonType(_oButton);
1207 tmp.get('element').tabIndex = '-1';
1208 tmp.get('element').setAttribute('role', 'button');
1209 tmp._selected = true;
1211 if (this.get('disabled')) {
1212 //Toolbar is disabled, disable the new button too!
1213 tmp.set('disabled', true);
1216 oButton.id = tmp.get('id');
1220 var el = tmp.get('element');
1223 nextSib = after.get('element').nextSibling;
1224 } else if (after.nextSibling) {
1225 nextSib = after.nextSibling;
1228 nextSib.parentNode.insertBefore(el, nextSib);
1231 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1233 var icon = document.createElement('span');
1234 icon.className = this.CLASS_PREFIX + '-icon';
1235 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1236 if (tmp._button.tagName.toLowerCase() == 'button') {
1237 tmp.get('element').setAttribute('unselectable', 'on');
1238 //Replace the Button HTML Element with an a href if it exists
1239 var a = document.createElement('a');
1240 a.innerHTML = tmp._button.innerHTML;
1243 Event.on(a, 'click', function(ev) {
1244 Event.stopEvent(ev);
1246 tmp._button.parentNode.replaceChild(a, tmp._button);
1250 if (oButton.type == 'select') {
1251 if (tmp._button.tagName.toLowerCase() == 'select') {
1252 icon.parentNode.removeChild(icon);
1253 var iel = tmp._button,
1254 parEl = tmp.get('element');
1255 parEl.parentNode.replaceChild(iel, parEl);
1256 //The 'element' value is currently the orphaned element
1257 //In order for "destroy" to execute we need to get('element') to reference the correct node.
1258 //I'm not sure if there is a direct approach to setting this value.
1259 tmp._configs.element.value = iel;
1261 //Don't put a class on it if it's a real select element
1262 tmp.addClass(this.CLASS_PREFIX + '-select');
1265 if (oButton.type == 'spin') {
1266 if (!Lang.isArray(oButton.range)) {
1267 oButton.range = [ 10, 100 ];
1269 this._makeSpinButton(tmp, oButton);
1271 tmp.get('element').setAttribute('title', tmp.get('label'));
1272 if (oButton.type != 'spin') {
1273 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1274 var showPicker = function(ev) {
1276 if (ev.keyCode && (ev.keyCode == 9)) {
1280 if (this._colorPicker) {
1281 this._colorPicker._button = oButton.value;
1283 var menuEL = tmp.getMenu().element;
1284 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1285 tmp.getMenu().show();
1287 tmp.getMenu().hide();
1290 YAHOO.util.Event.stopEvent(ev);
1292 tmp.on('mousedown', showPicker, oButton, this);
1293 tmp.on('keydown', showPicker, oButton, this);
1295 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1296 tmp.on('keypress', this._buttonClick, oButton, this);
1297 tmp.on('mousedown', function(ev) {
1298 YAHOO.util.Event.stopEvent(ev);
1299 this._buttonClick(ev, oButton);
1301 tmp.on('click', function(ev) {
1302 YAHOO.util.Event.stopEvent(ev);
1305 //Stop the mousedown event so we can trap the selection in the editor!
1306 tmp.on('mousedown', function(ev) {
1307 //YAHOO.util.Event.stopEvent(ev);
1309 tmp.on('click', function(ev) {
1310 //YAHOO.util.Event.stopEvent(ev);
1312 tmp.on('change', function(ev) {
1314 if (!oButton.menucmd) {
1315 oButton.menucmd = oButton.value;
1317 oButton.value = ev.value;
1318 this._buttonClick(ev, oButton);
1323 //Hijack the mousedown event in the menu and make it fire a button click..
1324 tmp.on('appendTo', function() {
1326 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1327 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1328 var oMenu = args[1];
1329 YAHOO.util.Event.stopEvent(args[0]);
1330 tmp._onMenuClick(args[0], tmp);
1331 if (!oButton.menucmd) {
1332 oButton.menucmd = oButton.value;
1334 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1335 self._buttonClick.call(self, args[1], oButton);
1339 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1340 YAHOO.util.Event.stopEvent(args[0]);
1342 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1343 YAHOO.util.Event.stopEvent(args[0]);
1350 //Stop the mousedown event so we can trap the selection in the editor!
1351 tmp.on('mousedown', function(ev) {
1352 YAHOO.util.Event.stopEvent(ev);
1354 tmp.on('click', function(ev) {
1355 YAHOO.util.Event.stopEvent(ev);
1358 if (this.browser.ie) {
1360 //Add a couple of new events for IE
1361 tmp.DOM_EVENTS.focusin = true;
1362 tmp.DOM_EVENTS.focusout = true;
1364 //Stop them so we don't loose focus in the Editor
1365 tmp.on('focusin', function(ev) {
1366 YAHOO.util.Event.stopEvent(ev);
1369 tmp.on('focusout', function(ev) {
1370 YAHOO.util.Event.stopEvent(ev);
1372 tmp.on('click', function(ev) {
1373 YAHOO.util.Event.stopEvent(ev);
1377 if (this.browser.webkit) {
1378 //This will keep the document from gaining focus and the editor from loosing it..
1379 //Forcefully remove the focus calls in button!
1380 tmp.hasFocus = function() {
1384 this._buttonList[this._buttonList.length] = tmp;
1385 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1386 if (Lang.isArray(oButton.menu)) {
1387 var menu = tmp.getMenu();
1388 if (menu && menu.renderEvent) {
1389 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1390 if (oButton.renderer) {
1391 menu.renderEvent.subscribe(oButton.renderer, tmp);
1400 * @method addSeparator
1401 * @description Add a new button separator to the toolbar.
1402 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1403 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1405 addSeparator: function(cont, after) {
1406 if (!this.get('element')) {
1407 this._queue[this._queue.length] = ['addSeparator', arguments];
1410 var sepCont = ((cont) ? cont : this.get('cont'));
1411 if (!this.get('element')) {
1412 this._queue[this._queue.length] = ['addSeparator', arguments];
1415 if (this._sepCount === null) {
1419 this._sep = document.createElement('SPAN');
1420 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1421 this._sep.innerHTML = '|';
1423 var _sep = this._sep.cloneNode(true);
1425 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1429 nextSib = after.get('element').nextSibling;
1430 } else if (after.nextSibling) {
1431 nextSib = after.nextSibling;
1436 if (nextSib == after) {
1437 nextSib.parentNode.appendChild(_sep);
1439 nextSib.parentNode.insertBefore(_sep, nextSib);
1443 sepCont.appendChild(_sep);
1448 * @method _createColorPicker
1450 * @description Creates the core DOM reference to the color picker menu item.
1451 * @param {String} id the id of the toolbar to prefix this DOM container with.
1453 _createColorPicker: function(id) {
1454 if (Dom.get(id + '_colors')) {
1455 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1457 var picker = document.createElement('div');
1458 picker.className = 'yui-toolbar-colors';
1459 picker.id = id + '_colors';
1460 picker.style.display = 'none';
1461 Event.on(window, 'load', function() {
1462 document.body.appendChild(picker);
1465 this._colorPicker = picker;
1468 for (var i in this._colorData) {
1469 if (Lang.hasOwnProperty(this._colorData, i)) {
1470 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1473 html += '<span><em>X</em><strong></strong></span>';
1474 window.setTimeout(function() {
1475 picker.innerHTML = html;
1478 Event.on(picker, 'mouseover', function(ev) {
1479 var picker = this._colorPicker;
1480 var em = picker.getElementsByTagName('em')[0];
1481 var strong = picker.getElementsByTagName('strong')[0];
1482 var tar = Event.getTarget(ev);
1483 if (tar.tagName.toLowerCase() == 'a') {
1484 em.style.backgroundColor = tar.style.backgroundColor;
1485 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1488 Event.on(picker, 'focus', function(ev) {
1489 Event.stopEvent(ev);
1491 Event.on(picker, 'click', function(ev) {
1492 Event.stopEvent(ev);
1494 Event.on(picker, 'mousedown', function(ev) {
1495 Event.stopEvent(ev);
1496 var tar = Event.getTarget(ev);
1497 if (tar.tagName.toLowerCase() == 'a') {
1498 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1499 if (retVal !== false) {
1501 color: tar.innerHTML,
1502 colorName: this._colorData['#' + tar.innerHTML],
1503 value: this._colorPicker._button
1506 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1508 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1513 * @method _resetColorPicker
1515 * @description Clears the currently selected color or mouseover color in the color picker.
1517 _resetColorPicker: function() {
1518 var em = this._colorPicker.getElementsByTagName('em')[0];
1519 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1520 em.style.backgroundColor = 'transparent';
1521 strong.innerHTML = '';
1524 * @method _makeColorButton
1526 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1527 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1529 _makeColorButton: function(_oButton) {
1530 if (!this._colorPicker) {
1531 this._createColorPicker(this.get('id'));
1533 _oButton.type = 'color';
1534 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1535 _oButton.menu.setBody('');
1536 _oButton.menu.render(this.get('cont'));
1537 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1538 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1539 _oButton.menu.beforeShowEvent.subscribe(function() {
1540 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1541 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1542 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1543 this._resetColorPicker();
1544 var _p = this._colorPicker;
1545 if (_p.parentNode) {
1546 _p.parentNode.removeChild(_p);
1548 _oButton.menu.setBody('');
1549 _oButton.menu.appendToBody(_p);
1550 this._colorPicker.style.display = 'block';
1556 * @method _makeSpinButton
1557 * @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.
1558 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1559 * @param {Object} oButton Object literal containing the buttons initial config
1561 _makeSpinButton: function(_button, oButton) {
1562 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1564 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1565 range = oButton.range,
1566 _b1 = document.createElement('a'),
1567 _b2 = document.createElement('a');
1570 _b1.tabIndex = '-1';
1571 _b2.tabIndex = '-1';
1573 //Setup the up and down arrows
1574 _b1.className = 'up';
1575 _b1.title = this.STR_SPIN_UP;
1576 _b1.innerHTML = this.STR_SPIN_UP;
1577 _b2.className = 'down';
1578 _b2.title = this.STR_SPIN_DOWN;
1579 _b2.innerHTML = this.STR_SPIN_DOWN;
1581 //Append them to the container
1582 _par.appendChild(_b1);
1583 _par.appendChild(_b2);
1585 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1586 _button.set('title', label);
1588 var cleanVal = function(value) {
1589 value = ((value < range[0]) ? range[0] : value);
1590 value = ((value > range[1]) ? range[1] : value);
1594 var br = this.browser;
1596 var strLabel = this.STR_SPIN_LABEL;
1597 if (this._titlebar && this._titlebar.firstChild) {
1598 tbar = this._titlebar.firstChild;
1601 var _intUp = function(ev) {
1602 YAHOO.util.Event.stopEvent(ev);
1603 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1604 var value = parseInt(_button.get('label'), 10);
1606 value = cleanVal(value);
1607 _button.set('label', ''+value);
1608 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1609 _button.set('title', label);
1610 if (!br.webkit && tbar) {
1611 //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
1614 self._buttonClick(ev, oButton);
1618 var _intDown = function(ev) {
1619 YAHOO.util.Event.stopEvent(ev);
1620 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1621 var value = parseInt(_button.get('label'), 10);
1623 value = cleanVal(value);
1625 _button.set('label', ''+value);
1626 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1627 _button.set('title', label);
1628 if (!br.webkit && tbar) {
1629 //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
1632 self._buttonClick(ev, oButton);
1636 var _intKeyUp = function(ev) {
1637 if (ev.keyCode == 38) {
1639 } else if (ev.keyCode == 40) {
1641 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1643 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1648 //Handle arrow keys..
1649 _button.on('keydown', _intKeyUp, this, true);
1651 //Listen for the click on the up button and act on it
1652 //Listen for the click on the down button and act on it
1653 Event.on(_b1, 'mousedown',function(ev) {
1654 Event.stopEvent(ev);
1656 Event.on(_b2, 'mousedown', function(ev) {
1657 Event.stopEvent(ev);
1659 Event.on(_b1, 'click', _intUp, this, true);
1660 Event.on(_b2, 'click', _intDown, this, true);
1664 * @method _buttonClick
1665 * @description Click handler for all buttons in the toolbar.
1666 * @param {String} ev The event that was passed in.
1667 * @param {Object} info Object literal of information about the button that was clicked.
1669 _buttonClick: function(ev, info) {
1672 if (ev && ev.type == 'keypress') {
1673 if (ev.keyCode == 9) {
1675 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1682 var fireNextEvent = true,
1685 info.isSelected = this.isSelected(info.id);
1688 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1689 if (retValue === false) {
1690 fireNextEvent = false;
1694 if (info.menucmd && fireNextEvent) {
1695 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1696 if (retValue === false) {
1697 fireNextEvent = false;
1700 if (fireNextEvent) {
1701 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1704 if (info.type == 'select') {
1705 var button = this.getButtonById(info.id);
1706 if (button.buttonType == 'rich') {
1707 var txt = info.value;
1708 for (var i = 0; i < info.menu.length; i++) {
1709 if (info.menu[i].value == info.value) {
1710 txt = info.menu[i].text;
1714 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1715 var _items = button.getMenu().getItems();
1716 for (var m = 0; m < _items.length; m++) {
1717 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1718 _items[m].cfg.setProperty('checked', true);
1720 _items[m].cfg.setProperty('checked', false);
1726 Event.stopEvent(ev);
1733 * @description Flag to determine if the arrow nav listeners have been attached
1739 * @property _navCounter
1740 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1746 * @method _navigateButtons
1747 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1748 * @param {Event} ev The Key Event
1750 _navigateButtons: function(ev) {
1751 switch (ev.keyCode) {
1754 if (ev.keyCode == 37) {
1759 if (this._navCounter > (this._buttonList.length - 1)) {
1760 this._navCounter = 0;
1762 if (this._navCounter < 0) {
1763 this._navCounter = (this._buttonList.length - 1);
1765 if (this._buttonList[this._navCounter]) {
1766 var el = this._buttonList[this._navCounter].get('element');
1767 if (this.browser.ie) {
1768 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1770 if (this._buttonList[this._navCounter].get('disabled')) {
1771 this._navigateButtons(ev);
1781 * @method _handleFocus
1782 * @description Sets up the listeners for the arrow key navigation
1784 _handleFocus: function() {
1785 if (!this._keyNav) {
1786 var ev = 'keypress';
1787 if (this.browser.ie) {
1790 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1791 this._keyNav = true;
1792 this._navCounter = -1;
1796 * @method getButtonById
1797 * @description Gets a button instance from the toolbar by is Dom id.
1798 * @param {String} id The Dom id to query for.
1799 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1801 getButtonById: function(id) {
1802 var len = this._buttonList.length;
1803 for (var i = 0; i < len; i++) {
1804 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1805 return this._buttonList[i];
1811 * @method getButtonByValue
1812 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1813 * @param {String} value The button value to query for.
1814 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1816 getButtonByValue: function(value) {
1817 var _buttons = this.get('buttons');
1821 var len = _buttons.length;
1822 for (var i = 0; i < len; i++) {
1823 if (_buttons[i].group !== undefined) {
1824 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1825 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1826 return this.getButtonById(_buttons[i].buttons[m].id);
1828 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1829 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1830 if (_buttons[i].buttons[m].menu[s].value == value) {
1831 return this.getButtonById(_buttons[i].buttons[m].id);
1837 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1838 return this.getButtonById(_buttons[i].id);
1840 if (_buttons[i].menu) { //Menu Button, loop through the values
1841 for (var j = 0; j < _buttons[i].menu.length; j++) {
1842 if (_buttons[i].menu[j].value == value) {
1843 return this.getButtonById(_buttons[i].id);
1852 * @method getButtonByIndex
1853 * @description Gets a button instance from the toolbar by is index in _buttonList.
1854 * @param {Number} index The index of the button in _buttonList.
1855 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1857 getButtonByIndex: function(index) {
1858 if (this._buttonList[index]) {
1859 return this._buttonList[index];
1865 * @method getButtons
1866 * @description Returns an array of buttons in the current toolbar
1869 getButtons: function() {
1870 return this._buttonList;
1873 * @method disableButton
1874 * @description Disables a button in the toolbar.
1875 * @param {String/Number} id Disable a button by it's id, index or value.
1878 disableButton: function(id) {
1879 var button = getButton.call(this, id);
1881 button.set('disabled', true);
1887 * @method enableButton
1888 * @description Enables a button in the toolbar.
1889 * @param {String/Number} id Enable a button by it's id, index or value.
1892 enableButton: function(id) {
1893 if (this.get('disabled')) {
1896 var button = getButton.call(this, id);
1898 if (button.get('disabled')) {
1899 button.set('disabled', false);
1906 * @method isSelected
1907 * @description Tells if a button is selected or not.
1908 * @param {String/Number} id A button by it's id, index or value.
1911 isSelected: function(id) {
1912 var button = getButton.call(this, id);
1914 return button._selected;
1919 * @method selectButton
1920 * @description Selects a button in the toolbar.
1921 * @param {String/Number} id Select a button by it's id, index or value.
1922 * @param {String} value If this is a Menu Button, check this item in the menu
1925 selectButton: function(id, value) {
1926 var button = getButton.call(this, id);
1928 button.addClass('yui-button-selected');
1929 button.addClass('yui-button-' + button.get('value') + '-selected');
1930 button._selected = true;
1932 if (button.buttonType == 'rich') {
1933 var _items = button.getMenu().getItems();
1934 for (var m = 0; m < _items.length; m++) {
1935 if (_items[m].value == value) {
1936 _items[m].cfg.setProperty('checked', true);
1937 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1939 _items[m].cfg.setProperty('checked', false);
1949 * @method deselectButton
1950 * @description Deselects a button in the toolbar.
1951 * @param {String/Number} id Deselect a button by it's id, index or value.
1954 deselectButton: function(id) {
1955 var button = getButton.call(this, id);
1957 button.removeClass('yui-button-selected');
1958 button.removeClass('yui-button-' + button.get('value') + '-selected');
1959 button.removeClass('yui-button-hover');
1960 button._selected = false;
1966 * @method deselectAllButtons
1967 * @description Deselects all buttons in the toolbar.
1970 deselectAllButtons: function() {
1971 var len = this._buttonList.length;
1972 for (var i = 0; i < len; i++) {
1973 this.deselectButton(this._buttonList[i]);
1977 * @method disableAllButtons
1978 * @description Disables all buttons in the toolbar.
1981 disableAllButtons: function() {
1982 if (this.get('disabled')) {
1985 var len = this._buttonList.length;
1986 for (var i = 0; i < len; i++) {
1987 this.disableButton(this._buttonList[i]);
1991 * @method enableAllButtons
1992 * @description Enables all buttons in the toolbar.
1995 enableAllButtons: function() {
1996 if (this.get('disabled')) {
1999 var len = this._buttonList.length;
2000 for (var i = 0; i < len; i++) {
2001 this.enableButton(this._buttonList[i]);
2005 * @method resetAllButtons
2006 * @description Resets all buttons to their initial state.
2007 * @param {Object} _ex Except these buttons
2010 resetAllButtons: function(_ex) {
2011 if (!Lang.isObject(_ex)) {
2014 if (this.get('disabled') || !this._buttonList) {
2017 var len = this._buttonList.length;
2018 for (var i = 0; i < len; i++) {
2019 var _button = this._buttonList[i];
2021 var disabled = _button._configs.disabled._initialConfig.value;
2022 if (_ex[_button.get('id')]) {
2023 this.enableButton(_button);
2024 this.selectButton(_button);
2027 this.disableButton(_button);
2029 this.enableButton(_button);
2031 this.deselectButton(_button);
2037 * @method destroyButton
2038 * @description Destroy a button in the toolbar.
2039 * @param {String/Number} id Destroy a button by it's id or index.
2042 destroyButton: function(id) {
2043 var button = getButton.call(this, id);
2045 var thisID = button.get('id'),
2046 new_list = [], i = 0,
2047 len = this._buttonList.length;
2051 for (i = 0; i < len; i++) {
2052 if (this._buttonList[i].get('id') != thisID) {
2053 new_list[new_list.length]= this._buttonList[i];
2057 this._buttonList = new_list;
2064 * @description Destroys the toolbar, all of it's elements and objects.
2067 destroy: function() {
2068 var len = this._configuredButtons.length, j, i, b;
2069 for(b = 0; b < len; b++) {
2070 this.destroyButton(this._configuredButtons[b]);
2073 this._configuredButtons = null;
2075 this.get('element').innerHTML = '';
2076 this.get('element').className = '';
2077 //Brutal Object Destroy
2079 if (Lang.hasOwnProperty(this, i)) {
2087 * @description Programatically collapse the toolbar.
2088 * @param {Boolean} collapse True to collapse, false to expand.
2090 collapse: function(collapse) {
2091 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2092 if (collapse === false) {
2093 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2095 Dom.removeClass(el[0], 'collapsed');
2096 el[0].title = this.STR_COLLAPSE;
2098 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2101 Dom.addClass(el[0], 'collapsed');
2102 el[0].title = this.STR_EXPAND;
2104 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2105 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2110 * @description Returns a string representing the toolbar.
2113 toString: function() {
2114 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2118 * @event buttonClick
2119 * @param {Object} o The object passed to this handler is the button config used to create the button.
2120 * @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.
2121 * @type YAHOO.util.CustomEvent
2125 * @param {Object} o The object passed to this handler is the button config used to create the button.
2126 * @description This is a special dynamic event that is created and dispatched based on the value property
2127 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2131 * { type: 'button', value: 'test', value: 'testButton' }
2134 * With the valueClick event you could subscribe to this buttons click event with this:
2135 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2136 * @type YAHOO.util.CustomEvent
2139 * @event toolbarExpanded
2140 * @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.
2141 * @type YAHOO.util.CustomEvent
2144 * @event toolbarCollapsed
2145 * @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.
2146 * @type YAHOO.util.CustomEvent
2151 * @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>
2152 * @namespace YAHOO.widget
2153 * @requires yahoo, dom, element, event, toolbar
2154 * @optional animation, container_core, resize, dragdrop
2158 var Dom = YAHOO.util.Dom,
2159 Event = YAHOO.util.Event,
2161 Toolbar = YAHOO.widget.Toolbar;
2164 * 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.
2166 * @class SimpleEditor
2167 * @extends YAHOO.util.Element
2168 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2169 * @param {Object} attrs Object liternal containing configuration parameters.
2172 YAHOO.widget.SimpleEditor = function(el, attrs) {
2175 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2176 Lang.augmentObject(o, el); //Break the config reference
2177 el = document.createElement('textarea');
2178 this.DOMReady = true;
2180 var c = Dom.get(o.container);
2183 document.body.appendChild(el);
2187 Lang.augmentObject(o, attrs); //Break the config reference
2196 if (Lang.isString(el)) {
2199 if (oConfig.attributes.id) {
2200 id = oConfig.attributes.id;
2202 this.DOMReady = true;
2203 id = Dom.generateId(el);
2206 oConfig.element = el;
2208 var element_cont = document.createElement('DIV');
2209 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2210 id: id + '_container'
2212 var div = document.createElement('div');
2213 Dom.addClass(div, 'first-child');
2214 oConfig.attributes.element_cont.appendChild(div);
2216 if (!oConfig.attributes.toolbar_cont) {
2217 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2218 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2219 div.appendChild(oConfig.attributes.toolbar_cont);
2221 var editorWrapper = document.createElement('DIV');
2222 div.appendChild(editorWrapper);
2223 oConfig.attributes.editor_wrapper = editorWrapper;
2225 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2229 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2232 * @property _resizeConfig
2233 * @description The default config for the Resize Utility
2245 * @method _setupResize
2246 * @description Creates the Resize instance and binds its events.
2248 _setupResize: function() {
2249 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2250 if (this.get('resize')) {
2252 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2253 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2254 this.resize.on('resize', function(args) {
2255 var anim = this.get('animate');
2256 this.set('animate', false);
2257 this.set('width', args.width + 'px');
2258 var h = args.height,
2259 th = (this.toolbar.get('element').clientHeight + 2),
2262 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2264 var newH = (h - th - dh);
2265 this.set('height', newH + 'px');
2266 this.get('element_cont').setStyle('height', '');
2267 this.set('animate', anim);
2273 * @description A reference to the Resize object
2274 * @type YAHOO.util.Resize
2280 * @description Sets up the DD instance used from the 'drag' config option.
2282 _setupDD: function() {
2283 if (!YAHOO.util.DD) { return false; }
2284 if (this.get('drag')) {
2285 var d = this.get('drag'),
2287 if (d === 'proxy') {
2288 dd = YAHOO.util.DDProxy;
2291 this.dd = new dd(this.get('element_cont').get('element'));
2292 this.toolbar.addClass('draggable');
2293 this.dd.setHandleElId(this.toolbar._titlebar);
2298 * @description A reference to the DragDrop object.
2299 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2304 * @property _lastCommand
2305 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2309 _undoNodeChange: function() {},
2310 _storeUndo: function() {},
2314 * @description Checks a keyMap entry against a key event
2315 * @param {Object} k The _keyMap object
2316 * @param {Event} e The Mouse Event
2319 _checkKey: function(k, e) {
2321 if ((e.keyCode === k.key)) {
2322 if (k.mods && (k.mods.length > 0)) {
2324 for (var i = 0; i < k.mods.length; i++) {
2325 if (this.browser.mac) {
2326 if (k.mods[i] == 'ctrl') {
2330 if (e[k.mods[i] + 'Key'] === true) {
2334 if (val === k.mods.length) {
2346 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2347 * 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.
2348 * @type {Object/Mixed}
2357 mods: ['shift', 'ctrl']
2368 mods: ['shift', 'ctrl']
2372 mods: ['shift', 'ctrl']
2376 mods: ['shift', 'ctrl']
2380 mods: ['shift', 'ctrl']
2384 mods: ['shift', 'ctrl']
2388 mods: ['shift', 'ctrl']
2396 mods: ['shift', 'ctrl']
2400 mods: ['shift', 'ctrl']
2404 mods: ['shift', 'ctrl']
2408 mods: ['shift', 'ctrl']
2413 * @method _cleanClassName
2414 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2415 * @param {String} str The classname to clean up
2418 _cleanClassName: function(str) {
2419 return str.replace(/ /g, '-').toLowerCase();
2422 * @property _textarea
2423 * @description Flag to determine if we are using a textarea or an HTML Node.
2428 * @property _docType
2429 * @description The DOCTYPE to use in the editable container.
2432 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2434 * @property editorDirty
2435 * @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.
2440 * @property _defaultCSS
2441 * @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' }
2444 _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; }',
2446 * @property _defaultToolbar
2448 * @description Default toolbar config.
2451 _defaultToolbar: null,
2453 * @property _lastButton
2455 * @description The last button pressed, so we don't disable it.
2460 * @property _baseHREF
2462 * @description The base location of the editable page (this page) so that relative paths for image work.
2465 _baseHREF: function() {
2466 var href = document.location.href;
2467 if (href.indexOf('?') !== -1) { //Remove the query string
2468 href = href.substring(0, href.indexOf('?'));
2470 href = href.substring(0, href.lastIndexOf('/')) + '/';
2474 * @property _lastImage
2476 * @description Safari reference for the last image selected (for styling as selected).
2481 * @property _blankImageLoaded
2483 * @description Don't load the blank image more than once..
2486 _blankImageLoaded: null,
2488 * @property _fixNodesTimer
2490 * @description Holder for the fixNodes timer
2493 _fixNodesTimer: null,
2495 * @property _nodeChangeTimer
2497 * @description Holds a reference to the nodeChange setTimeout call
2500 _nodeChangeTimer: null,
2502 * @property _nodeChangeDelayTimer
2504 * @description Holds a reference to the nodeChangeDelay setTimeout call
2507 _nodeChangeDelayTimer: null,
2509 * @property _lastNodeChangeEvent
2511 * @description Flag to determine the last event that fired a node change
2514 _lastNodeChangeEvent: null,
2516 * @property _lastNodeChange
2518 * @description Flag to determine when the last node change was fired
2523 * @property _rendered
2525 * @description Flag to determine if editor has been rendered or not
2530 * @property DOMReady
2532 * @description Flag to determine if DOM is ready or not
2537 * @property _selection
2539 * @description Holder for caching iframe selections
2546 * @description DOM Element holder for the editor Mask when disabled
2551 * @property _showingHiddenElements
2553 * @description Status of the hidden elements button
2556 _showingHiddenElements: null,
2558 * @property currentWindow
2559 * @description A reference to the currently open EditorWindow
2562 currentWindow: null,
2564 * @property currentEvent
2565 * @description A reference to the current editor event
2570 * @property operaEvent
2572 * @description setTimeout holder for Opera and Image DoubleClick event..
2577 * @property currentFont
2578 * @description A reference to the last font selected from the Toolbar
2583 * @property currentElement
2584 * @description A reference to the current working element in the editor
2587 currentElement: null,
2590 * @description A reference to the dompath container for writing the current working dom path to.
2595 * @property beforeElement
2596 * @description A reference to the H2 placed before the editor for Accessibilty.
2599 beforeElement: null,
2601 * @property afterElement
2602 * @description A reference to the H2 placed after the editor for Accessibilty.
2607 * @property invalidHTML
2608 * @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.
2626 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2627 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2632 * @property _contentTimer
2633 * @description setTimeout holder for documentReady check
2635 _contentTimer: null,
2638 * @property _contentTimerMax
2639 * @description The number of times the loaded content should be checked before giving up. Default: 500
2641 _contentTimerMax: 500,
2644 * @property _contentTimerCounter
2645 * @description Counter to check the number of times the body is polled for before giving up
2648 _contentTimerCounter: 0,
2651 * @property _disabled
2652 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2655 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2658 * @property _alwaysDisabled
2659 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2662 _alwaysDisabled: { undo: true, redo: true },
2665 * @property _alwaysEnabled
2666 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2669 _alwaysEnabled: { },
2672 * @property _semantic
2673 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2676 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2679 * @property _tag2cmd
2680 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2689 'sup': 'superscript',
2691 'img': 'insertimage',
2693 'ul' : 'insertunorderedlist',
2694 'ol' : 'insertorderedlist'
2698 * @private _createIframe
2699 * @description Creates the DOM and YUI Element for the iFrame editor area.
2700 * @param {String} id The string ID to prefix the iframe with
2701 * @return {Object} iFrame object
2703 _createIframe: function() {
2704 var ifrmDom = document.createElement('iframe');
2705 ifrmDom.id = this.get('id') + '_editor';
2713 allowTransparency: 'true',
2716 if (this.get('autoHeight')) {
2717 config.scrolling = 'no';
2719 for (var i in config) {
2720 if (Lang.hasOwnProperty(config, i)) {
2721 ifrmDom.setAttribute(i, config[i]);
2724 var isrc = 'javascript:;';
2725 if (this.browser.ie) {
2726 //isrc = 'about:blank';
2727 //TODO - Check this, I have changed it before..
2728 isrc = 'javascript:false;';
2730 ifrmDom.setAttribute('src', isrc);
2731 var ifrm = new YAHOO.util.Element(ifrmDom);
2732 ifrm.setStyle('visibility', 'hidden');
2736 * @private _isElement
2737 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2738 * @param {HTMLElement} el The element to check
2739 * @param {String} tag The tag that the element needs to be
2742 _isElement: function(el, tag) {
2743 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2746 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2752 * @private _hasParent
2753 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2754 * @param {HTMLElement} el The element to check
2755 * @param {String} tag The tag that the element needs to be
2756 * @return HTMLElement
2758 _hasParent: function(el, tag) {
2759 if (!el || !el.parentNode) {
2763 while (el.parentNode) {
2764 if (this._isElement(el, tag)) {
2767 if (el.parentNode) {
2778 * @description Get the Document of the IFRAME
2781 _getDoc: function() {
2784 if (this.get('iframe').get('element').contentWindow.document) {
2785 value = this.get('iframe').get('element').contentWindow.document;
2794 * @method _getWindow
2795 * @description Get the Window of the IFRAME
2798 _getWindow: function() {
2799 return this.get('iframe').get('element').contentWindow;
2803 * @description Attempt to set the focus of the iframes window.
2806 this._getWindow().focus();
2810 * @depreciated - This should not be used, moved to this.focus();
2811 * @method _focusWindow
2812 * @description Attempt to set the focus of the iframes window.
2814 _focusWindow: function() {
2819 * @method _hasSelection
2820 * @description Determines if there is a selection in the editor document.
2823 _hasSelection: function() {
2824 var sel = this._getSelection();
2825 var range = this._getRange();
2828 if (!sel || !range) {
2833 if (this.browser.ie) {
2841 if (this.browser.webkit) {
2842 if (sel+'' !== '') {
2846 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2855 * @method _getSelection
2856 * @description Handles the different selection objects across the A-Grade list.
2857 * @return {Object} Selection Object
2859 _getSelection: function() {
2861 if (this._getDoc() && this._getWindow()) {
2862 if (this._getDoc().selection &&! this.browser.opera) {
2863 _sel = this._getDoc().selection;
2865 _sel = this._getWindow().getSelection();
2867 //Handle Safari's lack of Selection Object
2868 if (this.browser.webkit) {
2869 if (_sel.baseNode) {
2870 this._selection = {};
2871 this._selection.baseNode = _sel.baseNode;
2872 this._selection.baseOffset = _sel.baseOffset;
2873 this._selection.extentNode = _sel.extentNode;
2874 this._selection.extentOffset = _sel.extentOffset;
2875 } else if (this._selection !== null) {
2876 _sel = this._getWindow().getSelection();
2877 _sel.setBaseAndExtent(
2878 this._selection.baseNode,
2879 this._selection.baseOffset,
2880 this._selection.extentNode,
2881 this._selection.extentOffset);
2882 this._selection = null;
2890 * @method _selectNode
2891 * @description Places the highlight around a given node
2892 * @param {HTMLElement} node The node to select
2894 _selectNode: function(node, collapse) {
2898 var sel = this._getSelection(),
2901 if (this.browser.ie) {
2902 try { //IE freaks out here sometimes..
2903 range = this._getDoc().body.createTextRange();
2904 range.moveToElementText(node);
2908 } else if (this.browser.webkit) {
2910 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2912 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2914 } else if (this.browser.opera) {
2915 sel = this._getWindow().getSelection();
2916 range = this._getDoc().createRange();
2917 range.selectNode(node);
2918 sel.removeAllRanges();
2919 sel.addRange(range);
2921 range = this._getDoc().createRange();
2922 range.selectNodeContents(node);
2923 sel.removeAllRanges();
2924 sel.addRange(range);
2926 //TODO - Check Performance
2932 * @description Handles the different range objects across the A-Grade list.
2933 * @return {Object} Range Object
2935 _getRange: function() {
2936 var sel = this._getSelection();
2942 if (this.browser.webkit && !sel.getRangeAt) {
2943 var _range = this._getDoc().createRange();
2945 _range.setStart(sel.anchorNode, sel.anchorOffset);
2946 _range.setEnd(sel.focusNode, sel.focusOffset);
2948 _range = this._getWindow().getSelection()+'';
2953 if (this.browser.ie) {
2955 return sel.createRange();
2961 if (sel.rangeCount > 0) {
2962 return sel.getRangeAt(0);
2968 * @method _setDesignMode
2969 * @description Sets the designMode property of the iFrame document's body.
2970 * @param {String} state This should be either on or off
2972 _setDesignMode: function(state) {
2973 if (this.get('setDesignMode')) {
2975 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on');
2981 * @method _toggleDesignMode
2982 * @description Toggles the designMode property of the iFrame document on and off.
2983 * @return {String} The state that it was set to.
2985 _toggleDesignMode: function() {
2986 var _dMode = this._getDoc().designMode,
2987 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on');
2988 this._setDesignMode(_state);
2993 * @property _focused
2994 * @description Holder for trapping focus/blur state and prevent double events
3000 * @method _handleFocus
3001 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
3002 * @param {Event} e The DOM Event
3004 _handleFocus: function(e) {
3005 if (!this._focused) {
3006 this._focused = true;
3007 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
3012 * @method _handleBlur
3013 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
3014 * @param {Event} e The DOM Event
3016 _handleBlur: function(e) {
3017 if (this._focused) {
3018 this._focused = false;
3019 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
3024 * @method _initEditorEvents
3025 * @description This method sets up the listeners on the Editors document.
3027 _initEditorEvents: function() {
3028 //Setup Listeners on iFrame
3029 var doc = this._getDoc(),
3030 win = this._getWindow();
3032 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3033 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3034 Event.on(doc, 'click', this._handleClick, this, true);
3035 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3036 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3037 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3038 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3039 /* TODO -- Everyone but Opera works here..
3040 Event.on(doc, 'paste', function() {
3045 Event.on(win, 'focus', this._handleFocus, this, true);
3046 Event.on(win, 'blur', this._handleBlur, this, true);
3050 * @method _removeEditorEvents
3051 * @description This method removes the listeners on the Editors document (for disabling).
3053 _removeEditorEvents: function() {
3054 //Remove Listeners on iFrame
3055 var doc = this._getDoc(),
3056 win = this._getWindow();
3058 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3059 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3060 Event.removeListener(doc, 'click', this._handleClick, this, true);
3061 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3062 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3063 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3064 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3067 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3068 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3070 _fixWebkitDivs: function() {
3071 if (this.browser.webkit) {
3072 var divs = this._getDoc().body.getElementsByTagName('div');
3073 Dom.addClass(divs, 'yui-wk-div');
3078 * @method _initEditor
3079 * @param {Boolean} raw Don't add events.
3080 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3082 _initEditor: function(raw) {
3083 if (this._editorInit) {
3086 this._editorInit = true;
3087 if (this.browser.ie) {
3088 this._getDoc().body.style.margin = '0';
3090 if (!this.get('disabled')) {
3091 this._setDesignMode('on');
3092 this._contentTimerCounter = 0;
3094 if (!this._getDoc().body) {
3095 this._contentTimerCounter = 0;
3096 this._editorInit = false;
3097 this._checkLoaded();
3102 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3104 if (!this.get('disabled')) {
3105 this._initEditorEvents();
3106 this.toolbar.set('disabled', false);
3110 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this });
3112 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3114 this._fixWebkitDivs();
3115 if (this.get('dompath')) {
3117 setTimeout(function() {
3118 self._writeDomPath.call(self);
3119 self._setupResize.call(self);
3123 for (var i in this.browser) {
3124 if (this.browser[i]) {
3128 if (this.get('ptags')) {
3131 Dom.addClass(this._getDoc().body, br.join(' '));
3132 this.nodeChange(true);
3136 * @method _checkLoaded
3137 * @param {Boolean} raw Don't add events.
3138 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3140 _checkLoaded: function(raw) {
3141 this._editorInit = false;
3142 this._contentTimerCounter++;
3143 if (this._contentTimer) {
3144 clearTimeout(this._contentTimer);
3146 if (this._contentTimerCounter > this._contentTimerMax) {
3151 if (this._getDoc() && this._getDoc().body) {
3152 if (this.browser.ie) {
3153 if (this._getDoc().body.readyState == 'complete') {
3157 if (this._getDoc().body._rteLoaded === true) {
3166 if (init === true) {
3167 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3168 this._initEditor(raw);
3171 this._contentTimer = setTimeout(function() {
3172 self._checkLoaded.call(self, raw);
3178 * @method _setInitialContent
3179 * @param {Boolean} raw Don't add events.
3180 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3182 _setInitialContent: function(raw) {
3184 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3191 var html = Lang.substitute(this.get('html'), {
3192 TITLE: this.STR_TITLE,
3193 CONTENT: this._cleanIncomingHTML(value),
3194 CSS: this.get('css'),
3195 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3196 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3200 html = html.replace(/RIGHT_BRACKET/gi, '{');
3201 html = html.replace(/LEFT_BRACKET/gi, '}');
3203 if (document.compatMode != 'BackCompat') {
3204 html = this._docType + "\n" + html;
3208 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3209 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3212 if (this.browser.air) {
3213 doc = this._getDoc().implementation.createHTMLDocument();
3214 var origDoc = this._getDoc();
3220 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3221 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3222 origDoc.body._rteLoaded = true;
3224 doc = this._getDoc();
3230 //Safari will only be here if we are hidden
3234 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3235 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3237 this.get('iframe').setStyle('visibility', '');
3239 this._checkLoaded(raw);
3244 * @method _setMarkupType
3245 * @param {String} action The action to take. Possible values are: css, default or semantic
3246 * @description This method will turn on/off the useCSS execCommand.
3248 _setMarkupType: function(action) {
3249 switch (this.get('markup')) {
3251 this._setEditorStyle(true);
3254 this._setEditorStyle(false);
3258 if (this._semantic[action]) {
3259 this._setEditorStyle(false);
3261 this._setEditorStyle(true);
3267 * Set the editor to use CSS instead of HTML
3268 * @param {Booleen} stat True/False
3270 _setEditorStyle: function(stat) {
3272 this._getDoc().execCommand('useCSS', false, !stat);
3278 * @method _getSelectedElement
3279 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3280 * @return {HTMLElement} The currently selected element.
3282 _getSelectedElement: function() {
3283 var doc = this._getDoc(),
3289 if (this.browser.ie) {
3290 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3291 range = this._getRange();
3293 elm = range.item ? range.item(0) : range.parentElement();
3294 if (this._hasSelection()) {
3296 //WTF.. Why can't I get an element reference here?!??!
3298 if (elm === doc.body) {
3302 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3303 elm = Event.getTarget(this.currentEvent);
3306 sel = this._getSelection();
3307 range = this._getRange();
3309 if (!sel || !range) {
3313 if (!this._hasSelection() && this.browser.webkit3) {
3316 if (this.browser.gecko) {
3318 if (range.startContainer) {
3319 if (range.startContainer.nodeType === 3) {
3320 elm = range.startContainer.parentNode;
3321 } else if (range.startContainer.nodeType === 1) {
3322 elm = range.startContainer;
3325 if (this.currentEvent) {
3326 var tar = Event.getTarget(this.currentEvent);
3327 if (!this._isElement(tar, 'html')) {
3337 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3338 if (sel.anchorNode.parentNode) { //next check parentNode
3339 elm = sel.anchorNode.parentNode;
3341 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3342 elm = sel.anchorNode.nextSibling;
3345 if (this._isElement(elm, 'br')) {
3349 elm = range.commonAncestorContainer;
3350 if (!range.collapsed) {
3351 if (range.startContainer == range.endContainer) {
3352 if (range.startOffset - range.endOffset < 2) {
3353 if (range.startContainer.hasChildNodes()) {
3354 elm = range.startContainer.childNodes[range.startOffset];
3363 if (this.currentEvent !== null) {
3365 switch (this.currentEvent.type) {
3369 if (this.browser.webkit) {
3370 elm = Event.getTarget(this.currentEvent);
3379 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3380 //TODO is this still needed?
3381 //elm = this.currentElement[0];
3385 if (this.browser.opera || this.browser.webkit) {
3386 if (this.currentEvent && !elm) {
3387 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3390 if (!elm || !elm.tagName) {
3393 if (this._isElement(elm, 'html')) {
3394 //Safari sometimes gives us the HTML node back..
3397 if (this._isElement(elm, 'body')) {
3398 //make sure that body means this body not the parent..
3401 if (elm && !elm.parentNode) { //Not in document
3404 if (elm === undefined) {
3411 * @method _getDomPath
3412 * @description This method will attempt to build the DOM path from the currently selected element.
3413 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3414 * @return {Array} An array of node references that will create the DOM Path.
3416 _getDomPath: function(el) {
3418 el = this._getSelectedElement();
3421 while (el !== null) {
3422 if (el.ownerDocument != this._getDoc()) {
3426 //Check to see if we get el.nodeName and nodeType
3427 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3428 domPath[domPath.length] = el;
3431 if (this._isElement(el, 'body')) {
3437 if (domPath.length === 0) {
3438 if (this._getDoc() && this._getDoc().body) {
3439 domPath[0] = this._getDoc().body;
3442 return domPath.reverse();
3446 * @method _writeDomPath
3447 * @description Write the current DOM path out to the dompath container below the editor.
3449 _writeDomPath: function() {
3450 var path = this._getDomPath(),
3455 for (var i = 0; i < path.length; i++) {
3456 var tag = path[i].tagName.toLowerCase();
3457 if ((tag == 'ol') && (path[i].type)) {
3458 tag += ':' + path[i].type;
3460 if (Dom.hasClass(path[i], 'yui-tag')) {
3461 tag = path[i].getAttribute('tag');
3463 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3465 case 'b': tag = 'strong'; break;
3466 case 'i': tag = 'em'; break;
3469 if (!Dom.hasClass(path[i], 'yui-non')) {
3470 if (Dom.hasClass(path[i], 'yui-tag')) {
3473 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3474 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3477 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3484 if (path[i].getAttribute('href', 2)) {
3485 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3489 var h = path[i].height;
3490 var w = path[i].width;
3491 if (path[i].style.height) {
3492 h = parseInt(path[i].style.height, 10);
3494 if (path[i].style.width) {
3495 w = parseInt(path[i].style.width, 10);
3497 pathStr += '(' + w + 'x' + h + ')';
3501 if (pathStr.length > 10) {
3502 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3504 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3506 pathArr[pathArr.length] = pathStr;
3509 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3510 //Prevent flickering
3511 if (this.dompath.innerHTML != str) {
3512 this.dompath.innerHTML = str;
3518 * @description Fix href and imgs as well as remove invalid HTML.
3520 _fixNodes: function() {
3522 var doc = this._getDoc(),
3525 for (var v in this.invalidHTML) {
3526 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3527 if (v.toLowerCase() != 'span') {
3528 var tags = doc.body.getElementsByTagName(v);
3530 for (var i = 0; i < tags.length; i++) {
3537 for (var h = 0; h < els.length; h++) {
3538 if (els[h].parentNode) {
3539 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3540 this._swapEl(els[h], 'span', function(el) {
3541 el.className = 'yui-non';
3544 els[h].parentNode.removeChild(els[h]);
3548 var imgs = this._getDoc().getElementsByTagName('img');
3549 Dom.addClass(imgs, 'yui-img');
3554 * @method _isNonEditable
3555 * @param Event ev The Dom event being checked
3556 * @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.
3557 * 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
3558 * disable and enable the Editor's toolbar based on the noedit state.
3561 _isNonEditable: function(ev) {
3562 if (this.get('allowNoEdit')) {
3563 var el = Event.getTarget(ev);
3564 if (this._isElement(el, 'html')) {
3567 var path = this._getDomPath(el);
3568 for (var i = (path.length - 1); i > -1; i--) {
3569 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3570 //if (this.toolbar.get('disabled') === false) {
3571 // this.toolbar.set('disabled', true);
3574 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3577 Event.stopEvent(ev);
3581 //if (this.toolbar.get('disabled') === true) {
3582 //Should only happen once..
3583 //this.toolbar.set('disabled', false);
3585 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3593 * @method _setCurrentEvent
3594 * @param {Event} ev The event to cache
3595 * @description Sets the current event property
3597 _setCurrentEvent: function(ev) {
3598 this.currentEvent = ev;
3602 * @method _handleClick
3603 * @param {Event} ev The event we are working on.
3604 * @description Handles all click events inside the iFrame document.
3606 _handleClick: function(ev) {
3607 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3608 if (ret === false) {
3611 if (this._isNonEditable(ev)) {
3614 this._setCurrentEvent(ev);
3615 if (this.currentWindow) {
3618 if (this.currentWindow) {
3621 if (this.browser.webkit) {
3622 var tar =Event.getTarget(ev);
3623 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3624 Event.stopEvent(ev);
3630 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3634 * @method _handleMouseUp
3635 * @param {Event} ev The event we are working on.
3636 * @description Handles all mouseup events inside the iFrame document.
3638 _handleMouseUp: function(ev) {
3639 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3640 if (ret === false) {
3643 if (this._isNonEditable(ev)) {
3646 //Don't set current event for mouseup.
3647 //It get's fired after a menu is closed and gives up a bogus event to work with
3648 //this._setCurrentEvent(ev);
3650 if (this.browser.opera) {
3652 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3654 * @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.
3656 var sel = Event.getTarget(ev);
3657 if (this._isElement(sel, 'img')) {
3659 if (this.operaEvent) {
3660 clearTimeout(this.operaEvent);
3661 this.operaEvent = null;
3662 this._handleDoubleClick(ev);
3664 this.operaEvent = window.setTimeout(function() {
3665 self.operaEvent = false;
3670 //This will stop Safari from selecting the entire document if you select all the text in the editor
3671 if (this.browser.webkit || this.browser.opera) {
3672 if (this.browser.webkit) {
3673 Event.stopEvent(ev);
3677 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3681 * @method _handleMouseDown
3682 * @param {Event} ev The event we are working on.
3683 * @description Handles all mousedown events inside the iFrame document.
3685 _handleMouseDown: function(ev) {
3686 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3687 if (ret === false) {
3690 if (this._isNonEditable(ev)) {
3693 this._setCurrentEvent(ev);
3694 var sel = Event.getTarget(ev);
3695 if (this.browser.webkit && this._hasSelection()) {
3696 var _sel = this._getSelection();
3697 if (!this.browser.webkit3) {
3698 _sel.collapse(true);
3700 _sel.collapseToStart();
3703 if (this.browser.webkit && this._lastImage) {
3704 Dom.removeClass(this._lastImage, 'selected');
3705 this._lastImage = null;
3707 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3708 if (this.browser.webkit) {
3709 Event.stopEvent(ev);
3710 if (this._isElement(sel, 'img')) {
3711 Dom.addClass(sel, 'selected');
3712 this._lastImage = sel;
3715 if (this.currentWindow) {
3720 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3724 * @method _handleDoubleClick
3725 * @param {Event} ev The event we are working on.
3726 * @description Handles all doubleclick events inside the iFrame document.
3728 _handleDoubleClick: function(ev) {
3729 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3730 if (ret === false) {
3733 if (this._isNonEditable(ev)) {
3736 this._setCurrentEvent(ev);
3737 var sel = Event.getTarget(ev);
3738 if (this._isElement(sel, 'img')) {
3739 this.currentElement[0] = sel;
3740 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3741 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3742 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3743 this.currentElement[0] = this._hasParent(sel, 'a');
3744 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3745 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3748 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3752 * @method _handleKeyUp
3753 * @param {Event} ev The event we are working on.
3754 * @description Handles all keyup events inside the iFrame document.
3756 _handleKeyUp: function(ev) {
3757 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3758 if (ret === false) {
3761 if (this._isNonEditable(ev)) {
3765 this._setCurrentEvent(ev);
3766 switch (ev.keyCode) {
3767 case this._keyMap.SELECT_ALL.key:
3768 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3772 case 32: //Space Bar
3775 case 37: //Left Arrow
3777 case 39: //Right Arrow
3778 case 40: //Down Arrow
3779 case 46: //Forward Delete
3781 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3782 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3783 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3787 if (!this.browser.ie) {
3788 if (this._nodeChangeTimer) {
3789 clearTimeout(this._nodeChangeTimer);
3792 this._nodeChangeTimer = setTimeout(function() {
3793 self._nodeChangeTimer = null;
3794 self.nodeChange.call(self);
3799 this.editorDirty = true;
3803 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3807 * @method _handleKeyPress
3808 * @param {Event} ev The event we are working on.
3809 * @description Handles all keypress events inside the iFrame document.
3811 _handleKeyPress: function(ev) {
3812 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3813 if (ret === false) {
3817 if (this.get('allowNoEdit')) {
3818 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3819 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3820 //Forward delete key
3821 Event.stopEvent(ev);
3824 if (this._isNonEditable(ev)) {
3827 this._setCurrentEvent(ev);
3829 if (this.browser.opera) {
3830 if (ev.keyCode === 13) {
3831 var tar = this._getSelectedElement();
3832 if (!this._isElement(tar, 'li')) {
3833 this.execCommand('inserthtml', '<br>');
3834 Event.stopEvent(ev);
3838 if (this.browser.webkit) {
3839 if (!this.browser.webkit3) {
3840 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3841 //This is CMD + z (for undo)
3842 if (this._hasParent(this._getSelectedElement(), 'li')) {
3843 Event.stopEvent(ev);
3849 this._fixListDupIds();
3850 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3854 * @method _handleKeyDown
3855 * @param {Event} ev The event we are working on.
3856 * @description Handles all keydown events inside the iFrame document.
3858 _handleKeyDown: function(ev) {
3859 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3860 if (ret === false) {
3863 var tar = null, _range = null;
3864 if (this._isNonEditable(ev)) {
3867 this._setCurrentEvent(ev);
3868 if (this.currentWindow) {
3871 if (this.currentWindow) {
3880 switch (ev.keyCode) {
3881 case this._keyMap.FOCUS_TOOLBAR.key:
3882 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3883 var h = this.toolbar.getElementsByTagName('h2')[0];
3884 if (h && h.firstChild) {
3885 h.firstChild.focus();
3887 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3888 //Focus After Element - Esc
3889 this.afterElement.focus();
3891 Event.stopEvent(ev);
3895 case this._keyMap.CREATE_LINK.key: //L
3896 if (this._hasSelection()) {
3897 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3898 var makeLink = true;
3899 if (this.get('limitCommands')) {
3900 if (!this.toolbar.getButtonByValue('createlink')) {
3905 this.execCommand('createlink', '');
3906 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3907 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3914 case this._keyMap.UNDO.key:
3915 case this._keyMap.REDO.key:
3916 if (this._checkKey(this._keyMap.REDO, ev)) {
3919 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3925 case this._keyMap.BOLD.key:
3926 if (this._checkKey(this._keyMap.BOLD, ev)) {
3931 case this._keyMap.FONT_SIZE_UP.key:
3932 case this._keyMap.FONT_SIZE_DOWN.key:
3933 var uk = false, dk = false;
3934 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3937 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3941 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3942 label = parseInt(fs_button.get('label'), 10),
3943 newValue = (label + 1);
3946 newValue = (label - 1);
3949 action = 'fontsize';
3950 value = newValue + 'px';
3955 case this._keyMap.ITALIC.key:
3956 if (this._checkKey(this._keyMap.ITALIC, ev)) {
3962 case this._keyMap.UNDERLINE.key:
3963 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3964 action = 'underline';
3969 if (this.browser.ie) {
3970 //Insert a tab in Internet Explorer
3971 _range = this._getRange();
3972 tar = this._getSelectedElement();
3973 if (!this._isElement(tar, 'li')) {
3975 _range.pasteHTML(' ');
3976 _range.collapse(false);
3979 Event.stopEvent(ev);
3983 if (this.browser.gecko > 1.8) {
3984 tar = this._getSelectedElement();
3985 if (this._isElement(tar, 'li')) {
3987 this._getDoc().execCommand('outdent', null, '');
3989 this._getDoc().execCommand('indent', null, '');
3992 } else if (!this._hasSelection()) {
3993 this.execCommand('inserthtml', ' ');
3995 Event.stopEvent(ev);
3999 var p = null, i = 0;
4000 if (this.get('ptags') && !ev.shiftKey) {
4001 if (this.browser.gecko) {
4002 tar = this._getSelectedElement();
4003 if (!this._hasParent(tar, 'li')) {
4004 if (this._hasParent(tar, 'p')) {
4005 p = this._getDoc().createElement('p');
4006 p.innerHTML = ' ';
4007 Dom.insertAfter(p, tar);
4008 this._selectNode(p.firstChild);
4009 } else if (this._isElement(tar, 'body')) {
4010 this.execCommand('insertparagraph', null);
4011 var ps = this._getDoc().body.getElementsByTagName('p');
4012 for (i = 0; i < ps.length; i++) {
4013 if (ps[i].getAttribute('_moz_dirty') !== null) {
4014 p = this._getDoc().createElement('p');
4015 p.innerHTML = ' ';
4016 Dom.insertAfter(p, ps[i]);
4017 this._selectNode(p.firstChild);
4018 ps[i].removeAttribute('_moz_dirty');
4023 action = 'insertparagraph';
4025 Event.stopEvent(ev);
4028 if (this.browser.webkit) {
4029 tar = this._getSelectedElement();
4030 if (!this._hasParent(tar, 'li')) {
4031 this.execCommand('insertparagraph', null);
4032 var divs = this._getDoc().body.getElementsByTagName('div');
4033 for (i = 0; i < divs.length; i++) {
4034 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4035 Dom.addClass(divs[i], 'yui-wk-p');
4038 Event.stopEvent(ev);
4042 if (this.browser.webkit) {
4043 tar = this._getSelectedElement();
4044 if (!this._hasParent(tar, 'li')) {
4045 if (this.browser.webkit4) {
4046 this.execCommand('insertlinebreak');
4048 this.execCommand('inserthtml', '<var id="yui-br"></var>');
4049 var holder = this._getDoc().getElementById('yui-br'),
4050 br = this._getDoc().createElement('br'),
4051 caret = this._getDoc().createElement('span');
4053 holder.parentNode.replaceChild(br, holder);
4054 caret.className = 'yui-non';
4055 caret.innerHTML = ' ';
4056 Dom.insertAfter(caret, br);
4057 this._selectNode(caret);
4059 Event.stopEvent(ev);
4062 if (this.browser.ie) {
4063 //Insert a <br> instead of a <p></p> in Internet Explorer
4064 _range = this._getRange();
4065 tar = this._getSelectedElement();
4066 if (!this._isElement(tar, 'li')) {
4068 _range.pasteHTML('<br>');
4069 _range.collapse(false);
4072 Event.stopEvent(ev);
4078 if (this.browser.ie) {
4081 if (doExec && action) {
4082 this.execCommand(action, value);
4083 Event.stopEvent(ev);
4087 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4091 * @property _fixListRunning
4093 * @description Keeps more than one _fixListDupIds from running at the same time.
4095 _fixListRunning: null,
4098 * @method _fixListDupIds
4099 * @description Some browsers will duplicate the id of an LI when created in designMode.
4100 * This method will fix the duplicate id issue. However it will only preserve the first element
4101 * in the document list with the unique id.
4103 _fixListDupIds: function() {
4104 if (this._fixListRunning) {
4107 if (this._getDoc()) {
4108 this._fixListRunning = true;
4109 var lis = this._getDoc().body.getElementsByTagName('li'),
4111 for (i = 0; i < lis.length; i++) {
4113 if (ids[lis[i].id]) {
4116 ids[lis[i].id] = true;
4119 this._fixListRunning = false;
4125 * @param {Event} ev The event we are working on.
4126 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4128 _listFix: function(ev) {
4129 var testLi = null, par = null, preContent = false, range = null;
4131 if (this.browser.webkit) {
4132 if (ev.keyCode && (ev.keyCode == 13)) {
4133 if (this._hasParent(this._getSelectedElement(), 'li')) {
4134 var tar = this._hasParent(this._getSelectedElement(), 'li');
4135 if (tar.previousSibling) {
4136 if (tar.firstChild && (tar.firstChild.length == 1)) {
4137 this._selectNode(tar);
4144 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4145 testLi = this._getSelectedElement();
4146 if (this._hasParent(testLi, 'li')) {
4147 testLi = this._hasParent(testLi, 'li');
4148 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4149 par = this._hasParent(testLi, 'ul');
4151 par = this._hasParent(testLi, 'ol');
4153 if (this._isElement(par.previousSibling, 'li')) {
4154 par.removeChild(testLi);
4155 par.parentNode.insertBefore(testLi, par.nextSibling);
4156 if (this.browser.ie) {
4157 range = this._getDoc().body.createTextRange();
4158 range.moveToElementText(testLi);
4159 range.collapse(false);
4162 if (this.browser.webkit) {
4163 this._selectNode(testLi.firstChild);
4165 Event.stopEvent(ev);
4171 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4172 var preLi = this._getSelectedElement();
4173 if (this._hasParent(preLi, 'li')) {
4174 preContent = this._hasParent(preLi, 'li').innerHTML;
4176 if (this.browser.webkit) {
4177 this._getDoc().execCommand('inserttext', false, '\t');
4179 testLi = this._getSelectedElement();
4180 if (this._hasParent(testLi, 'li')) {
4181 par = this._hasParent(testLi, 'li');
4182 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4183 if (this.browser.webkit) {
4184 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4185 //Remove the span element that Safari puts in
4187 par.removeChild(span[0]);
4188 par.innerHTML = Lang.trim(par.innerHTML);
4189 //Put the HTML from the LI into this new LI
4191 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4193 par.innerHTML = '<span class="yui-non"> </span> ';
4198 par.innerHTML = preContent + ' ';
4200 par.innerHTML = ' ';
4204 par.parentNode.replaceChild(newUl, par);
4205 newUl.appendChild(par);
4206 if (this.browser.webkit) {
4207 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4208 if (!this.browser.webkit3) {
4209 par.parentNode.parentNode.style.display = 'list-item';
4210 setTimeout(function() {
4211 par.parentNode.parentNode.style.display = 'block';
4214 } else if (this.browser.ie) {
4215 range = this._getDoc().body.createTextRange();
4216 range.moveToElementText(par);
4217 range.collapse(false);
4220 this._selectNode(par);
4222 Event.stopEvent(ev);
4224 if (this.browser.webkit) {
4225 Event.stopEvent(ev);
4231 * @method nodeChange
4232 * @param {Boolean} force Optional paramenter to skip the threshold counter
4233 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4235 nodeChange: function(force) {
4238 if (this.get('nodeChangeDelay')) {
4239 this._nodeChangeDelayTimer = window.setTimeout(function() {
4240 NCself._nodeChangeDelayTimer = null;
4241 NCself._nodeChange.apply(NCself, arguments);
4249 * @method _nodeChange
4250 * @param {Boolean} force Optional paramenter to skip the threshold counter
4251 * @description Fired from nodeChange in a setTimeout.
4253 _nodeChange: function(force) {
4254 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4255 thisNodeChange = Math.round(new Date().getTime() / 1000),
4258 if (force === true) {
4259 this._lastNodeChange = 0;
4262 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4263 if (this._fixNodesTimer === null) {
4264 this._fixNodesTimer = window.setTimeout(function() {
4265 self._fixNodes.call(self);
4266 self._fixNodesTimer = null;
4270 this._lastNodeChange = thisNodeChange;
4271 if (this.currentEvent) {
4273 this._lastNodeChangeEvent = this.currentEvent.type;
4277 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4278 if (beforeNodeChange === false) {
4281 if (this.get('dompath')) {
4282 window.setTimeout(function() {
4283 self._writeDomPath.call(self);
4286 //Check to see if we are disabled before continuing
4287 if (!this.get('disabled')) {
4288 if (this.STOP_NODE_CHANGE) {
4289 //Reset this var for next action
4290 this.STOP_NODE_CHANGE = false;
4293 var sel = this._getSelection(),
4294 range = this._getRange(),
4295 el = this._getSelectedElement(),
4296 fn_button = this.toolbar.getButtonByValue('fontname'),
4297 fs_button = this.toolbar.getButtonByValue('fontsize'),
4298 undo_button = this.toolbar.getButtonByValue('undo'),
4299 redo_button = this.toolbar.getButtonByValue('redo');
4301 //Handle updating the toolbar with active buttons
4303 if (this._lastButton) {
4304 _ex[this._lastButton.id] = true;
4305 //this._lastButton = null;
4307 if (!this._isElement(el, 'body')) {
4309 _ex[fn_button.get('id')] = true;
4312 _ex[fs_button.get('id')] = true;
4316 delete _ex[redo_button.get('id')];
4318 this.toolbar.resetAllButtons(_ex);
4320 //Handle disabled buttons
4321 for (var d = 0; d < this._disabled.length; d++) {
4322 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4323 if (_button && _button.get) {
4324 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4327 if (!this._hasSelection() && !this.get('insert')) {
4328 switch (this._disabled[d]) {
4333 //No Selection - disable
4334 this.toolbar.disableButton(_button);
4337 if (!this._alwaysDisabled[this._disabled[d]]) {
4338 this.toolbar.enableButton(_button);
4341 if (!this._alwaysEnabled[this._disabled[d]]) {
4342 this.toolbar.deselectButton(_button);
4347 var path = this._getDomPath();
4348 var tag = null, cmd = null;
4349 for (var i = 0; i < path.length; i++) {
4350 tag = path[i].tagName.toLowerCase();
4351 if (path[i].getAttribute('tag')) {
4352 tag = path[i].getAttribute('tag').toLowerCase();
4354 cmd = this._tag2cmd[tag];
4355 if (cmd === undefined) {
4358 if (!Lang.isArray(cmd)) {
4362 //Bold and Italic styles
4363 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4364 cmd[cmd.length] = 'bold';
4366 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4367 cmd[cmd.length] = 'italic';
4369 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4370 cmd[cmd.length] = 'underline';
4372 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4373 cmd[cmd.length] = 'strikethrough';
4375 if (cmd.length > 0) {
4376 for (var j = 0; j < cmd.length; j++) {
4377 this.toolbar.selectButton(cmd[j]);
4378 this.toolbar.enableButton(cmd[j]);
4382 switch (path[i].style.textAlign.toLowerCase()) {
4387 var alignType = path[i].style.textAlign.toLowerCase();
4388 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4391 this.toolbar.selectButton('justify' + alignType);
4392 this.toolbar.enableButton('justify' + alignType);
4398 //Reset Font Family and Size to the inital configs
4400 var family = fn_button._configs.label._initialConfig.value;
4401 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4402 this._updateMenuChecked('fontname', family);
4406 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4409 var hd_button = this.toolbar.getButtonByValue('heading');
4411 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4412 this._updateMenuChecked('heading', 'none');
4414 var img_button = this.toolbar.getButtonByValue('insertimage');
4415 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4416 this.toolbar.disableButton(img_button);
4418 if (this._lastButton && this._lastButton.isSelected) {
4419 this.toolbar.deselectButton(this._lastButton.id);
4421 this._undoNodeChange();
4425 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4429 * @method _updateMenuChecked
4430 * @param {Object} button The command identifier of the button you want to check
4431 * @param {String} value The value of the menu item you want to check
4432 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4433 * @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.
4435 _updateMenuChecked: function(button, value, tbar) {
4437 tbar = this.toolbar;
4439 var _button = tbar.getButtonByValue(button);
4440 _button.checkValue(value);
4444 * @method _handleToolbarClick
4445 * @param {Event} ev The event that triggered the button click
4446 * @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.
4448 _handleToolbarClick: function(ev) {
4451 var cmd = ev.button.value;
4452 if (ev.button.menucmd) {
4454 cmd = ev.button.menucmd;
4456 this._lastButton = ev.button;
4457 if (this.STOP_EXEC_COMMAND) {
4458 this.STOP_EXEC_COMMAND = false;
4461 this.execCommand(cmd, value);
4462 if (!this.browser.webkit) {
4464 setTimeout(function() {
4465 Fself.focus.call(Fself);
4469 Event.stopEvent(ev);
4473 * @method _setupAfterElement
4474 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4476 _setupAfterElement: function() {
4477 if (!this.beforeElement) {
4478 this.beforeElement = document.createElement('h2');
4479 this.beforeElement.className = 'yui-editor-skipheader';
4480 this.beforeElement.tabIndex = '-1';
4481 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4482 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4484 if (!this.afterElement) {
4485 this.afterElement = document.createElement('h2');
4486 this.afterElement.className = 'yui-editor-skipheader';
4487 this.afterElement.tabIndex = '-1';
4488 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4489 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4494 * @method _disableEditor
4495 * @param {Boolean} disabled Pass true to disable, false to enable
4496 * @description Creates a mask to place over the Editor.
4498 _disableEditor: function(disabled) {
4499 var iframe, par, html, height;
4500 if (!this.get('disabled_iframe')) {
4501 iframe = this._createIframe();
4502 iframe.set('id', 'disabled_' + this.get('iframe').get('id'));
4503 iframe.setStyle('height', '100%');
4504 iframe.setStyle('display', 'none');
4505 iframe.setStyle('visibility', 'visible');
4506 this.set('disabled_iframe', iframe);
4507 par = this.get('iframe').get('parentNode');
4508 par.appendChild(iframe.get('element'));
4511 iframe = this.get('disabled_iframe');
4514 this._orgIframe = this.get('iframe');
4517 this.toolbar.set('disabled', true);
4520 html = this.getEditorHTML();
4521 height = this.get('iframe').get('offsetHeight');
4522 iframe.setStyle('visibility', '');
4523 iframe.setStyle('position', '');
4524 iframe.setStyle('top', '');
4525 iframe.setStyle('left', '');
4526 this._orgIframe.setStyle('visibility', 'hidden');
4527 this._orgIframe.setStyle('position', 'absolute');
4528 this._orgIframe.setStyle('top', '-99999px');
4529 this._orgIframe.setStyle('left', '-99999px');
4530 this.set('iframe', iframe);
4531 this._setInitialContent(true);
4534 this._mask = document.createElement('DIV');
4535 Dom.addClass(this._mask, 'yui-editor-masked');
4536 if (this.browser.ie) {
4537 this._mask.style.height = height + 'px';
4539 this.get('iframe').get('parentNode').appendChild(this._mask);
4541 this.on('editorContentReloaded', function() {
4542 this._getDoc().body._rteLoaded = false;
4543 this.setEditorHTML(html);
4544 iframe.setStyle('display', 'block');
4545 this.unsubscribeAll('editorContentReloaded');
4549 this._mask.parentNode.removeChild(this._mask);
4552 this.toolbar.set('disabled', false);
4554 iframe.setStyle('visibility', 'hidden');
4555 iframe.setStyle('position', 'absolute');
4556 iframe.setStyle('top', '-99999px');
4557 iframe.setStyle('left', '-99999px');
4558 this._orgIframe.setStyle('visibility', '');
4559 this._orgIframe.setStyle('position', '');
4560 this._orgIframe.setStyle('top', '');
4561 this._orgIframe.setStyle('left', '');
4562 this.set('iframe', this._orgIframe);
4566 window.setTimeout(function() {
4567 self.nodeChange.call(self);
4573 * @property SEP_DOMPATH
4574 * @description The value to place in between the Dom path items
4579 * @property STR_LEAVE_EDITOR
4580 * @description The accessibility string for the element after the iFrame
4583 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4585 * @property STR_BEFORE_EDITOR
4586 * @description The accessibility string for the element before the iFrame
4589 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>',
4591 * @property STR_TITLE
4592 * @description The Title of the HTML document that is created in the iFrame
4595 STR_TITLE: 'Rich Text Area.',
4597 * @property STR_IMAGE_HERE
4598 * @description The text to place in the URL textbox when using the blankimage.
4601 STR_IMAGE_HERE: 'Image URL Here',
4603 * @property STR_IMAGE_URL
4604 * @description The label string for Image URL
4607 STR_IMAGE_URL: 'Image URL',
4609 * @property STR_LINK_URL
4610 * @description The label string for the Link URL.
4613 STR_LINK_URL: 'Link URL',
4616 * @property STOP_EXEC_COMMAND
4617 * @description Set to true when you want the default execCommand function to not process anything
4620 STOP_EXEC_COMMAND: false,
4623 * @property STOP_NODE_CHANGE
4624 * @description Set to true when you want the default nodeChange function to not process anything
4627 STOP_NODE_CHANGE: false,
4630 * @property CLASS_NOEDIT
4631 * @description CSS class applied to elements that are not editable.
4634 CLASS_NOEDIT: 'yui-noedit',
4637 * @property CLASS_CONTAINER
4638 * @description Default CSS class to apply to the editors container element
4641 CLASS_CONTAINER: 'yui-editor-container',
4644 * @property CLASS_EDITABLE
4645 * @description Default CSS class to apply to the editors iframe element
4648 CLASS_EDITABLE: 'yui-editor-editable',
4651 * @property CLASS_EDITABLE_CONT
4652 * @description Default CSS class to apply to the editors iframe's parent element
4655 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4658 * @property CLASS_PREFIX
4659 * @description Default prefix for dynamically created class names
4662 CLASS_PREFIX: 'yui-editor',
4665 * @description Standard browser detection
4668 browser: function() {
4669 var br = YAHOO.env.ua;
4671 if (br.webkit >= 420) {
4672 br.webkit3 = br.webkit;
4676 if (br.webkit >= 530) {
4677 br.webkit4 = br.webkit;
4683 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4691 * @description The Editor class' initialization method
4693 init: function(p_oElement, p_oAttributes) {
4695 if (!this._defaultToolbar) {
4696 this._defaultToolbar = {
4698 titlebar: 'Text Editing Tools',
4701 { group: 'fontstyle', label: 'Font Name and Size',
4703 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4705 { text: 'Arial', checked: true },
4706 { text: 'Arial Black' },
4707 { text: 'Comic Sans MS' },
4708 { text: 'Courier New' },
4709 { text: 'Lucida Console' },
4711 { text: 'Times New Roman' },
4712 { text: 'Trebuchet MS' },
4716 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4719 { type: 'separator' },
4720 { group: 'textstyle', label: 'Font Style',
4722 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4723 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4724 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4725 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4726 { type: 'separator' },
4727 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4728 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4732 { type: 'separator' },
4733 { group: 'indentlist', label: 'Lists',
4735 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4736 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4739 { type: 'separator' },
4740 { group: 'insertitem', label: 'Insert Item',
4742 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4743 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4750 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4751 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4754 this.currentElement = [];
4755 this.on('contentReady', function() {
4756 this.DOMReady = true;
4762 * @method initAttributes
4763 * @description Initializes all of the configuration attributes used to create
4765 * @param {Object} attr Object literal specifying a set of
4766 * configuration attributes used to create the editor.
4768 initAttributes: function(attr) {
4769 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4773 * @config setDesignMode
4774 * @description Should the Editor set designMode on the document. Default: true.
4778 this.setAttributeConfig('setDesignMode', {
4779 value: ((attr.setDesignMode === false) ? false : true)
4782 * @config nodeChangeDelay
4783 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true.
4787 this.setAttributeConfig('nodeChangeDelay', {
4788 value: ((attr.nodeChangeDelay === false) ? false : true)
4792 * @description The max number of undo levels to store.
4796 this.setAttributeConfig('maxUndo', {
4798 value: attr.maxUndo || 30
4803 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4807 this.setAttributeConfig('ptags', {
4809 value: attr.ptags || false
4813 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4817 this.setAttributeConfig('insert', {
4819 value: attr.insert || false,
4820 method: function(insert) {
4828 var tmp = this._defaultToolbar.buttons;
4829 for (var i = 0; i < tmp.length; i++) {
4830 if (tmp[i].buttons) {
4831 for (var a = 0; a < tmp[i].buttons.length; a++) {
4832 if (tmp[i].buttons[a].value) {
4833 if (buttons[tmp[i].buttons[a].value]) {
4834 delete tmp[i].buttons[a].disabled;
4845 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4846 * We will create one and place it in this container. If no container is passed we will append to document.body.
4850 this.setAttributeConfig('container', {
4852 value: attr.container || false
4856 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4860 this.setAttributeConfig('plainText', {
4862 value: attr.plainText || false
4867 * @description Internal config for holding the iframe element.
4871 this.setAttributeConfig('iframe', {
4876 * @config disabled_iframe
4877 * @description Internal config for holding the iframe element used when disabling the Editor.
4881 this.setAttributeConfig('disabled_iframe', {
4886 * @depreciated - No longer used, should use this.get('element')
4888 * @description Internal config for holding the textarea element (replaced with element).
4892 this.setAttributeConfig('textarea', {
4897 * @config nodeChangeThreshold
4898 * @description The number of seconds that need to be in between nodeChange processing
4902 this.setAttributeConfig('nodeChangeThreshold', {
4903 value: attr.nodeChangeThreshold || 3,
4904 validator: YAHOO.lang.isNumber
4907 * @config allowNoEdit
4908 * @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.
4909 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4913 this.setAttributeConfig('allowNoEdit', {
4914 value: attr.allowNoEdit || false,
4915 validator: YAHOO.lang.isBoolean
4918 * @config limitCommands
4919 * @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.
4923 this.setAttributeConfig('limitCommands', {
4924 value: attr.limitCommands || false,
4925 validator: YAHOO.lang.isBoolean
4928 * @config element_cont
4929 * @description Internal config for the editors container
4933 this.setAttributeConfig('element_cont', {
4934 value: attr.element_cont
4938 * @config editor_wrapper
4939 * @description The outter wrapper for the entire editor.
4943 this.setAttributeConfig('editor_wrapper', {
4944 value: attr.editor_wrapper || null,
4949 * @description The height of the editor iframe container, not including the toolbar..
4950 * @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
4953 this.setAttributeConfig('height', {
4954 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4955 method: function(height) {
4956 if (this._rendered) {
4957 //We have been rendered, change the height
4958 if (this.get('animate')) {
4959 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4961 to: parseInt(height, 10)
4966 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4972 * @config autoHeight
4973 * @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.
4975 * @type Boolean || Number
4977 this.setAttributeConfig('autoHeight', {
4978 value: attr.autoHeight || false,
4979 method: function(a) {
4981 if (this.get('iframe')) {
4982 this.get('iframe').get('element').setAttribute('scrolling', 'no');
4984 this.on('afterNodeChange', this._handleAutoHeight, this, true);
4985 this.on('editorKeyDown', this._handleAutoHeight, this, true);
4986 this.on('editorKeyPress', this._handleAutoHeight, this, true);
4988 if (this.get('iframe')) {
4989 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4991 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4992 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4993 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4999 * @description The width of the editor container.
5000 * @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
5003 this.setAttributeConfig('width', {
5004 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
5005 method: function(width) {
5006 if (this._rendered) {
5007 //We have been rendered, change the width
5008 if (this.get('animate')) {
5009 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
5011 to: parseInt(width, 10)
5016 this.get('element_cont').setStyle('width', width);
5023 * @attribute blankimage
5024 * @description The URL for the image placeholder to put in when inserting an image.
5025 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
5028 this.setAttributeConfig('blankimage', {
5029 value: attr.blankimage || this._getBlankImage()
5033 * @description The Base CSS used to format the content of the editor
5034 * @default <code><pre>html {
5039 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
5043 text-decoration: underline;
5046 .warning-localfile {
5047 border-bottom: 1px dashed red !important;
5050 cursor: wait !important;
5052 img.selected { //Safari image selection
5053 border: 2px dotted #808080;
5056 cursor: pointer !important;
5062 this.setAttributeConfig('css', {
5063 value: attr.css || this._defaultCSS,
5068 * @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)
5069 * @default This HTML requires a few things if you are to override:
5070 <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>
5071 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
5076 <title>{TITLE}</title>
5077 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5088 <body onload="document.body._rteLoaded = true;">
5096 this.setAttributeConfig('html', {
5097 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>',
5102 * @attribute extracss
5103 * @description Extra user defined css to load after the default SimpleEditor CSS
5107 this.setAttributeConfig('extracss', {
5108 value: attr.extracss || '',
5113 * @attribute handleSubmit
5114 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
5115 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
5116 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
5120 this.setAttributeConfig('handleSubmit', {
5121 value: attr.handleSubmit || false,
5122 method: function(exec) {
5123 if (this.get('element').form) {
5124 if (!this._formButtons) {
5125 this._formButtons = [];
5128 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5129 var i = this.get('element').form.getElementsByTagName('input');
5130 for (var s = 0; s < i.length; s++) {
5131 var type = i[s].getAttribute('type');
5132 if (type && (type.toLowerCase() == 'submit')) {
5133 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5134 this._formButtons[this._formButtons.length] = i[s];
5138 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5139 if (this._formButtons) {
5140 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5147 * @attribute disabled
5148 * @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.
5149 All Toolbar buttons are also disabled so they cannot be used.
5154 this.setAttributeConfig('disabled', {
5156 method: function(disabled) {
5157 if (this._rendered) {
5158 this._disableEditor(disabled);
5164 * @description When save HTML is called, this element will be updated as well as the source of data.
5168 this.setAttributeConfig('saveEl', {
5169 value: this.get('element')
5172 * @config toolbar_cont
5173 * @description Internal config for the toolbars container
5177 this.setAttributeConfig('toolbar_cont', {
5182 * @attribute toolbar
5183 * @description The default toolbar config.
5186 this.setAttributeConfig('toolbar', {
5187 value: attr.toolbar || this._defaultToolbar,
5189 method: function(toolbar) {
5190 if (!toolbar.buttonType) {
5191 toolbar.buttonType = this._defaultToolbar.buttonType;
5193 this._defaultToolbar = toolbar;
5197 * @attribute animate
5198 * @description Should the editor animate window movements
5199 * @default false unless Animation is found, then true
5202 this.setAttributeConfig('animate', {
5203 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5204 validator: function(value) {
5206 if (!YAHOO.util.Anim) {
5214 * @description A reference to the panel we are using for windows.
5218 this.setAttributeConfig('panel', {
5221 validator: function(value) {
5223 if (!YAHOO.widget.Overlay) {
5230 * @attribute focusAtStart
5231 * @description Should we focus the window when the content is ready?
5235 this.setAttributeConfig('focusAtStart', {
5236 value: attr.focusAtStart || false,
5238 method: function(fs) {
5240 this.on('editorContentLoaded', function() {
5242 setTimeout(function() {
5243 self.focus.call(self);
5244 self.editorDirty = false;
5251 * @attribute dompath
5252 * @description Toggle the display of the current Dom path below the editor
5256 this.setAttributeConfig('dompath', {
5257 value: attr.dompath || false,
5258 method: function(dompath) {
5259 if (dompath && !this.dompath) {
5260 this.dompath = document.createElement('DIV');
5261 this.dompath.id = this.get('id') + '_dompath';
5262 Dom.addClass(this.dompath, 'dompath');
5263 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5264 if (this.get('iframe')) {
5265 this._writeDomPath();
5267 } else if (!dompath && this.dompath) {
5268 this.dompath.parentNode.removeChild(this.dompath);
5269 this.dompath = null;
5275 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5276 * @default "semantic"
5279 this.setAttributeConfig('markup', {
5280 value: attr.markup || 'semantic',
5281 validator: function(markup) {
5282 switch (markup.toLowerCase()) {
5293 * @attribute removeLineBreaks
5294 * @description Should we remove linebreaks and extra spaces on cleanup
5298 this.setAttributeConfig('removeLineBreaks', {
5299 value: attr.removeLineBreaks || false,
5300 validator: YAHOO.lang.isBoolean
5305 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5306 * @type {Boolean/String}
5308 this.setAttributeConfig('drag', {
5310 value: attr.drag || false
5315 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5316 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5319 this.setAttributeConfig('resize', {
5321 value: attr.resize || false
5325 * @config filterWord
5326 * @description Attempt to filter out MS Word HTML from the Editor's output.
5329 this.setAttributeConfig('filterWord', {
5330 value: attr.filterWord || false,
5331 validator: YAHOO.lang.isBoolean
5337 * @method _getBlankImage
5338 * @description Retrieves the full url of the image to use as the blank image.
5339 * @return {String} The URL to the blank image
5341 _getBlankImage: function() {
5342 if (!this.DOMReady) {
5343 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5347 if (!this._blankImageLoaded) {
5348 if (YAHOO.widget.EditorInfo.blankImage) {
5349 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5350 this._blankImageLoaded = true;
5352 var div = document.createElement('div');
5353 div.style.position = 'absolute';
5354 div.style.top = '-9999px';
5355 div.style.left = '-9999px';
5356 div.className = this.CLASS_PREFIX + '-blankimage';
5357 document.body.appendChild(div);
5358 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5359 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5361 img = img.replace('app:/', '');
5362 this.set('blankimage', img);
5363 this._blankImageLoaded = true;
5364 div.parentNode.removeChild(div);
5365 YAHOO.widget.EditorInfo.blankImage = img;
5368 img = this.get('blankimage');
5374 * @method _handleAutoHeight
5375 * @description Handles resizing the editor's height based on the content
5377 _handleAutoHeight: function() {
5378 var doc = this._getDoc(),
5380 docEl = doc.documentElement;
5382 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5383 var newHeight = body.scrollHeight;
5384 if (this.browser.webkit) {
5385 newHeight = docEl.scrollHeight;
5387 if (newHeight < parseInt(this.get('height'), 10)) {
5388 newHeight = parseInt(this.get('height'), 10);
5390 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5391 var anim = this.get('animate');
5392 this.set('animate', false);
5393 this.set('height', newHeight + 'px');
5394 this.set('animate', anim);
5395 if (this.browser.ie) {
5396 //Internet Explorer needs this
5397 this.get('iframe').setStyle('height', '99%');
5398 this.get('iframe').setStyle('zoom', '1');
5400 window.setTimeout(function() {
5401 self.get('iframe').setStyle('height', '100%');
5408 * @property _formButtons
5409 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5415 * @property _formButtonClicked
5416 * @description The form button that was clicked to submit the form.
5419 _formButtonClicked: null,
5422 * @method _handleFormButtonClick
5423 * @description The click listener assigned to each submit button in the Editor's parent form.
5424 * @param {Event} ev The click event
5426 _handleFormButtonClick: function(ev) {
5427 var tar = Event.getTarget(ev);
5428 this._formButtonClicked = tar;
5432 * @method _handleFormSubmit
5433 * @description Handles the form submission.
5434 * @param {Object} ev The Form Submit Event
5436 _handleFormSubmit: function(ev) {
5439 var form = this.get('element').form,
5440 tar = this._formButtonClicked || false;
5442 Event.removeListener(form, 'submit', this._handleFormSubmit);
5443 if (YAHOO.env.ua.ie) {
5444 //form.fireEvent("onsubmit");
5445 if (tar && !tar.disabled) {
5448 } else { // Gecko, Opera, and Safari
5449 if (tar && !tar.disabled) {
5452 var oEvent = document.createEvent("HTMLEvents");
5453 oEvent.initEvent("submit", true, true);
5454 form.dispatchEvent(oEvent);
5455 if (YAHOO.env.ua.webkit) {
5456 if (YAHOO.lang.isFunction(form.submit)) {
5462 //Removed this, not need since removing Safari 2.x
5463 //Event.stopEvent(ev);
5467 * @method _handleFontSize
5468 * @description Handles the font size button in the toolbar.
5469 * @param {Object} o Object returned from Toolbar's buttonClick Event
5471 _handleFontSize: function(o) {
5472 var button = this.toolbar.getButtonById(o.button.id);
5473 var value = button.get('label') + 'px';
5474 this.execCommand('fontsize', value);
5479 * @description Handles the colorpicker buttons in the toolbar.
5480 * @param {Object} o Object returned from Toolbar's buttonClick Event
5482 _handleColorPicker: function(o) {
5484 var value = '#' + o.color;
5485 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5486 this.execCommand(cmd, value);
5491 * @method _handleAlign
5492 * @description Handles the alignment buttons in the toolbar.
5493 * @param {Object} o Object returned from Toolbar's buttonClick Event
5495 _handleAlign: function(o) {
5497 for (var i = 0; i < o.button.menu.length; i++) {
5498 if (o.button.menu[i].value == o.button.value) {
5499 cmd = o.button.menu[i].value;
5502 var value = this._getSelection();
5504 this.execCommand(cmd, value);
5509 * @method _handleAfterNodeChange
5510 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5512 _handleAfterNodeChange: function() {
5513 var path = this._getDomPath(),
5518 fn_button = this.toolbar.getButtonByValue('fontname'),
5519 fs_button = this.toolbar.getButtonByValue('fontsize'),
5520 hd_button = this.toolbar.getButtonByValue('heading');
5522 for (var i = 0; i < path.length; i++) {
5525 var tag = elm.tagName.toLowerCase();
5528 if (elm.getAttribute('tag')) {
5529 tag = elm.getAttribute('tag');
5532 family = elm.getAttribute('face');
5533 if (Dom.getStyle(elm, 'font-family')) {
5534 family = Dom.getStyle(elm, 'font-family');
5536 family = family.replace(/'/g, '');
5539 if (tag.substring(0, 1) == 'h') {
5541 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5542 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5543 hd_button.set('label', hd_button._configs.menu.value[h].text);
5546 this._updateMenuChecked('heading', tag);
5552 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5553 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5555 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5559 family = fn_button._configs.label._initialConfig.value;
5561 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5562 if (fn_button.get('label') != familyLabel) {
5563 fn_button.set('label', familyLabel);
5564 this._updateMenuChecked('fontname', family);
5569 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5570 if ((fontsize === null) || isNaN(fontsize)) {
5571 fontsize = fs_button._configs.label._initialConfig.value;
5573 fs_button.set('label', ''+fontsize);
5576 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5577 this.toolbar.enableButton(fn_button);
5578 this.toolbar.enableButton(fs_button);
5579 this.toolbar.enableButton('forecolor');
5580 this.toolbar.enableButton('backcolor');
5582 if (this._isElement(elm, 'img')) {
5583 if (YAHOO.widget.Overlay) {
5584 this.toolbar.enableButton('createlink');
5587 if (this._hasParent(elm, 'blockquote')) {
5588 this.toolbar.selectButton('indent');
5589 this.toolbar.disableButton('indent');
5590 this.toolbar.enableButton('outdent');
5592 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5593 this.toolbar.disableButton('indent');
5595 this._lastButton = null;
5600 * @method _handleInsertImageClick
5601 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5603 _handleInsertImageClick: function() {
5604 if (this.get('limitCommands')) {
5605 if (!this.toolbar.getButtonByValue('insertimage')) {
5610 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5611 var _handleAEC = function() {
5612 var el = this.currentElement[0],
5615 el = this._getSelectedElement();
5618 if (el.getAttribute('src')) {
5619 src = el.getAttribute('src', 2);
5620 if (src.indexOf(this.get('blankimage')) != -1) {
5621 src = this.STR_IMAGE_HERE;
5625 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5626 if ((str !== '') && (str !== null)) {
5627 el.setAttribute('src', str);
5628 } else if (str === '') {
5629 el.parentNode.removeChild(el);
5630 this.currentElement = [];
5632 } else if ((str === null)) {
5633 src = el.getAttribute('src', 2);
5634 if (src.indexOf(this.get('blankimage')) != -1) {
5635 el.parentNode.removeChild(el);
5636 this.currentElement = [];
5641 this.toolbar.set('disabled', false);
5642 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5644 this.on('afterExecCommand', _handleAEC, this, true);
5648 * @method _handleInsertImageWindowClose
5649 * @description Handles the closing of the Image Properties Window.
5651 _handleInsertImageWindowClose: function() {
5656 * @method _isLocalFile
5657 * @param {String} url THe url/string to check
5658 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5660 _isLocalFile: function(url) {
5661 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5668 * @method _handleCreateLinkClick
5669 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5671 _handleCreateLinkClick: function() {
5672 if (this.get('limitCommands')) {
5673 if (!this.toolbar.getButtonByValue('createlink')) {
5678 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5680 var _handleAEC = function() {
5681 var el = this.currentElement[0],
5685 if (el.getAttribute('href', 2) !== null) {
5686 url = el.getAttribute('href', 2);
5689 var str = prompt(this.STR_LINK_URL + ': ', url);
5690 if ((str !== '') && (str !== null)) {
5692 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5693 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5694 //Found an @ sign, prefix with mailto:
5695 urlValue = 'mailto:' + urlValue;
5697 /* :// not found adding */
5698 if (urlValue.substring(0, 1) != '#') {
5699 //urlValue = 'http:/'+'/' + urlValue;
5703 el.setAttribute('href', urlValue);
5704 } else if (str !== null) {
5705 var _span = this._getDoc().createElement('span');
5706 _span.innerHTML = el.innerHTML;
5707 Dom.addClass(_span, 'yui-non');
5708 el.parentNode.replaceChild(_span, el);
5711 this.toolbar.set('disabled', false);
5712 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5714 this.on('afterExecCommand', _handleAEC, this);
5719 * @method _handleCreateLinkWindowClose
5720 * @description Handles the closing of the Link Properties Window.
5722 _handleCreateLinkWindowClose: function() {
5724 this.currentElement = [];
5728 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5730 render: function() {
5731 if (this._rendered) {
5734 if (!this.DOMReady) {
5735 this._queue[this._queue.length] = ['render', arguments];
5738 if (this.get('element')) {
5739 if (this.get('element').tagName) {
5740 this._textarea = true;
5741 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5742 this._textarea = false;
5750 this._rendered = true;
5752 window.setTimeout(function() {
5753 self._render.call(self);
5759 * @description Causes the toolbar and the editor to render and replace the textarea.
5761 _render: function() {
5763 this.set('textarea', this.get('element'));
5765 this.get('element_cont').setStyle('display', 'none');
5766 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5768 this.set('iframe', this._createIframe());
5770 window.setTimeout(function() {
5771 self._setInitialContent.call(self);
5774 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5776 if (this.get('disabled')) {
5777 this._disableEditor(true);
5780 var tbarConf = this.get('toolbar');
5781 //Create Toolbar instance
5782 if (tbarConf instanceof Toolbar) {
5783 this.toolbar = tbarConf;
5784 //Set the toolbar to disabled until content is loaded
5785 this.toolbar.set('disabled', true);
5787 //Set the toolbar to disabled until content is loaded
5788 tbarConf.disabled = true;
5789 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5792 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5795 this.toolbar.on('toolbarCollapsed', function() {
5796 if (this.currentWindow) {
5800 this.toolbar.on('toolbarExpanded', function() {
5801 if (this.currentWindow) {
5805 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5807 this.toolbar.on('colorPickerClicked', function(o) {
5808 this._handleColorPicker(o);
5809 return false; //Stop the buttonClick event
5812 this.toolbar.on('alignClick', this._handleAlign, this, true);
5813 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5814 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5815 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5816 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5817 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5820 //Replace Textarea with editable area
5821 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5824 this.setStyle('visibility', 'hidden');
5825 this.setStyle('position', 'absolute');
5826 this.setStyle('top', '-9999px');
5827 this.setStyle('left', '-9999px');
5828 this.get('element_cont').appendChild(this.get('element'));
5829 this.get('element_cont').setStyle('display', 'block');
5832 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5833 this.get('iframe').addClass(this.CLASS_EDITABLE);
5835 //Set height and width of editor container
5836 this.get('element_cont').setStyle('width', this.get('width'));
5837 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5839 this.get('iframe').setStyle('width', '100%'); //WIDTH
5840 this.get('iframe').setStyle('height', '100%');
5844 window.setTimeout(function() {
5845 self._setupAfterElement.call(self);
5847 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5850 * @method execCommand
5851 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5852 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5853 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5855 execCommand: function(action, value) {
5856 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5857 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5858 this.STOP_EXEC_COMMAND = false;
5861 this._lastCommand = action;
5862 this._setMarkupType(action);
5863 if (this.browser.ie) {
5864 this._getWindow().focus();
5868 if (this.get('limitCommands')) {
5869 if (!this.toolbar.getButtonByValue(action)) {
5874 this.editorDirty = true;
5876 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5877 var retValue = this['cmd_' + action.toLowerCase()](value);
5880 action = retValue[1];
5883 value = retValue[2];
5888 this._getDoc().execCommand(action, false, value);
5893 this.on('afterExecCommand', function() {
5894 this.unsubscribeAll('afterExecCommand');
5897 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5900 /* {{{ Command Overrides */
5904 * @param value Value passed from the execCommand method
5905 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5907 cmd_bold: function(value) {
5908 if (!this.browser.webkit) {
5909 var el = this._getSelectedElement();
5910 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5911 if (el.style.fontWeight == 'bold') {
5912 el.style.fontWeight = '';
5913 var b = this._getDoc().createElement('b'),
5914 par = el.parentNode;
5915 par.replaceChild(b, el);
5923 * @method cmd_italic
5924 * @param value Value passed from the execCommand method
5925 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5928 cmd_italic: function(value) {
5929 if (!this.browser.webkit) {
5930 var el = this._getSelectedElement();
5931 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5932 if (el.style.fontStyle == 'italic') {
5933 el.style.fontStyle = '';
5934 var i = this._getDoc().createElement('i'),
5935 par = el.parentNode;
5936 par.replaceChild(i, el);
5946 * @method cmd_underline
5947 * @param value Value passed from the execCommand method
5948 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5950 cmd_underline: function(value) {
5951 if (!this.browser.webkit) {
5952 var el = this._getSelectedElement();
5953 if (el && this._isElement(el, 'span')) {
5954 if (el.style.textDecoration == 'underline') {
5955 el.style.textDecoration = 'none';
5957 el.style.textDecoration = 'underline';
5965 * @method cmd_backcolor
5966 * @param value Value passed from the execCommand method
5967 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5969 cmd_backcolor: function(value) {
5971 el = this._getSelectedElement(),
5972 action = 'backcolor';
5974 if (this.browser.gecko || this.browser.opera) {
5975 this._setEditorStyle(true);
5976 action = 'hilitecolor';
5979 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5980 el.style.backgroundColor = value;
5981 this._selectNode(el);
5984 if (this.get('insert')) {
5985 el = this._createInsertElement({ backgroundColor: value });
5987 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
5988 this._selectNode(this.currentElement[0]);
5993 return [exec, action];
5996 * @method cmd_forecolor
5997 * @param value Value passed from the execCommand method
5998 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
6000 cmd_forecolor: function(value) {
6002 el = this._getSelectedElement();
6004 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6005 Dom.setStyle(el, 'color', value);
6006 this._selectNode(el);
6009 if (this.get('insert')) {
6010 el = this._createInsertElement({ color: value });
6012 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
6013 this._selectNode(this.currentElement[0]);
6020 * @method cmd_unlink
6021 * @param value Value passed from the execCommand method
6022 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
6024 cmd_unlink: function(value) {
6025 this._swapEl(this.currentElement[0], 'span', function(el) {
6026 el.className = 'yui-non';
6031 * @method cmd_createlink
6032 * @param value Value passed from the execCommand method
6033 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
6035 cmd_createlink: function(value) {
6036 var el = this._getSelectedElement(), _a = null;
6037 if (this._hasParent(el, 'a')) {
6038 this.currentElement[0] = this._hasParent(el, 'a');
6039 } else if (this._isElement(el, 'li')) {
6040 _a = this._getDoc().createElement('a');
6041 _a.innerHTML = el.innerHTML;
6044 this.currentElement[0] = _a;
6045 } else if (!this._isElement(el, 'a')) {
6046 this._createCurrentElement('a');
6047 _a = this._swapEl(this.currentElement[0], 'a');
6048 this.currentElement[0] = _a;
6050 this.currentElement[0] = el;
6055 * @method cmd_insertimage
6056 * @param value Value passed from the execCommand method
6057 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
6059 cmd_insertimage: function(value) {
6060 var exec = true, _img = null, action = 'insertimage',
6061 el = this._getSelectedElement();
6064 value = this.get('blankimage');
6068 * @knownissue Safari Cursor Position
6069 * @browser Safari 2.x
6070 * @description The issue here is that we have no way of knowing where the cursor position is
6071 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6074 if (this._isElement(el, 'img')) {
6075 this.currentElement[0] = el;
6078 if (this._getDoc().queryCommandEnabled(action)) {
6079 this._getDoc().execCommand(action, false, value);
6080 var imgs = this._getDoc().getElementsByTagName('img');
6081 for (var i = 0; i < imgs.length; i++) {
6082 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
6083 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
6084 this.currentElement[0] = imgs[i];
6089 if (el == this._getDoc().body) {
6090 _img = this._getDoc().createElement('img');
6091 _img.setAttribute('src', value);
6092 YAHOO.util.Dom.addClass(_img, 'yui-img');
6093 this._getDoc().body.appendChild(_img);
6095 this._createCurrentElement('img');
6096 _img = this._getDoc().createElement('img');
6097 _img.setAttribute('src', value);
6098 YAHOO.util.Dom.addClass(_img, 'yui-img');
6099 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
6101 this.currentElement[0] = _img;
6108 * @method cmd_inserthtml
6109 * @param value Value passed from the execCommand method
6110 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6112 cmd_inserthtml: function(value) {
6113 var exec = true, action = 'inserthtml', _span = null, _range = null;
6115 * @knownissue Safari cursor position
6116 * @browser Safari 2.x
6117 * @description The issue here is that we have no way of knowing where the cursor position is
6118 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6120 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6121 this._createCurrentElement('img');
6122 _span = this._getDoc().createElement('span');
6123 _span.innerHTML = value;
6124 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6126 } else if (this.browser.ie) {
6127 _range = this._getRange();
6129 _range.item(0).outerHTML = value;
6131 _range.pasteHTML(value);
6139 * @param tag The tag of the list you want to create (eg, ul or ol)
6140 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6142 cmd_list: function(tag) {
6143 var exec = true, list = null, li = 0, el = null, str = '',
6144 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6146 action = 'insertunorderedlist';
6149 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6150 * @browser Safari 2.x
6151 * The issue with this workaround is that when applied to a set of text
6152 * that has BR's in it, Safari may or may not pick up the individual items as
6153 * list items. This is fixed in WebKit (Safari 3)
6154 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6156 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6157 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) {
6158 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6159 el = selEl.parentNode;
6160 list = this._getDoc().createElement('span');
6161 YAHOO.util.Dom.addClass(list, 'yui-non');
6163 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div');
6164 for (li = 0; li < lis.length; li++) {
6165 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>';
6167 list.innerHTML = str;
6168 this.currentElement[0] = el;
6169 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6171 this._createCurrentElement(tag.toLowerCase());
6172 list = this._getDoc().createElement(tag);
6173 for (li = 0; li < this.currentElement.length; li++) {
6174 var newli = this._getDoc().createElement('li');
6175 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
6176 list.appendChild(newli);
6178 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6181 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'),
6182 items = list.firstChild.innerHTML.split(b_tag), i, item;
6183 if (items.length > 0) {
6184 list.innerHTML = '';
6185 for (i = 0; i < items.length; i++) {
6186 item = this._getDoc().createElement('li');
6187 item.innerHTML = items[i];
6188 list.appendChild(item);
6192 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6193 this.currentElement[0] = list;
6194 var _h = this.currentElement[0].firstChild;
6195 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6196 if (this.browser.webkit) {
6197 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6202 el = this._getSelectedElement();
6203 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..
6204 if (this.browser.ie) {
6205 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6206 el = el.getElementsByTagName('li')[0];
6209 var lis2 = el.parentNode.getElementsByTagName('li');
6210 for (var j = 0; j < lis2.length; j++) {
6211 str += lis2[j].innerHTML + '<br>';
6213 var newEl = this._getDoc().createElement('span');
6214 newEl.innerHTML = str;
6215 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6218 this._getDoc().execCommand(action, '', el.parentNode);
6223 if (this.browser.opera) {
6225 window.setTimeout(function() {
6226 var liso = self._getDoc().getElementsByTagName('li');
6227 for (var i = 0; i < liso.length; i++) {
6228 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6229 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6234 if (this.browser.ie && exec) {
6236 if (this._getRange().html) {
6237 html = '<li>' + this._getRange().html+ '</li>';
6239 var t = this._getRange().text.split('\n');
6242 for (var ie = 0; ie < t.length; ie++) {
6243 html += '<li>' + t[ie] + '</li>';
6246 var txt = this._getRange().text;
6248 html = '<li id="new_list_item">' + txt + '</li>';
6250 html = '<li>' + txt + '</li>';
6254 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6255 var new_item = this._getDoc().getElementById('new_list_item');
6257 var range = this._getDoc().body.createTextRange();
6258 range.moveToElementText(new_item);
6259 range.collapse(false);
6269 * @method cmd_insertorderedlist
6270 * @param value Value passed from the execCommand method
6271 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6273 cmd_insertorderedlist: function(value) {
6274 return [this.cmd_list('ol')];
6277 * @method cmd_insertunorderedlist
6278 * @param value Value passed from the execCommand method
6279 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6281 cmd_insertunorderedlist: function(value) {
6282 return [this.cmd_list('ul')];
6285 * @method cmd_fontname
6286 * @param value Value passed from the execCommand method
6287 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6289 cmd_fontname: function(value) {
6291 selEl = this._getSelectedElement();
6293 this.currentFont = value;
6294 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6295 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6297 } else if (this.get('insert') && !this._hasSelection()) {
6298 var el = this._createInsertElement({ fontFamily: value });
6304 * @method cmd_fontsize
6305 * @param value Value passed from the execCommand method
6306 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6308 cmd_fontsize: function(value) {
6309 var el = null, go = true;
6310 el = this._getSelectedElement();
6311 if (this.browser.webkit) {
6312 if (this.currentElement[0]) {
6313 if (el == this.currentElement[0]) {
6315 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6316 this._selectNode(el);
6317 this.currentElement[0] = el;
6322 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6323 el = this._getSelectedElement();
6324 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6325 if (this.get('insert') && this.browser.ie) {
6326 var r = this._getRange();
6330 this._selectNode(el);
6332 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6333 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6335 if (this.get('insert') && !this._hasSelection()) {
6336 el = this._createInsertElement({ fontSize: value });
6337 this.currentElement[0] = el;
6338 this._selectNode(this.currentElement[0]);
6340 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6341 this._selectNode(this.currentElement[0]);
6351 * @param {HTMLElement} el The element to swap with
6352 * @param {String} tagName The tagname of the element that you wish to create
6353 * @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.
6354 * @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.
6356 _swapEl: function(el, tagName, callback) {
6357 var _el = this._getDoc().createElement(tagName);
6359 _el.innerHTML = el.innerHTML;
6361 if (typeof callback == 'function') {
6362 callback.call(this, _el);
6365 el.parentNode.replaceChild(_el, el);
6371 * @method _createInsertElement
6372 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6373 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6374 * @return {HTMLElement}
6376 _createInsertElement: function(css) {
6377 this._createCurrentElement('span', css);
6378 var el = this.currentElement[0];
6379 if (this.browser.webkit) {
6380 //Little Safari Hackery here..
6381 el.innerHTML = '<span class="yui-non"> </span>';
6383 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6384 } else if (this.browser.ie || this.browser.opera) {
6385 el.innerHTML = ' ';
6388 this._selectNode(el, true);
6393 * @method _createCurrentElement
6394 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6395 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6396 * @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.
6397 * 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
6398 * <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.
6400 _createCurrentElement: function(tagName, tagStyle) {
6401 tagName = ((tagName) ? tagName : 'a');
6404 _doc = this._getDoc();
6406 if (this.currentFont) {
6410 tagStyle.fontFamily = this.currentFont;
6411 this.currentFont = null;
6413 this.currentElement = [];
6415 var _elCreate = function(tagName, tagStyle) {
6417 tagName = ((tagName) ? tagName : 'span');
6418 tagName = tagName.toLowerCase();
6426 el = _doc.createElement(tagName);
6429 el = _doc.createElement(tagName);
6430 if (tagName === 'span') {
6431 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6432 YAHOO.util.Dom.addClass(el, 'yui-tag');
6433 el.setAttribute('tag', tagName);
6436 for (var k in tagStyle) {
6437 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6438 el.style[k] = tagStyle[k];
6446 if (!this._hasSelection()) {
6447 if (this._getDoc().queryCommandEnabled('insertimage')) {
6448 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6449 var imgs = this._getDoc().getElementsByTagName('img');
6450 for (var j = 0; j < imgs.length; j++) {
6451 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6452 el = _elCreate(tagName, tagStyle);
6453 imgs[j].parentNode.replaceChild(el, imgs[j]);
6454 this.currentElement[this.currentElement.length] = el;
6458 if (this.currentEvent) {
6459 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6462 tar = this._getDoc().body;
6467 * @knownissue Safari Cursor Position
6468 * @browser Safari 2.x
6469 * @description The issue here is that we have no way of knowing where the cursor position is
6470 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6472 el = _elCreate(tagName, tagStyle);
6473 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6474 if (this._isElement(tar, 'html')) {
6475 tar = this._getDoc().body;
6477 tar.appendChild(el);
6478 } else if (tar.nextSibling) {
6479 tar.parentNode.insertBefore(el, tar.nextSibling);
6481 tar.parentNode.appendChild(el);
6483 //this.currentElement = el;
6484 this.currentElement[this.currentElement.length] = el;
6485 this.currentEvent = null;
6486 if (this.browser.webkit) {
6487 //Force Safari to focus the new element
6488 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6489 if (this.browser.webkit3) {
6490 this._getSelection().collapseToStart();
6492 this._getSelection().collapse(true);
6497 //Force CSS Styling for this action...
6498 this._setEditorStyle(true);
6499 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6500 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6502 if (!this._isElement(this._getSelectedElement(), 'body')) {
6503 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6504 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6506 for (var _els = 0; _els < __els.length; _els++) {
6507 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6508 for (var e = 0; e < _tmp1.length; e++) {
6509 _tmp[_tmp.length] = _tmp1[e];
6514 for (var i = 0; i < _tmp.length; i++) {
6515 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6516 if (tagName !== 'span') {
6517 el = _elCreate(tagName, tagStyle);
6519 el = _elCreate(_tmp[i].tagName, tagStyle);
6521 el.innerHTML = _tmp[i].innerHTML;
6522 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6523 var fc = _tmp[i].getElementsByTagName('li')[0];
6524 _tmp[i].style.fontFamily = 'inherit';
6525 fc.style.fontFamily = 'inherit';
6526 el.innerHTML = fc.innerHTML;
6529 this.currentElement[this.currentElement.length] = el;
6530 } else if (this._isElement(_tmp[i], 'li')) {
6531 _tmp[i].innerHTML = '';
6532 _tmp[i].appendChild(el);
6533 _tmp[i].style.fontFamily = 'inherit';
6534 this.currentElement[this.currentElement.length] = el;
6536 if (_tmp[i].parentNode) {
6537 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6538 this.currentElement[this.currentElement.length] = el;
6539 this.currentEvent = null;
6540 if (this.browser.webkit) {
6541 //Force Safari to focus the new element
6542 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6543 if (this.browser.webkit3) {
6544 this._getSelection().collapseToStart();
6546 this._getSelection().collapse(true);
6549 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6550 this._getSelection().empty();
6552 if (this.browser.gecko) {
6553 this._getSelection().collapseToStart();
6559 var len = this.currentElement.length;
6560 for (var o = 0; o < len; o++) {
6561 if ((o + 1) != len) { //Skip the last one in the list
6562 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6563 if (this._isElement(this.currentElement[o], 'br')) {
6564 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6573 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6576 saveHTML: function() {
6577 var html = this.cleanHTML();
6578 if (this._textarea) {
6579 this.get('element').value = html;
6581 this.get('element').innerHTML = html;
6583 if (this.get('saveEl') !== this.get('element')) {
6584 var out = this.get('saveEl');
6585 if (Lang.isString(out)) {
6589 if (out.tagName.toLowerCase() === 'textarea') {
6592 out.innerHTML = html;
6599 * @method setEditorHTML
6600 * @param {String} incomingHTML The html content to load into the editor
6601 * @description Loads HTML into the editors body
6603 setEditorHTML: function(incomingHTML) {
6604 var html = this._cleanIncomingHTML(incomingHTML);
6605 html = html.replace(/RIGHT_BRACKET/gi, '{');
6606 html = html.replace(/LEFT_BRACKET/gi, '}');
6607 this._getDoc().body.innerHTML = html;
6611 * @method getEditorHTML
6612 * @description Gets the unprocessed/unfiltered HTML from the editor
6614 getEditorHTML: function() {
6616 var b = this._getDoc().body;
6620 return this._getDoc().body.innerHTML;
6627 * @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.
6630 if (this.browser.gecko) {
6631 this._setDesignMode('on');
6634 if (this.browser.webkit) {
6636 window.setTimeout(function() {
6637 self._setInitialContent.call(self);
6640 //Adding this will close all other Editor window's when showing this one.
6641 if (this.currentWindow) {
6644 //Put the iframe back in place
6645 this.get('iframe').setStyle('position', 'static');
6646 this.get('iframe').setStyle('left', '');
6650 * @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.
6653 //Adding this will close all other Editor window's.
6654 if (this.currentWindow) {
6657 if (this._fixNodesTimer) {
6658 clearTimeout(this._fixNodesTimer);
6659 this._fixNodesTimer = null;
6661 if (this._nodeChangeTimer) {
6662 clearTimeout(this._nodeChangeTimer);
6663 this._nodeChangeTimer = null;
6665 this._lastNodeChange = 0;
6666 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6667 this.get('iframe').setStyle('position', 'absolute');
6668 this.get('iframe').setStyle('left', '-9999px');
6671 * @method _cleanIncomingHTML
6672 * @param {String} html The unfiltered HTML
6673 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6674 * @return {String} The filtered HTML
6676 _cleanIncomingHTML: function(html) {
6677 html = html.replace(/{/gi, 'RIGHT_BRACKET');
6678 html = html.replace(/}/gi, 'LEFT_BRACKET');
6680 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6681 html = html.replace(/<\/strong>/gi, '</b>');
6683 //replace embed before em check
6684 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6685 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6687 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6688 html = html.replace(/<\/em>/gi, '</i>');
6689 html = html.replace(/_moz_dirty=""/gi, '');
6691 //Put embed tags back in..
6692 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6693 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6694 if (this.get('plainText')) {
6695 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6696 html = html.replace(/ /gi, ' '); //Replace all double spaces
6697 html = html.replace(/\t/gi, ' '); //Replace all tabs
6699 //Removing Script Tags from the Editor
6700 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6701 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6702 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6703 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6704 //Replace the line feeds
6705 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6707 //Remove Bad HTML elements (used to be script nodes)
6708 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6709 //Replace the lines feeds
6710 html = html.replace(/<YUI_LF>/g, '\n');
6715 * @param {String} html The unfiltered HTML
6716 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6717 * @return {String} The filtered HTML
6719 cleanHTML: function(html) {
6720 //Start Filtering Output
6723 html = this.getEditorHTML();
6725 var markup = this.get('markup');
6726 //Make some backups...
6727 html = this.pre_filter_linebreaks(html, markup);
6730 html = this.filter_msword(html);
6732 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6733 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6735 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6736 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6738 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6739 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6740 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6741 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6743 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6744 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6746 //Convert b and i tags to strong and em tags
6747 if ((markup == 'semantic') || (markup == 'xhtml')) {
6748 //html = html.replace(/<i(\s+[^>]*)?>/gi, "<em$1>");
6749 html = html.replace(/<i([^>]*)>/gi, "<em$1>");
6750 html = html.replace(/<\/i>/gi, '</em>');
6751 //html = html.replace(/<b(\s+[^>]*)?>/gi, "<strong$1>");
6752 html = html.replace(/<b([^>]*)>/gi, "<strong$1>");
6753 html = html.replace(/<\/b>/gi, '</strong>');
6756 html = html.replace(/_moz_dirty=""/gi, '');
6758 //normalize strikethrough
6759 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6760 html = html.replace(/\/strike>/gi, '/span>');
6764 if (this.browser.ie) {
6765 html = html.replace(/text-decoration/gi, 'text-decoration');
6766 html = html.replace(/font-weight/gi, 'font-weight');
6767 html = html.replace(/_width="([^>]*)"/gi, '');
6768 html = html.replace(/_height="([^>]*)"/gi, '');
6769 //Cleanup Image URL's
6770 var url = this._baseHREF.replace(/\//gi, '\\/'),
6771 re = new RegExp('src="' + url, 'gi');
6772 html = html.replace(re, 'src="');
6774 html = html.replace(/<font/gi, '<font');
6775 html = html.replace(/<\/font>/gi, '</font>');
6776 html = html.replace(/<span/gi, '<span');
6777 html = html.replace(/<\/span>/gi, '</span>');
6778 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6779 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6780 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6781 if (this.browser.webkit) {
6782 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6783 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6785 html = html.replace(/\/u>/gi, '/span>');
6786 if (markup == 'css') {
6787 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6788 html = html.replace(/<\/em>/gi, '</i>');
6789 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6790 html = html.replace(/<\/strong>/gi, '</b>');
6791 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6792 html = html.replace(/\/b>/gi, '/span>');
6793 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6794 html = html.replace(/\/i>/gi, '/span>');
6796 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6798 html = html.replace(/<u/gi, '<u');
6799 html = html.replace(/\/u>/gi, '/u>');
6801 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6802 html = html.replace(/\/ol>/gi, '/ol>');
6803 html = html.replace(/<li/gi, '<li');
6804 html = html.replace(/\/li>/gi, '/li>');
6805 html = this.filter_safari(html);
6807 html = this.filter_internals(html);
6809 html = this.filter_all_rgb(html);
6811 //Replace our backups with the real thing
6812 html = this.post_filter_linebreaks(html, markup);
6814 if (markup == 'xhtml') {
6815 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6816 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6818 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6819 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6821 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6822 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6824 html = this.filter_invalid_lists(html);
6826 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6827 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6829 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6830 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6832 //This should fix &'s in URL's
6833 html = html.replace(/ & /gi, ' YUI_AMP ');
6834 html = html.replace(/ &/gi, ' YUI_AMP_F ');
6835 html = html.replace(/& /gi, ' YUI_AMP_R ');
6836 html = html.replace(/&/gi, '&');
6837 html = html.replace(/ YUI_AMP /gi, ' & ');
6838 html = html.replace(/ YUI_AMP_F /gi, ' &');
6839 html = html.replace(/ YUI_AMP_R /gi, '& ');
6841 //Trim the output, removing whitespace from the beginning and end
6842 html = YAHOO.lang.trim(html);
6844 if (this.get('removeLineBreaks')) {
6845 html = html.replace(/\n/g, '').replace(/\r/g, '');
6846 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6849 for (var v in this.invalidHTML) {
6850 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6851 if (Lang.isObject(v) && v.keepContents) {
6852 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6854 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6859 /* LATER -- Add DOM manipulation
6861 var frag = document.createDocumentFragment();
6862 frag.innerHTML = html;
6864 var ps = frag.getElementsByTagName('p'),
6866 for (var i = 0; i < len; i++) {
6867 var ps2 = ps[i].getElementsByTagName('p');
6873 html = frag.innerHTML;
6877 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6882 * @method filter_msword
6883 * @param String html The HTML string to filter
6884 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6886 filter_msword: function(html) {
6887 if (!this.get('filterWord')) {
6890 //Remove the ms o: tags
6891 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6892 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6894 //Remove the ms w: tags
6895 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6897 //Remove mso-? styles.
6898 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6900 //Remove more bogus MS styles.
6901 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6902 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6903 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6904 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6905 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6906 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6907 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6908 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6910 //Remove XML declarations
6911 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6914 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6916 //Remove language tags
6917 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6919 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6920 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6921 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
6926 * @method filter_invalid_lists
6927 * @param String html The HTML string to filter
6928 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6930 filter_invalid_lists: function(html) {
6931 html = html.replace(/<\/li>\n/gi, '</li>');
6933 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6934 html = html.replace(/<\/ol>/gi, '</ol></li>');
6935 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
6937 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6938 html = html.replace(/<\/ul>/gi, '</ul></li>');
6939 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
6941 html = html.replace(/<\/li>/gi, "</li>");
6942 html = html.replace(/<\/ol>/gi, "</ol>");
6943 html = html.replace(/<ol>/gi, "<ol>");
6944 html = html.replace(/<ul>/gi, "<ul>");
6948 * @method filter_safari
6949 * @param String html The HTML string to filter
6950 * @description Filters strings specific to Safari
6953 filter_safari: function(html) {
6954 if (this.browser.webkit) {
6955 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6956 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
6957 html = html.replace(/Apple-style-span/gi, '');
6958 html = html.replace(/style="line-height: normal;"/gi, '');
6959 html = html.replace(/yui-wk-div/gi, '');
6960 html = html.replace(/yui-wk-p/gi, '');
6964 html = html.replace(/<li><\/li>/gi, '');
6965 html = html.replace(/<li> <\/li>/gi, '');
6966 html = html.replace(/<li> <\/li>/gi, '');
6967 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6968 if (this.get('ptags')) {
6969 html = html.replace(/<div([^>]*)>/g, '<p$1>');
6970 html = html.replace(/<\/div>/gi, '</p>');
6972 //html = html.replace(/<div>/gi, '<br>');
6973 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>');
6974 html = html.replace(/<\/div>/gi, '');
6980 * @method filter_internals
6981 * @param String html The HTML string to filter
6982 * @description Filters internal RTE strings and bogus attrs we don't want
6985 filter_internals: function(html) {
6986 html = html.replace(/\r/g, '');
6987 //Fix stuff we don't want
6988 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6990 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6992 html = html.replace(/yui-tag-span/gi, '');
6993 html = html.replace(/yui-tag/gi, '');
6994 html = html.replace(/yui-non/gi, '');
6995 html = html.replace(/yui-img/gi, '');
6996 html = html.replace(/ tag="span"/gi, '');
6997 html = html.replace(/ class=""/gi, '');
6998 html = html.replace(/ style=""/gi, '');
6999 html = html.replace(/ class=" "/gi, '');
7000 html = html.replace(/ class=" "/gi, '');
7001 html = html.replace(/ target=""/gi, '');
7002 html = html.replace(/ title=""/gi, '');
7004 if (this.browser.ie) {
7005 html = html.replace(/ class= /gi, '');
7006 html = html.replace(/ class= >/gi, '');
7012 * @method filter_all_rgb
7013 * @param String str The HTML string to filter
7014 * @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"
7017 filter_all_rgb: function(str) {
7018 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
7019 var arr = str.match(exp);
7020 if (Lang.isArray(arr)) {
7021 for (var i = 0; i < arr.length; i++) {
7022 var color = this.filter_rgb(arr[i]);
7023 str = str.replace(arr[i].toString(), color);
7030 * @method filter_rgb
7031 * @param String css The CSS string containing rgb(#,#,#);
7032 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
7035 filter_rgb: function(css) {
7036 if (css.toLowerCase().indexOf('rgb') != -1) {
7037 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
7038 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
7040 if (rgb.length == 5) {
7041 var r = parseInt(rgb[1], 10).toString(16);
7042 var g = parseInt(rgb[2], 10).toString(16);
7043 var b = parseInt(rgb[3], 10).toString(16);
7045 r = r.length == 1 ? '0' + r : r;
7046 g = g.length == 1 ? '0' + g : g;
7047 b = b.length == 1 ? '0' + b : b;
7049 css = "#" + r + g + b;
7055 * @method pre_filter_linebreaks
7056 * @param String html The HTML to filter
7057 * @param String markup The markup type to filter to
7058 * @description HTML Pre Filter
7061 pre_filter_linebreaks: function(html, markup) {
7062 if (this.browser.webkit) {
7063 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
7064 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
7066 html = html.replace(/<br>/gi, '<YUI_BR>');
7067 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
7068 html = html.replace(/<br\/>/gi, '<YUI_BR>');
7069 html = html.replace(/<br \/>/gi, '<YUI_BR>');
7070 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
7071 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
7072 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
7073 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
7075 html = html.replace(/<YUI_BR>$/, '');
7077 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
7078 if (this.browser.ie) {
7079 html = html.replace(/ /g, '\t');
7084 * @method post_filter_linebreaks
7085 * @param String html The HTML to filter
7086 * @param String markup The markup type to filter to
7087 * @description HTML Pre Filter
7090 post_filter_linebreaks: function(html, markup) {
7091 if (markup == 'xhtml') {
7092 html = html.replace(/<YUI_BR>/g, '<br />');
7094 html = html.replace(/<YUI_BR>/g, '<br>');
7099 * @method clearEditorDoc
7100 * @description Clear the doc of the Editor
7102 clearEditorDoc: function() {
7103 this._getDoc().body.innerHTML = ' ';
7106 * @method openWindow
7107 * @description Override Method for Advanced Editor
7109 openWindow: function(win) {
7112 * @method moveWindow
7113 * @description Override Method for Advanced Editor
7115 moveWindow: function() {
7119 * @method _closeWindow
7120 * @description Override Method for Advanced Editor
7122 _closeWindow: function() {
7125 * @method closeWindow
7126 * @description Override Method for Advanced Editor
7128 closeWindow: function() {
7129 //this.unsubscribeAll('afterExecCommand');
7130 this.toolbar.resetAllButtons();
7135 * @description Destroys the editor, all of it's elements and objects.
7138 destroy: function() {
7139 if (this._nodeChangeDelayTimer) {
7140 clearTimeout(this._nodeChangeDelayTimer);
7145 this.resize.destroy();
7150 if (this.get('panel')) {
7151 this.get('panel').destroy();
7154 this.toolbar.destroy();
7155 this.setStyle('visibility', 'visible');
7156 this.setStyle('position', 'static');
7157 this.setStyle('top', '');
7158 this.setStyle('left', '');
7159 var textArea = this.get('element');
7160 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7161 this.get('element_cont').get('element').innerHTML = '';
7162 this.set('handleSubmit', false); //Remove the submit handler
7167 * @description Returns a string representing the editor.
7170 toString: function() {
7171 var str = 'SimpleEditor';
7172 if (this.get && this.get('element_cont')) {
7173 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7180 * @event toolbarLoaded
7181 * @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.
7182 * @type YAHOO.util.CustomEvent
7186 * @description Event is fired after the cleanHTML method is called.
7187 * @type YAHOO.util.CustomEvent
7190 * @event afterRender
7191 * @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.
7192 * @type YAHOO.util.CustomEvent
7195 * @event editorContentLoaded
7196 * @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.
7197 * @type YAHOO.util.CustomEvent
7200 * @event beforeNodeChange
7201 * @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.
7202 * @type YAHOO.util.CustomEvent
7205 * @event afterNodeChange
7206 * @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.
7207 * @type YAHOO.util.CustomEvent
7210 * @event beforeExecCommand
7211 * @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.
7212 * @type YAHOO.util.CustomEvent
7215 * @event afterExecCommand
7216 * @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.
7217 * @type YAHOO.util.CustomEvent
7220 * @event editorMouseUp
7221 * @param {Event} ev The DOM Event that occured
7222 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7223 * @type YAHOO.util.CustomEvent
7226 * @event editorMouseDown
7227 * @param {Event} ev The DOM Event that occured
7228 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7229 * @type YAHOO.util.CustomEvent
7232 * @event editorDoubleClick
7233 * @param {Event} ev The DOM Event that occured
7234 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7235 * @type YAHOO.util.CustomEvent
7238 * @event editorClick
7239 * @param {Event} ev The DOM Event that occured
7240 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7241 * @type YAHOO.util.CustomEvent
7244 * @event editorKeyUp
7245 * @param {Event} ev The DOM Event that occured
7246 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7247 * @type YAHOO.util.CustomEvent
7250 * @event editorKeyPress
7251 * @param {Event} ev The DOM Event that occured
7252 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7253 * @type YAHOO.util.CustomEvent
7256 * @event editorKeyDown
7257 * @param {Event} ev The DOM Event that occured
7258 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7259 * @type YAHOO.util.CustomEvent
7262 * @event beforeEditorMouseUp
7263 * @param {Event} ev The DOM Event that occured
7264 * @description Fires before editor event, returning false will stop the internal processing.
7265 * @type YAHOO.util.CustomEvent
7268 * @event beforeEditorMouseDown
7269 * @param {Event} ev The DOM Event that occured
7270 * @description Fires before editor event, returning false will stop the internal processing.
7271 * @type YAHOO.util.CustomEvent
7274 * @event beforeEditorDoubleClick
7275 * @param {Event} ev The DOM Event that occured
7276 * @description Fires before editor event, returning false will stop the internal processing.
7277 * @type YAHOO.util.CustomEvent
7280 * @event beforeEditorClick
7281 * @param {Event} ev The DOM Event that occured
7282 * @description Fires before editor event, returning false will stop the internal processing.
7283 * @type YAHOO.util.CustomEvent
7286 * @event beforeEditorKeyUp
7287 * @param {Event} ev The DOM Event that occured
7288 * @description Fires before editor event, returning false will stop the internal processing.
7289 * @type YAHOO.util.CustomEvent
7292 * @event beforeEditorKeyPress
7293 * @param {Event} ev The DOM Event that occured
7294 * @description Fires before editor event, returning false will stop the internal processing.
7295 * @type YAHOO.util.CustomEvent
7298 * @event beforeEditorKeyDown
7299 * @param {Event} ev The DOM Event that occured
7300 * @description Fires before editor event, returning false will stop the internal processing.
7301 * @type YAHOO.util.CustomEvent
7305 * @event editorWindowFocus
7306 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7307 * @type YAHOO.util.CustomEvent
7310 * @event editorWindowBlur
7311 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7312 * @type YAHOO.util.CustomEvent
7317 * @description Singleton object used to track the open window objects and panels across the various open editors
7321 YAHOO.widget.EditorInfo = {
7324 * @property _instances
7325 * @description A reference to all editors on the page.
7331 * @property blankImage
7332 * @description A reference to the blankImage url
7339 * @description A reference to the currently open window object in any editor on the page.
7340 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7346 * @description A reference to the currently open panel in any editor on the page.
7347 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7351 * @method getEditorById
7352 * @description Returns a reference to the Editor object associated with the given textarea
7353 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7354 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7356 getEditorById: function(id) {
7357 if (!YAHOO.lang.isString(id)) {
7358 //Not a string, assume a node Reference
7361 if (this._instances[id]) {
7362 return this._instances[id];
7368 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved.
7369 * @param {HTMLElement} form The form to check if this Editor instance belongs to
7371 saveAll: function(form) {
7372 var i, e, items = YAHOO.widget.EditorInfo._instances;
7375 if (Lang.hasOwnProperty(items, i)) {
7377 if (e.get('element').form && (e.get('element').form == form)) {
7384 if (Lang.hasOwnProperty(items, i)) {
7385 items[i].saveHTML();
7392 * @description Returns a string representing the EditorInfo.
7395 toString: function() {
7397 for (var i in this._instances) {
7398 if (Lang.hasOwnProperty(this._instances, i)) {
7402 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7412 * @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>
7413 * @namespace YAHOO.widget
7414 * @requires yahoo, dom, element, event, container_core, simpleeditor
7415 * @optional dragdrop, animation, menu, button, resize
7419 var Dom = YAHOO.util.Dom,
7420 Event = YAHOO.util.Event,
7422 Toolbar = YAHOO.widget.Toolbar;
7425 * 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.
7428 * @extends YAHOO.widget.SimpleEditor
7429 * @param {String/HTMLElement} el The textarea element to turn into an editor.
7430 * @param {Object} attrs Object liternal containing configuration parameters.
7433 YAHOO.widget.Editor = function(el, attrs) {
7434 YAHOO.widget.Editor.superclass.constructor.call(this, el, attrs);
7437 YAHOO.extend(YAHOO.widget.Editor, YAHOO.widget.SimpleEditor, {
7440 * @property _undoCache
7441 * @description An Array hash of the Undo Levels.
7447 * @property _undoLevel
7448 * @description The index of the current undo state.
7454 * @method _hasUndoLevel
7455 * @description Checks to see if we have an undo level available
7458 _hasUndoLevel: function() {
7459 return ((this._undoCache.length > 1) && this._undoLevel);
7463 * @method _undoNodeChange
7464 * @description nodeChange listener for undo processing
7466 _undoNodeChange: function() {
7467 var undo_button = this.toolbar.getButtonByValue('undo'),
7468 redo_button = this.toolbar.getButtonByValue('redo');
7469 if (undo_button && redo_button) {
7470 if (this._hasUndoLevel()) {
7471 this.toolbar.enableButton(undo_button);
7473 if (this._undoLevel < this._undoCache.length) {
7474 this.toolbar.enableButton(redo_button);
7477 this._lastCommand = null;
7481 * @method _checkUndo
7482 * @description Prunes the undo cache when it reaches the maxUndo config
7484 _checkUndo: function() {
7485 var len = this._undoCache.length,
7487 if (len >= this.get('maxUndo')) {
7488 for (var i = (len - this.get('maxUndo')); i < len; i++) {
7489 tmp.push(this._undoCache[i]);
7491 this._undoCache = tmp;
7492 this._undoLevel = this._undoCache.length;
7498 * @description Puts the content of the Editor into the _undoCache.
7499 * //TODO Convert the hash to a series of TEXTAREAS to store state in.
7500 * @param {String} str The content of the Editor
7502 _putUndo: function(str) {
7503 if (this._undoLevel === this._undoCache.length) {
7504 this._undoCache.push(str);
7505 this._undoLevel = this._undoCache.length;
7507 var str = this.getEditorHTML();
7508 var last = this._undoCache[this._undoLevel];
7511 this._undoCache = [];
7512 this._undoLevel = 0;
7520 * @description Get's a level from the undo cache.
7521 * @param {Number} index The index of the undo level we want to get.
7524 _getUndo: function(index) {
7525 this._undoLevel = index;
7526 return this._undoCache[index];
7530 * @method _storeUndo
7531 * @description Method to call when you want to store an undo state. Currently called from nodeChange and _handleKeyUp
7533 _storeUndo: function() {
7534 if (this._lastCommand === 'undo' || this._lastCommand === 'redo') {
7537 if (!this._undoCache) {
7538 this._undoCache = [];
7539 this._undoLevel = 0;
7542 var str = this.getEditorHTML();
7543 //var last = this._undoCache[this._undoCache.length - 1];
7544 var last = this._undoCache[this._undoLevel - 1];
7552 this._undoNodeChange();
7555 * @property STR_BEFORE_EDITOR
7556 * @description The accessibility string for the element before the iFrame
7559 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>',
7561 * @property STR_CLOSE_WINDOW
7562 * @description The Title of the close button in the Editor Window
7565 STR_CLOSE_WINDOW: 'Close Window',
7567 * @property STR_CLOSE_WINDOW_NOTE
7568 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
7571 STR_CLOSE_WINDOW_NOTE: 'To close this window use the Control + Shift + W key',
7573 * @property STR_IMAGE_PROP_TITLE
7574 * @description The title for the Image Property Editor Window
7577 STR_IMAGE_PROP_TITLE: 'Image Options',
7579 * @property STR_IMAGE_TITLE
7580 * @description The label string for Image Description
7583 STR_IMAGE_TITLE: 'Description',
7585 * @property STR_IMAGE_SIZE
7586 * @description The label string for Image Size
7589 STR_IMAGE_SIZE: 'Size',
7591 * @property STR_IMAGE_ORIG_SIZE
7592 * @description The label string for Original Image Size
7595 STR_IMAGE_ORIG_SIZE: 'Original Size',
7597 * @property STR_IMAGE_COPY
7598 * @description The label string for the image copy and paste message for Opera and Safari
7601 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>',
7603 * @property STR_IMAGE_PADDING
7604 * @description The label string for the image padding.
7607 STR_IMAGE_PADDING: 'Padding',
7609 * @property STR_IMAGE_BORDER
7610 * @description The label string for the image border.
7613 STR_IMAGE_BORDER: 'Border',
7615 * @property STR_IMAGE_BORDER_SIZE
7616 * @description The label string for the image border size.
7619 STR_IMAGE_BORDER_SIZE: 'Border Size',
7621 * @property STR_IMAGE_BORDER_TYPE
7622 * @description The label string for the image border type.
7625 STR_IMAGE_BORDER_TYPE: 'Border Type',
7627 * @property STR_IMAGE_TEXTFLOW
7628 * @description The label string for the image text flow.
7631 STR_IMAGE_TEXTFLOW: 'Text Flow',
7633 * @property STR_LOCAL_FILE_WARNING
7634 * @description The label string for the local file warning.
7637 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>',
7639 * @property STR_LINK_PROP_TITLE
7640 * @description The label string for the Link Property Editor Window.
7643 STR_LINK_PROP_TITLE: 'Link Options',
7645 * @property STR_LINK_PROP_REMOVE
7646 * @description The label string for the Remove link from text link inside the property editor.
7649 STR_LINK_PROP_REMOVE: 'Remove link from text',
7651 * @property STR_LINK_NEW_WINDOW
7652 * @description The string for the open in a new window label.
7655 STR_LINK_NEW_WINDOW: 'Open in a new window.',
7657 * @property STR_LINK_TITLE
7658 * @description The string for the link description.
7661 STR_LINK_TITLE: 'Description',
7663 * @property STR_NONE
7664 * @description The string for the word none.
7670 * @property CLASS_LOCAL_FILE
7671 * @description CSS class applied to an element when it's found to have a local url.
7674 CLASS_LOCAL_FILE: 'warning-localfile',
7677 * @property CLASS_HIDDEN
7678 * @description CSS class applied to the body when the hiddenelements button is pressed.
7681 CLASS_HIDDEN: 'yui-hidden',
7684 * @description The Editor class' initialization method
7686 init: function(p_oElement, p_oAttributes) {
7689 if (!this._defaultToolbar) {
7690 this._defaultToolbar = {
7692 titlebar: 'Text Editing Tools',
7694 buttonType: 'advanced',
7696 { group: 'fontstyle', label: 'Font Name and Size',
7698 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
7700 { text: 'Arial', checked: true },
7701 { text: 'Arial Black' },
7702 { text: 'Comic Sans MS' },
7703 { text: 'Courier New' },
7704 { text: 'Lucida Console' },
7706 { text: 'Times New Roman' },
7707 { text: 'Trebuchet MS' },
7711 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
7714 { type: 'separator' },
7715 { group: 'textstyle', label: 'Font Style',
7717 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
7718 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
7719 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
7720 { type: 'separator' },
7721 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true },
7722 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true }
7725 { type: 'separator' },
7726 { group: 'textstyle2', label: ' ',
7728 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
7729 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true },
7730 { type: 'separator' },
7731 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true },
7732 { type: 'push', label: 'Show/Hide Hidden Elements', value: 'hiddenelements' }
7735 { type: 'separator' },
7736 { group: 'undoredo', label: 'Undo/Redo',
7738 { type: 'push', label: 'Undo', value: 'undo', disabled: true },
7739 { type: 'push', label: 'Redo', value: 'redo', disabled: true }
7743 { type: 'separator' },
7744 { group: 'alignment', label: 'Alignment',
7746 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
7747 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
7748 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
7749 { type: 'push', label: 'Justify', value: 'justifyfull' }
7752 { type: 'separator' },
7753 { group: 'parastyle', label: 'Paragraph Style',
7755 { type: 'select', label: 'Normal', value: 'heading', disabled: true,
7757 { text: 'Normal', value: 'none', checked: true },
7758 { text: 'Header 1', value: 'h1' },
7759 { text: 'Header 2', value: 'h2' },
7760 { text: 'Header 3', value: 'h3' },
7761 { text: 'Header 4', value: 'h4' },
7762 { text: 'Header 5', value: 'h5' },
7763 { text: 'Header 6', value: 'h6' }
7768 { type: 'separator' },
7770 { group: 'indentlist2', label: 'Indenting and Lists',
7772 { type: 'push', label: 'Indent', value: 'indent', disabled: true },
7773 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
7774 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
7775 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
7778 { type: 'separator' },
7779 { group: 'insertitem', label: 'Insert Item',
7781 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
7782 { type: 'push', label: 'Insert Image', value: 'insertimage' }
7789 if (!this._defaultImageToolbarConfig) {
7790 this._defaultImageToolbarConfig = {
7791 buttonType: this._defaultToolbar.buttonType,
7793 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
7795 { type: 'push', label: 'Left', value: 'left' },
7796 { type: 'push', label: 'Inline', value: 'inline' },
7797 { type: 'push', label: 'Block', value: 'block' },
7798 { type: 'push', label: 'Right', value: 'right' }
7801 { type: 'separator' },
7802 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
7804 { type: 'spin', label: '0', value: 'padding', range: [0, 50] }
7807 { type: 'separator' },
7808 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
7810 { type: 'select', label: this.STR_IMAGE_BORDER_SIZE, value: 'bordersize',
7812 { text: 'none', value: '0', checked: true },
7813 { text: '1px', value: '1' },
7814 { text: '2px', value: '2' },
7815 { text: '3px', value: '3' },
7816 { text: '4px', value: '4' },
7817 { text: '5px', value: '5' }
7820 { type: 'select', label: this.STR_IMAGE_BORDER_TYPE, value: 'bordertype', disabled: true,
7822 { text: 'Solid', value: 'solid', checked: true },
7823 { text: 'Dashed', value: 'dashed' },
7824 { text: 'Dotted', value: 'dotted' }
7827 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
7834 YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes);
7836 _render: function() {
7837 YAHOO.widget.Editor.superclass._render.apply(this, arguments);
7839 //Render the panel in another thread and delay it a little..
7840 window.setTimeout(function() {
7841 self._renderPanel.call(self);
7845 * @method initAttributes
7846 * @description Initializes all of the configuration attributes used to create
7848 * @param {Object} attr Object literal specifying a set of
7849 * configuration attributes used to create the editor.
7851 initAttributes: function(attr) {
7852 YAHOO.widget.Editor.superclass.initAttributes.call(this, attr);
7855 * @attribute localFileWarning
7856 * @description Should we throw the warning if we detect a file that is local to their machine?
7860 this.setAttributeConfig('localFileWarning', {
7861 value: attr.locaFileWarning || true
7865 * @attribute hiddencss
7866 * @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>
7867 * @default <code><pre>
7868 .yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u,
7869 .yui-hidden div, .yui-hidden p, .yui-hidden span, .yui-hidden img, .yui-hidden ul, .yui-hidden ol,
7870 .yui-hidden li, .yui-hidden table {
7871 border: 1px dotted #ccc;
7873 .yui-hidden .yui-non {
7881 this.setAttributeConfig('hiddencss', {
7882 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; }',
7890 * @description A reference to the HTML elements used for the body of Editor Windows.
7895 * @method _defaultImageToolbar
7896 * @description A reference to the Toolbar Object inside Image Editor Window.
7898 _defaultImageToolbar: null,
7901 * @method _defaultImageToolbarConfig
7902 * @description Config to be used for the default Image Editor Window.
7904 _defaultImageToolbarConfig: null,
7908 * @description Fix href and imgs as well as remove invalid HTML.
7910 _fixNodes: function() {
7911 YAHOO.widget.Editor.superclass._fixNodes.call(this);
7915 var imgs = this._getDoc().getElementsByTagName('img');
7916 for (var im = 0; im < imgs.length; im++) {
7917 if (imgs[im].getAttribute('href', 2)) {
7918 url = imgs[im].getAttribute('src', 2);
7919 if (this._isLocalFile(url)) {
7920 Dom.addClass(imgs[im], this.CLASS_LOCAL_FILE);
7922 Dom.removeClass(imgs[im], this.CLASS_LOCAL_FILE);
7926 var fakeAs = this._getDoc().body.getElementsByTagName('a');
7927 for (var a = 0; a < fakeAs.length; a++) {
7928 if (fakeAs[a].getAttribute('href', 2)) {
7929 url = fakeAs[a].getAttribute('href', 2);
7930 if (this._isLocalFile(url)) {
7931 Dom.addClass(fakeAs[a], this.CLASS_LOCAL_FILE);
7933 Dom.removeClass(fakeAs[a], this.CLASS_LOCAL_FILE);
7941 * @property _disabled
7942 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
7945 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent' ],
7948 * @property _alwaysDisabled
7949 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
7952 _alwaysDisabled: { 'outdent': true },
7955 * @property _alwaysEnabled
7956 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
7959 _alwaysEnabled: { hiddenelements: true },
7962 * @method _handleKeyDown
7963 * @param {Event} ev The event we are working on.
7964 * @description Override method that handles some new keydown events inside the iFrame document.
7966 _handleKeyDown: function(ev) {
7967 YAHOO.widget.Editor.superclass._handleKeyDown.call(this, ev);
7972 switch (ev.keyCode) {
7974 case this._keyMap.JUSTIFY_LEFT.key: //Left
7975 if (this._checkKey(this._keyMap.JUSTIFY_LEFT, ev)) {
7976 action = 'justifyleft';
7980 //case 220: //Center
7981 case this._keyMap.JUSTIFY_CENTER.key:
7982 if (this._checkKey(this._keyMap.JUSTIFY_CENTER, ev)) {
7983 action = 'justifycenter';
7988 case this._keyMap.JUSTIFY_RIGHT.key:
7989 if (this._checkKey(this._keyMap.JUSTIFY_RIGHT, ev)) {
7990 action = 'justifyright';
7995 if (doExec && action) {
7996 this.execCommand(action, null);
7997 Event.stopEvent(ev);
8003 * @method _renderCreateLinkWindow
8004 * @description Pre renders the CreateLink window so we get faster window opening.
8006 _renderCreateLinkWindow: function() {
8007 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>';
8008 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>';
8009 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>';
8011 var body = document.createElement('div');
8012 body.innerHTML = str;
8014 var unlinkCont = document.createElement('div');
8015 unlinkCont.className = 'removeLink';
8016 var unlink = document.createElement('a');
8018 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
8019 unlink.title = this.STR_LINK_PROP_REMOVE;
8020 Event.on(unlink, 'click', function(ev) {
8021 Event.stopEvent(ev);
8022 this.unsubscribeAll('afterExecCommand');
8023 this.execCommand('unlink');
8026 unlinkCont.appendChild(unlink);
8027 body.appendChild(unlinkCont);
8029 this._windows.createlink = {};
8030 this._windows.createlink.body = body;
8031 //body.style.display = 'none';
8032 Event.on(body, 'keyup', function(e) {
8033 Event.stopPropagation(e);
8035 this.get('panel').editor_form.appendChild(body);
8036 this.fireEvent('windowCreateLinkRender', { type: 'windowCreateLinkRender', panel: this.get('panel'), body: body });
8039 _handleCreateLinkClick: function() {
8040 var el = this._getSelectedElement();
8041 if (this._isElement(el, 'img')) {
8042 this.STOP_EXEC_COMMAND = true;
8043 this.currentElement[0] = el;
8044 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
8045 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
8048 if (this.get('limitCommands')) {
8049 if (!this.toolbar.getButtonByValue('createlink')) {
8054 this.on('afterExecCommand', function() {
8055 var win = new YAHOO.widget.EditorWindow('createlink', {
8059 var el = this.currentElement[0],
8066 if (el.getAttribute('href', 2) !== null) {
8067 url = el.getAttribute('href', 2);
8068 if (this._isLocalFile(url)) {
8069 //Local File throw Warning
8070 win.setFooter(this.STR_LOCAL_FILE_WARNING);
8076 if (el.getAttribute('title') !== null) {
8077 title = el.getAttribute('title');
8079 if (el.getAttribute('target') !== null) {
8080 target = el.getAttribute('target');
8084 if (this._windows.createlink && this._windows.createlink.body) {
8085 body = this._windows.createlink.body;
8087 body = this._renderCreateLinkWindow();
8090 win.setHeader(this.STR_LINK_PROP_TITLE);
8093 Event.purgeElement(this.get('id') + '_createlink_url');
8095 Dom.get(this.get('id') + '_createlink_url').value = url;
8096 Dom.get(this.get('id') + '_createlink_title').value = title;
8097 Dom.get(this.get('id') + '_createlink_target').checked = ((target) ? true : false);
8100 Event.onAvailable(this.get('id') + '_createlink_url', function() {
8101 var id = this.get('id');
8102 window.setTimeout(function() {
8104 YAHOO.util.Dom.get(id + '_createlink_url').focus();
8108 if (this._isLocalFile(url)) {
8109 //Local File throw Warning
8110 Dom.addClass(this.get('id') + '_createlink_url', 'warning');
8111 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8113 Dom.removeClass(this.get('id') + '_createlink_url', 'warning');
8114 this.get('panel').setFooter(' ');
8116 Event.on(this.get('id') + '_createlink_url', 'blur', function() {
8117 var url = Dom.get(this.get('id') + '_createlink_url');
8118 if (this._isLocalFile(url.value)) {
8119 //Local File throw Warning
8120 Dom.addClass(url, 'warning');
8121 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8123 Dom.removeClass(url, 'warning');
8124 this.get('panel').setFooter(' ');
8129 this.openWindow(win);
8135 * @method _handleCreateLinkWindowClose
8136 * @description Handles the closing of the Link Properties Window.
8138 _handleCreateLinkWindowClose: function() {
8140 var url = Dom.get(this.get('id') + '_createlink_url'),
8141 target = Dom.get(this.get('id') + '_createlink_target'),
8142 title = Dom.get(this.get('id') + '_createlink_title'),
8143 el = arguments[0].win.el,
8146 if (url && url.value) {
8147 var urlValue = url.value;
8148 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8149 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8150 //Found an @ sign, prefix with mailto:
8151 urlValue = 'mailto:' + urlValue;
8153 // :// not found adding
8154 if (urlValue.substring(0, 1) != '#') {
8155 urlValue = 'http:/'+'/' + urlValue;
8160 el.setAttribute('href', urlValue);
8161 if (target.checked) {
8162 el.setAttribute('target', target.value);
8164 el.setAttribute('target', '');
8166 el.setAttribute('title', ((title.value) ? title.value : ''));
8169 var _span = this._getDoc().createElement('span');
8170 _span.innerHTML = el.innerHTML;
8171 Dom.addClass(_span, 'yui-non');
8172 el.parentNode.replaceChild(_span, el);
8174 Dom.removeClass(url, 'warning');
8175 Dom.get(this.get('id') + '_createlink_url').value = '';
8176 Dom.get(this.get('id') + '_createlink_title').value = '';
8177 Dom.get(this.get('id') + '_createlink_target').checked = false;
8179 this.currentElement = [];
8184 * @method _renderInsertImageWindow
8185 * @description Pre renders the InsertImage window so we get faster window opening.
8187 _renderInsertImageWindow: function() {
8188 var el = this.currentElement[0];
8189 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>';
8190 var body = document.createElement('div');
8191 body.innerHTML = str;
8193 var tbarCont = document.createElement('div');
8194 tbarCont.id = this.get('id') + '_img_toolbar';
8195 body.appendChild(tbarCont);
8197 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>';
8198 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>';
8199 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>';
8200 var div = document.createElement('div');
8201 div.innerHTML = str2;
8202 body.appendChild(div);
8205 Lang.augmentObject(o, this._defaultImageToolbarConfig); //Break the config reference
8207 var tbar = new YAHOO.widget.Toolbar(tbarCont, o);
8208 tbar.editor_el = el;
8209 this._defaultImageToolbar = tbar;
8211 var cont = tbar.get('cont');
8212 var hw = document.createElement('div');
8213 hw.className = 'yui-toolbar-group yui-toolbar-group-height-width height-width';
8214 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
8215 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>';
8216 cont.insertBefore(hw, cont.firstChild);
8218 Event.onAvailable(this.get('id') + '_insertimage_width', function() {
8219 Event.on(this.get('id') + '_insertimage_width', 'blur', function() {
8220 var value = parseInt(Dom.get(this.get('id') + '_insertimage_width').value, 10);
8222 this._defaultImageToolbar.editor_el.style.width = value + 'px';
8223 //Removed moveWindow call so the window doesn't jump
8224 //this.moveWindow();
8228 Event.onAvailable(this.get('id') + '_insertimage_height', function() {
8229 Event.on(this.get('id') + '_insertimage_height', 'blur', function() {
8230 var value = parseInt(Dom.get(this.get('id') + '_insertimage_height').value, 10);
8232 this._defaultImageToolbar.editor_el.style.height = value + 'px';
8233 //Removed moveWindow call so the window doesn't jump
8234 //this.moveWindow();
8240 tbar.on('colorPickerClicked', function(o) {
8241 var size = '1', type = 'solid', color = 'black', el = this._defaultImageToolbar.editor_el;
8243 if (el.style.borderLeftWidth) {
8244 size = parseInt(el.style.borderLeftWidth, 10);
8246 if (el.style.borderLeftStyle) {
8247 type = el.style.borderLeftStyle;
8249 if (el.style.borderLeftColor) {
8250 color = el.style.borderLeftColor;
8252 var borderString = size + 'px ' + type + ' #' + o.color;
8253 el.style.border = borderString;
8256 tbar.on('buttonClick', function(o) {
8257 var value = o.button.value,
8258 el = this._defaultImageToolbar.editor_el,
8260 if (o.button.menucmd) {
8261 value = o.button.menucmd;
8263 var size = '1', type = 'solid', color = 'black';
8265 /* All border calcs are done on the left border
8266 since our default interface only supports
8267 one border size/type and color */
8268 if (el.style.borderLeftWidth) {
8269 size = parseInt(el.style.borderLeftWidth, 10);
8271 if (el.style.borderLeftStyle) {
8272 type = el.style.borderLeftStyle;
8274 if (el.style.borderLeftColor) {
8275 color = el.style.borderLeftColor;
8279 if (this.browser.webkit && this._lastImage) {
8280 Dom.removeClass(this._lastImage, 'selected');
8281 this._lastImage = null;
8284 borderString = parseInt(o.button.value, 10) + 'px ' + type + ' ' + color;
8285 el.style.border = borderString;
8286 if (parseInt(o.button.value, 10) > 0) {
8287 tbar.enableButton('bordertype');
8288 tbar.enableButton('bordercolor');
8290 tbar.disableButton('bordertype');
8291 tbar.disableButton('bordercolor');
8295 if (this.browser.webkit && this._lastImage) {
8296 Dom.removeClass(this._lastImage, 'selected');
8297 this._lastImage = null;
8299 borderString = size + 'px ' + o.button.value + ' ' + color;
8300 el.style.border = borderString;
8304 tbar.deselectAllButtons();
8305 el.style.display = '';
8306 el.align = o.button.value;
8309 tbar.deselectAllButtons();
8310 el.style.display = '';
8314 tbar.deselectAllButtons();
8315 el.style.display = 'block';
8316 el.align = 'center';
8319 var _button = tbar.getButtonById(o.button.id);
8320 el.style.margin = _button.get('label') + 'px';
8323 tbar.selectButton(o.button.value);
8324 if (value !== 'padding') {
8331 if (this.get('localFileWarning')) {
8332 Event.on(this.get('id') + '_insertimage_link', 'blur', function() {
8333 var url = Dom.get(this.get('id') + '_insertimage_link');
8334 if (this._isLocalFile(url.value)) {
8335 //Local File throw Warning
8336 Dom.addClass(url, 'warning');
8337 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8339 Dom.removeClass(url, 'warning');
8340 this.get('panel').setFooter(' ');
8342 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8343 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8349 Event.on(this.get('id') + '_insertimage_url', 'blur', function() {
8350 var url = Dom.get(this.get('id') + '_insertimage_url'),
8351 el = this.currentElement[0];
8353 if (url.value && el) {
8354 if (url.value == el.getAttribute('src', 2)) {
8358 if (this._isLocalFile(url.value)) {
8359 //Local File throw Warning
8360 Dom.addClass(url, 'warning');
8361 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8362 } else if (this.currentElement[0]) {
8363 Dom.removeClass(url, 'warning');
8364 this.get('panel').setFooter(' ');
8366 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8367 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8370 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8371 this.currentElement[0].setAttribute('src', url.value);
8375 img.onerror = function() {
8376 url.value = self.STR_IMAGE_HERE;
8377 img.setAttribute('src', self.get('blankimage'));
8378 self.currentElement[0].setAttribute('src', self.get('blankimage'));
8379 YAHOO.util.Dom.get(self.get('id') + '_insertimage_height').value = img.height;
8380 YAHOO.util.Dom.get(self.get('id') + '_insertimage_width').value = img.width;
8382 var id = this.get('id');
8383 window.setTimeout(function() {
8384 YAHOO.util.Dom.get(id + '_insertimage_height').value = img.height;
8385 YAHOO.util.Dom.get(id + '_insertimage_width').value = img.width;
8386 if (self.currentElement && self.currentElement[0]) {
8387 if (!self.currentElement[0]._height) {
8388 self.currentElement[0]._height = img.height;
8390 if (!self.currentElement[0]._width) {
8391 self.currentElement[0]._width = img.width;
8394 //Removed moveWindow call so the window doesn't jump
8395 //self.moveWindow();
8396 }, 800); //Bumped the timeout up to account for larger images..
8398 if (url.value != this.STR_IMAGE_HERE) {
8399 img.src = url.value;
8407 this._windows.insertimage = {};
8408 this._windows.insertimage.body = body;
8409 //body.style.display = 'none';
8410 this.get('panel').editor_form.appendChild(body);
8411 this.fireEvent('windowInsertImageRender', { type: 'windowInsertImageRender', panel: this.get('panel'), body: body, toolbar: tbar });
8416 * @method _handleInsertImageClick
8417 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
8419 _handleInsertImageClick: function() {
8420 if (this.get('limitCommands')) {
8421 if (!this.toolbar.getButtonByValue('insertimage')) {
8425 this.on('afterExecCommand', function() {
8426 var el = this.currentElement[0],
8440 win = new YAHOO.widget.EditorWindow('insertimage', {
8445 el = this._getSelectedElement();
8449 if (el.getAttribute('src')) {
8450 src = el.getAttribute('src', 2);
8451 if (src.indexOf(this.get('blankimage')) != -1) {
8452 src = this.STR_IMAGE_HERE;
8456 if (el.getAttribute('alt', 2)) {
8457 title = el.getAttribute('alt', 2);
8459 if (el.getAttribute('title', 2)) {
8460 title = el.getAttribute('title', 2);
8463 if (el.parentNode && this._isElement(el.parentNode, 'a')) {
8464 link = el.parentNode.getAttribute('href', 2);
8465 if (el.parentNode.getAttribute('target') !== null) {
8466 target = el.parentNode.getAttribute('target');
8469 height = parseInt(el.height, 10);
8470 width = parseInt(el.width, 10);
8471 if (el.style.height) {
8472 height = parseInt(el.style.height, 10);
8474 if (el.style.width) {
8475 width = parseInt(el.style.width, 10);
8477 if (el.style.margin) {
8478 padding = parseInt(el.style.margin, 10);
8482 el._height = height;
8487 oheight = el._height;
8491 if (this._windows.insertimage && this._windows.insertimage.body) {
8492 body = this._windows.insertimage.body;
8493 this._defaultImageToolbar.resetAllButtons();
8495 body = this._renderInsertImageWindow();
8498 tbar = this._defaultImageToolbar;
8499 tbar.editor_el = el;
8505 if (el.style.borderLeftWidth) {
8506 bsize = parseInt(el.style.borderLeftWidth, 10);
8508 if (el.style.borderLeftStyle) {
8509 btype = el.style.borderLeftStyle;
8511 var bs_button = tbar.getButtonByValue('bordersize'),
8512 bSizeStr = ((parseInt(bsize, 10) > 0) ? '' : this.STR_NONE);
8513 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">' + bSizeStr + '</span>');
8514 this._updateMenuChecked('bordersize', bsize, tbar);
8516 var bt_button = tbar.getButtonByValue('bordertype');
8517 bt_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">asdfa</span>');
8518 this._updateMenuChecked('bordertype', btype, tbar);
8519 if (parseInt(bsize, 10) > 0) {
8520 tbar.enableButton(bt_button);
8521 tbar.enableButton(bs_button);
8522 tbar.enableButton('bordercolor');
8525 if ((el.align == 'right') || (el.align == 'left')) {
8526 tbar.selectButton(el.align);
8527 } else if (el.style.display == 'block') {
8528 tbar.selectButton('block');
8530 tbar.selectButton('inline');
8532 if (parseInt(el.style.marginLeft, 10) > 0) {
8533 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft, 10));
8535 if (el.style.borderSize) {
8536 tbar.selectButton('bordersize');
8537 tbar.selectButton(parseInt(el.style.borderSize, 10));
8539 tbar.getButtonByValue('padding').set('label', ''+padding);
8543 win.setHeader(this.STR_IMAGE_PROP_TITLE);
8546 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8547 win.setFooter(this.STR_IMAGE_COPY);
8549 this.openWindow(win);
8550 Dom.get(this.get('id') + '_insertimage_url').value = src;
8551 Dom.get(this.get('id') + '_insertimage_title').value = title;
8552 Dom.get(this.get('id') + '_insertimage_link').value = link;
8553 Dom.get(this.get('id') + '_insertimage_target').checked = ((target) ? true : false);
8554 Dom.get(this.get('id') + '_insertimage_width').value = width;
8555 Dom.get(this.get('id') + '_insertimage_height').value = height;
8558 if (((height != oheight) || (width != owidth)) && (!blankimage)) {
8559 var s = document.createElement('span');
8560 s.className = 'info';
8561 s.innerHTML = this.STR_IMAGE_ORIG_SIZE + ': ('+ owidth +' x ' + oheight + ')';
8562 if (Dom.get(this.get('id') + '_insertimage_height').nextSibling) {
8563 var old = Dom.get(this.get('id') + '_insertimage_height').nextSibling;
8564 old.parentNode.removeChild(old);
8566 Dom.get(this.get('id') + '_insertimage_height').parentNode.appendChild(s);
8569 this.toolbar.selectButton('insertimage');
8570 var id = this.get('id');
8571 window.setTimeout(function() {
8573 YAHOO.util.Dom.get(id + '_insertimage_url').focus();
8575 YAHOO.util.Dom.get(id + '_insertimage_url').select();
8584 * @method _handleInsertImageWindowClose
8585 * @description Handles the closing of the Image Properties Window.
8587 _handleInsertImageWindowClose: function() {
8588 var url = Dom.get(this.get('id') + '_insertimage_url'),
8589 title = Dom.get(this.get('id') + '_insertimage_title'),
8590 link = Dom.get(this.get('id') + '_insertimage_link'),
8591 target = Dom.get(this.get('id') + '_insertimage_target'),
8592 el = arguments[0].win.el;
8594 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8595 el.setAttribute('src', url.value);
8596 el.setAttribute('title', title.value);
8597 el.setAttribute('alt', title.value);
8598 var par = el.parentNode;
8600 var urlValue = link.value;
8601 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8602 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8603 //Found an @ sign, prefix with mailto:
8604 urlValue = 'mailto:' + urlValue;
8606 // :// not found adding
8607 urlValue = 'http:/'+'/' + urlValue;
8610 if (par && this._isElement(par, 'a')) {
8611 par.setAttribute('href', urlValue);
8612 if (target.checked) {
8613 par.setAttribute('target', target.value);
8615 par.setAttribute('target', '');
8618 var _a = this._getDoc().createElement('a');
8619 _a.setAttribute('href', urlValue);
8620 if (target.checked) {
8621 _a.setAttribute('target', target.value);
8623 _a.setAttribute('target', '');
8625 el.parentNode.replaceChild(_a, el);
8629 if (par && this._isElement(par, 'a')) {
8630 par.parentNode.replaceChild(el, par);
8634 //No url/src given, remove the node from the document
8635 el.parentNode.removeChild(el);
8637 Dom.get(this.get('id') + '_insertimage_url').value = '';
8638 Dom.get(this.get('id') + '_insertimage_title').value = '';
8639 Dom.get(this.get('id') + '_insertimage_link').value = '';
8640 Dom.get(this.get('id') + '_insertimage_target').checked = false;
8641 Dom.get(this.get('id') + '_insertimage_width').value = 0;
8642 Dom.get(this.get('id') + '_insertimage_height').value = 0;
8643 this._defaultImageToolbar.resetAllButtons();
8644 this.currentElement = [];
8648 * @property EDITOR_PANEL_ID
8649 * @description HTML id to give the properties window in the DOM.
8652 EDITOR_PANEL_ID: '-panel',
8655 * @method _renderPanel
8656 * @description Renders the panel used for Editor Windows to the document so we can start using it..
8657 * @return {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>}
8659 _renderPanel: function() {
8660 var panelEl = document.createElement('div');
8661 Dom.addClass(panelEl, 'yui-editor-panel');
8662 panelEl.id = this.get('id') + this.EDITOR_PANEL_ID;
8663 panelEl.style.position = 'absolute';
8664 panelEl.style.top = '-9999px';
8665 panelEl.style.left = '-9999px';
8666 document.body.appendChild(panelEl);
8667 this.get('element_cont').insertBefore(panelEl, this.get('element_cont').get('firstChild'));
8671 var panel = new YAHOO.widget.Overlay(this.get('id') + this.EDITOR_PANEL_ID, {
8679 this.set('panel', panel);
8681 panel.setBody('---');
8682 panel.setHeader(' ');
8683 panel.setFooter(' ');
8686 var body = document.createElement('div');
8687 body.className = this.CLASS_PREFIX + '-body-cont';
8688 for (var b in this.browser) {
8689 if (this.browser[b]) {
8690 Dom.addClass(body, b);
8694 Dom.addClass(body, ((YAHOO.widget.Button && (this._defaultToolbar.buttonType == 'advanced')) ? 'good-button' : 'no-button'));
8696 var _note = document.createElement('h3');
8697 _note.className = 'yui-editor-skipheader';
8698 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
8699 body.appendChild(_note);
8700 var form = document.createElement('fieldset');
8701 panel.editor_form = form;
8703 body.appendChild(form);
8704 var _close = document.createElement('span');
8705 _close.innerHTML = 'X';
8706 _close.title = this.STR_CLOSE_WINDOW;
8707 _close.className = 'close';
8709 Event.on(_close, 'click', this.closeWindow, this, true);
8711 var _knob = document.createElement('span');
8712 _knob.innerHTML = '^';
8713 _knob.className = 'knob';
8714 panel.editor_knob = _knob;
8716 var _header = document.createElement('h3');
8717 panel.editor_header = _header;
8718 _header.innerHTML = '<span></span>';
8720 panel.setHeader(' '); //Clear the current header
8721 panel.appendToHeader(_header);
8722 _header.appendChild(_close);
8723 _header.appendChild(_knob);
8724 panel.setBody(' '); //Clear the current body
8725 panel.setFooter(' '); //Clear the current footer
8726 panel.appendToBody(body); //Append the new DOM node to it
8728 Event.on(panel.element, 'click', function(ev) {
8729 Event.stopPropagation(ev);
8732 var fireShowEvent = function() {
8734 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
8735 this._handleWindowInputs(false);
8737 panel.showEvent.subscribe(fireShowEvent, this, true);
8738 panel.hideEvent.subscribe(function() {
8739 this._handleWindowInputs(true);
8741 panel.renderEvent.subscribe(function() {
8742 this._renderInsertImageWindow();
8743 this._renderCreateLinkWindow();
8744 this.fireEvent('windowRender', { type: 'windowRender', panel: panel });
8745 this._handleWindowInputs(true);
8748 if (this.DOMReady) {
8749 this.get('panel').render();
8751 Event.onDOMReady(function() {
8752 this.get('panel').render();
8755 return this.get('panel');
8758 * @method _handleWindowInputs
8759 * @param {Boolean} disable The state to set all inputs in all Editor windows to. Defaults to: false.
8760 * @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.
8762 _handleWindowInputs: function(disable) {
8763 if (!Lang.isBoolean(disable)) {
8766 var inputs = this.get('panel').element.getElementsByTagName('input');
8767 for (var i = 0; i < inputs.length; i++) {
8769 inputs[i].disabled = disable;
8774 * @method openWindow
8775 * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance
8776 * @description Opens a new "window/panel"
8778 openWindow: function(win) {
8781 window.setTimeout(function() {
8782 self.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
8784 Event.on(document, 'keydown', this._closeWindow, this, true);
8786 if (this.currentWindow) {
8790 var xy = Dom.getXY(this.currentElement[0]),
8791 elXY = Dom.getXY(this.get('iframe').get('element')),
8792 panel = this.get('panel'),
8793 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8794 wWidth = (parseInt(win.attrs.width, 10) / 2),
8798 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
8800 var form = panel.editor_form;
8802 var wins = this._windows;
8803 for (var b in wins) {
8804 if (Lang.hasOwnProperty(wins, b)) {
8805 if (wins[b] && wins[b].body) {
8806 if (b == win.name) {
8807 Dom.setStyle(wins[b].body, 'display', 'block');
8809 Dom.setStyle(wins[b].body, 'display', 'none');
8815 if (this._windows[win.name].body) {
8816 Dom.setStyle(this._windows[win.name].body, 'display', 'block');
8817 form.appendChild(this._windows[win.name].body);
8819 if (Lang.isObject(win.body)) { //Assume it's a reference
8820 form.appendChild(win.body);
8821 } else { //Assume it's a string
8822 var _tmp = document.createElement('div');
8823 _tmp.innerHTML = win.body;
8824 form.appendChild(_tmp);
8827 panel.editor_header.firstChild.innerHTML = win.header;
8828 if (win.footer !== null) {
8829 panel.setFooter(win.footer);
8831 panel.cfg.setProperty('width', win.attrs.width);
8833 this.currentWindow = win;
8834 this.moveWindow(true);
8836 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
8839 * @method moveWindow
8840 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
8841 * @description Realign the window with the currentElement and reposition the knob above the panel.
8843 moveWindow: function(force) {
8844 if (!this.currentWindow) {
8847 var win = this.currentWindow,
8848 xy = Dom.getXY(this.currentElement[0]),
8849 elXY = Dom.getXY(this.get('iframe').get('element')),
8850 panel = this.get('panel'),
8851 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8852 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
8853 wWidth = (parseInt(win.attrs.width, 10) / 2),
8855 orgXY = panel.cfg.getProperty('xy') || [0,0],
8856 _knob = panel.editor_knob,
8862 newXY[0] = ((newXY[0] - wWidth) + 20);
8863 //Account for the Scroll bars in a scrolled editor window.
8864 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
8865 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
8867 if (this._isElement(this.currentElement[0], 'img')) {
8868 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
8869 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
8870 newXY[1] = (newXY[1] + 75); //Placeholder sizea
8872 var w = parseInt(this.currentElement[0].width, 10);
8873 var h = parseInt(this.currentElement[0].height, 10);
8874 newXY[0] = (newXY[0] + (w / 2));
8875 newXY[1] = (newXY[1] + h);
8877 newXY[1] = newXY[1] + 15;
8879 var fs = Dom.getStyle(this.currentElement[0], 'fontSize');
8880 if (fs && fs.indexOf && fs.indexOf('px') != -1) {
8881 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize'), 10) + 5;
8883 newXY[1] = newXY[1] + 20;
8886 if (newXY[0] < elXY[0]) {
8887 newXY[0] = elXY[0] + 5;
8891 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10))) {
8892 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10)) - (wWidth * 2) - 5);
8897 xDiff = (newXY[0] - orgXY[0]);
8898 yDiff = (newXY[1] - orgXY[1]);
8902 var iTop = elXY[1] + parseInt(this.get('height'), 10);
8903 var iLeft = elXY[0] + parseInt(this.get('width'), 10);
8904 if (newXY[1] > iTop) {
8907 if (newXY[0] > iLeft) {
8908 newXY[0] = (iLeft / 2);
8911 //Convert negative numbers to positive so we can get the difference in distance
8912 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
8913 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
8915 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)
8919 if (this.currentElement[0].width) {
8920 elW = (parseInt(this.currentElement[0].width, 10) / 2);
8923 var leftOffset = xy[0] + elXY[0] + elW;
8924 _knobLeft = leftOffset - newXY[0];
8925 //Check to see if the knob will go off either side & reposition it
8926 if (_knobLeft > (parseInt(win.attrs.width, 10) - 1)) {
8927 _knobLeft = ((parseInt(win.attrs.width, 10) - 30) - 1);
8928 } else if (_knobLeft < 40) {
8931 if (isNaN(_knobLeft)) {
8936 _knob.style.left = _knobLeft + 'px';
8938 //Removed Animation from a forced move..
8939 panel.cfg.setProperty('xy', newXY);
8941 if (this.get('animate')) {
8942 anim = new YAHOO.util.Anim(panel.element, {}, 0.5, YAHOO.util.Easing.easeOut);
8951 anim.onComplete.subscribe(function() {
8952 panel.cfg.setProperty('xy', newXY);
8954 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
8955 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, 0.5, YAHOO.util.Easing.easeOut);
8957 var _knobAnim = new YAHOO.util.Anim(_knob, {
8961 }, 0.6, YAHOO.util.Easing.easeOut);
8963 iframeAnim.animate();
8964 _knobAnim.animate();
8966 _knob.style.left = _knobLeft + 'px';
8967 panel.cfg.setProperty('xy', newXY);
8974 * @method _closeWindow
8975 * @description Close the currently open EditorWindow with the Escape key.
8976 * @param {Event} ev The keypress Event that we are trapping
8978 _closeWindow: function(ev) {
8979 //if ((ev.charCode == 87) && ev.shiftKey && ev.ctrlKey) {
8980 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
8981 if (this.currentWindow) {
8987 * @method closeWindow
8988 * @description Close the currently open EditorWindow.
8990 closeWindow: function(keepOpen) {
8991 this.fireEvent('window' + this.currentWindow.name + 'Close', { type: 'window' + this.currentWindow.name + 'Close', win: this.currentWindow, el: this.currentElement[0] });
8992 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
8993 this.currentWindow = null;
8994 this.get('panel').hide();
8995 this.get('panel').cfg.setProperty('xy', [-900,-900]);
8996 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
8997 this.unsubscribeAll('afterExecCommand');
8998 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
8999 this.toolbar.resetAllButtons();
9001 Event.removeListener(document, 'keydown', this._closeWindow);
9004 /* {{{ Command Overrides - These commands are only over written when we are using the advanced version */
9008 * @description Pulls an item from the Undo stack and updates the Editor
9009 * @param value Value passed from the execCommand method
9011 cmd_undo: function(value) {
9012 if (this._hasUndoLevel()) {
9013 var c_html = this.getEditorHTML(), html;
9014 if (!this._undoLevel) {
9015 this._undoLevel = this._undoCache.length;
9017 this._undoLevel = (this._undoLevel - 1);
9018 if (this._undoCache[this._undoLevel]) {
9019 html = this._getUndo(this._undoLevel);
9020 if (html != c_html) {
9021 this.setEditorHTML(html);
9023 this._undoLevel = (this._undoLevel - 1);
9024 html = this._getUndo(this._undoLevel);
9025 if (html != c_html) {
9026 this.setEditorHTML(html);
9030 this._undoLevel = 0;
9031 this.toolbar.disableButton('undo');
9039 * @description Pulls an item from the Undo stack and updates the Editor
9040 * @param value Value passed from the execCommand method
9042 cmd_redo: function(value) {
9043 this._undoLevel = this._undoLevel + 1;
9044 if (this._undoLevel >= this._undoCache.length) {
9045 this._undoLevel = this._undoCache.length;
9047 if (this._undoCache[this._undoLevel]) {
9048 var html = this._getUndo(this._undoLevel);
9049 this.setEditorHTML(html);
9051 this.toolbar.disableButton('redo');
9057 * @method cmd_heading
9058 * @param value Value passed from the execCommand method
9059 * @description This is an execCommand override method. It is called from execCommand when the execCommand('heading') is used.
9061 cmd_heading: function(value) {
9065 _sel = this._getSelection(),
9066 _selEl = this._getSelectedElement();
9072 if (this.browser.ie) {
9073 action = 'formatblock';
9075 if (value == this.STR_NONE) {
9076 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'))) {
9077 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
9078 _sel = _sel.parentNode;
9080 if (this._isElement(_sel, 'html')) {
9083 el = this._swapEl(_selEl, 'span', function(el) {
9084 el.className = 'yui-non';
9086 this._selectNode(el);
9087 this.currentElement[0] = el;
9091 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')) {
9092 el = this._swapEl(_selEl, value);
9093 this._selectNode(el);
9094 this.currentElement[0] = el;
9096 this._createCurrentElement(value);
9097 this._selectNode(this.currentElement[0]);
9101 return [exec, action];
9104 * @method cmd_hiddenelements
9105 * @param value Value passed from the execCommand method
9106 * @description This is an execCommand override method. It is called from execCommand when the execCommand('hiddenelements') is used.
9108 cmd_hiddenelements: function(value) {
9109 if (this._showingHiddenElements) {
9110 //Don't auto highlight the hidden button
9111 this._lastButton = null;
9112 this._showingHiddenElements = false;
9113 this.toolbar.deselectButton('hiddenelements');
9114 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN);
9116 this._showingHiddenElements = true;
9117 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN);
9118 this.toolbar.selectButton('hiddenelements');
9123 * @method cmd_removeformat
9124 * @param value Value passed from the execCommand method
9125 * @description This is an execCommand override method. It is called from execCommand when the execCommand('removeformat') is used.
9127 cmd_removeformat: function(value) {
9130 * @knownissue Remove Format issue
9131 * @browser Safari 2.x
9132 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
9133 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
9134 * So here we are making the best possible guess and acting on it.
9136 if (this.browser.webkit && !this._getDoc().queryCommandEnabled('removeformat')) {
9137 var _txt = this._getSelection()+'';
9138 this._createCurrentElement('span');
9139 this.currentElement[0].className = 'yui-non';
9140 this.currentElement[0].innerHTML = _txt;
9141 for (var i = 1; i < this.currentElement.length; i++) {
9142 this.currentElement[i].parentNode.removeChild(this.currentElement[i]);
9150 * @method cmd_script
9151 * @param action action passed from the execCommand method
9152 * @param value Value passed from the execCommand method
9153 * @description This is a combined execCommand override method. It is called from the cmd_superscript and cmd_subscript methods.
9155 cmd_script: function(action, value) {
9156 var exec = true, tag = action.toLowerCase().substring(0, 3),
9157 _span = null, _selEl = this._getSelectedElement();
9159 if (this.browser.webkit) {
9160 if (this._isElement(_selEl, tag)) {
9161 _span = this._swapEl(this.currentElement[0], 'span', function(el) {
9162 el.className = 'yui-non';
9164 this._selectNode(_span);
9166 this._createCurrentElement(tag);
9167 var _sub = this._swapEl(this.currentElement[0], tag);
9168 this._selectNode(_sub);
9169 this.currentElement[0] = _sub;
9176 * @method cmd_superscript
9177 * @param value Value passed from the execCommand method
9178 * @description This is an execCommand override method. It is called from execCommand when the execCommand('superscript') is used.
9180 cmd_superscript: function(value) {
9181 return [this.cmd_script('superscript', value)];
9184 * @method cmd_subscript
9185 * @param value Value passed from the execCommand method
9186 * @description This is an execCommand override method. It is called from execCommand when the execCommand('subscript') is used.
9188 cmd_subscript: function(value) {
9189 return [this.cmd_script('subscript', value)];
9192 * @method cmd_indent
9193 * @param value Value passed from the execCommand method
9194 * @description This is an execCommand override method. It is called from execCommand when the execCommand('indent') is used.
9196 cmd_indent: function(value) {
9197 var exec = true, selEl = this._getSelectedElement(), _bq = null;
9199 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9200 //if (this.browser.webkit || this.browser.ie) {
9201 if (this.browser.ie) {
9202 if (this._isElement(selEl, 'blockquote')) {
9203 _bq = this._getDoc().createElement('blockquote');
9204 _bq.innerHTML = selEl.innerHTML;
9205 selEl.innerHTML = '';
9206 selEl.appendChild(_bq);
9207 this._selectNode(_bq);
9209 _bq = this._getDoc().createElement('blockquote');
9210 var html = this._getRange().htmlText;
9211 _bq.innerHTML = html;
9212 this._createCurrentElement('blockquote');
9214 for (var i = 0; i < this.currentElement.length; i++) {
9215 _bq = this._getDoc().createElement('blockquote');
9216 _bq.innerHTML = this.currentElement[i].innerHTML;
9217 this.currentElement[i].parentNode.replaceChild(_bq, this.currentElement[i]);
9218 this.currentElement[i] = _bq;
9221 this.currentElement[0].parentNode.replaceChild(_bq, this.currentElement[0]);
9222 this.currentElement[0] = _bq;
9223 this._selectNode(this.currentElement[0]);
9227 value = 'blockquote';
9229 return [exec, 'formatblock', value];
9232 * @method cmd_outdent
9233 * @param value Value passed from the execCommand method
9234 * @description This is an execCommand override method. It is called from execCommand when the execCommand('outdent') is used.
9236 cmd_outdent: function(value) {
9237 var exec = true, selEl = this._getSelectedElement(), _bq = null, _span = null;
9238 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9239 if (this.browser.webkit || this.browser.ie) {
9240 //if (this.browser.ie) {
9241 selEl = this._getSelectedElement();
9242 if (this._isElement(selEl, 'blockquote')) {
9243 var par = selEl.parentNode;
9244 if (this._isElement(selEl.parentNode, 'blockquote')) {
9245 par.innerHTML = selEl.innerHTML;
9246 this._selectNode(par);
9248 _span = this._getDoc().createElement('span');
9249 _span.innerHTML = selEl.innerHTML;
9250 YAHOO.util.Dom.addClass(_span, 'yui-non');
9251 par.replaceChild(_span, selEl);
9252 this._selectNode(_span);
9260 return [exec, 'outdent', value];
9263 * @method cmd_justify
9264 * @param dir The direction to justify
9265 * @description This is a factory method for the justify family of commands.
9267 cmd_justify: function(dir) {
9268 if (this.browser.ie) {
9269 if (this._hasSelection()) {
9270 this._createCurrentElement('span');
9271 this._swapEl(this.currentElement[0], 'div', function(el) {
9272 el.style.textAlign = dir;
9278 return [true, 'justify' + dir, ''];
9281 * @method cmd_justifycenter
9282 * @param value Value passed from the execCommand method
9283 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifycenter') is used.
9285 cmd_justifycenter: function() {
9286 return [this.cmd_justify('center')];
9289 * @method cmd_justifyleft
9290 * @param value Value passed from the execCommand method
9291 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyleft') is used.
9293 cmd_justifyleft: function() {
9294 return [this.cmd_justify('left')];
9297 * @method cmd_justifyright
9298 * @param value Value passed from the execCommand method
9299 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyright') is used.
9301 cmd_justifyright: function() {
9302 return [this.cmd_justify('right')];
9307 * @description Returns a string representing the editor.
9310 toString: function() {
9312 if (this.get && this.get('element_cont')) {
9313 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
9319 * @event beforeOpenWindow
9320 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9321 * @param {Overlay} panel The Overlay object that is used to create the window.
9322 * @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.
9323 * @type YAHOO.util.CustomEvent
9326 * @event afterOpenWindow
9327 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9328 * @param {Overlay} panel The Overlay object that is used to create the window.
9329 * @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.
9330 * @type YAHOO.util.CustomEvent
9333 * @event closeWindow
9334 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9335 * @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.
9336 * @type YAHOO.util.CustomEvent
9339 * @event windowCMDOpen
9340 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9341 * @param {Overlay} panel The Overlay object that is used to create the window.
9342 * @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.
9343 * @type YAHOO.util.CustomEvent
9346 * @event windowCMDClose
9347 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9348 * @param {Overlay} panel The Overlay object that is used to create the window.
9349 * @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.
9350 * @type YAHOO.util.CustomEvent
9353 * @event windowRender
9354 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9355 * @param {Overlay} panel The Overlay object that is used to create the window.
9356 * @description Event fired when the initial Overlay is rendered. Can be used to manipulate the content of the panel.
9357 * @type YAHOO.util.CustomEvent
9360 * @event windowInsertImageRender
9361 * @param {Overlay} panel The Overlay object that is used to create the window.
9362 * @param {HTMLElement} body The HTML element used as the body of the window..
9363 * @param {Toolbar} toolbar A reference to the toolbar object used inside this window.
9364 * @description Event fired when the pre render of the Insert Image window has finished.
9365 * @type YAHOO.util.CustomEvent
9368 * @event windowCreateLinkRender
9369 * @param {Overlay} panel The Overlay object that is used to create the window.
9370 * @param {HTMLElement} body The HTML element used as the body of the window..
9371 * @description Event fired when the pre render of the Create Link window has finished.
9372 * @type YAHOO.util.CustomEvent
9378 * @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.
9379 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
9380 * @class EditorWindow
9381 * @param {String} name The name of the window.
9382 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
9384 YAHOO.widget.EditorWindow = function(name, attrs) {
9388 * @description A unique name for the window
9390 this.name = name.replace(' ', '_');
9394 * @description The window attributes
9399 YAHOO.widget.EditorWindow.prototype = {
9403 * @description Holder for the header of the window, used in Editor.openWindow
9409 * @description Holder for the body of the window, used in Editor.openWindow
9415 * @description Holder for the footer of the window, used in Editor.openWindow
9420 * @description Sets the header for the window.
9421 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
9423 setHeader: function(str) {
9428 * @description Sets the body for the window.
9429 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
9431 setBody: function(str) {
9436 * @description Sets the footer for the window.
9437 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
9439 setFooter: function(str) {
9444 * @description Returns a string representing the EditorWindow.
9447 toString: function() {
9448 return 'Editor Window (' + this.name + ')';
9452 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.9.0", build: "2800"});