2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 var Dom = YAHOO.util.Dom,
9 Event = YAHOO.util.Event,
13 * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
14 * @class ToolbarButtonAdvanced
15 * @namespace YAHOO.widget
16 * @requires yahoo, dom, element, event, container_core, menu, button
18 * Provides a toolbar button based on the button and menu widgets.
20 * @class ToolbarButtonAdvanced
21 * @param {String/HTMLElement} el The element to turn into a button.
22 * @param {Object} attrs Object liternal containing configuration parameters.
24 if (YAHOO.widget.Button) {
25 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button;
27 * @property buttonType
29 * @description Tells if the Button is a Rich Button or a Simple Button
31 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich';
34 * @param {String} value The value of the option that we want to mark as selected
35 * @description Select an option by value
37 YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) {
38 var _menuItems = this.getMenu().getItems();
39 if (_menuItems.length === 0) {
40 this.getMenu()._onBeforeShow();
41 _menuItems = this.getMenu().getItems();
43 for (var i = 0; i < _menuItems.length; i++) {
44 _menuItems[i].cfg.setProperty('checked', false);
45 if (_menuItems[i].value == value) {
46 _menuItems[i].cfg.setProperty('checked', true);
51 YAHOO.widget.ToolbarButtonAdvanced = function() {};
56 * @description <p>Creates a basic custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p><p>Provides a toolbar button based on the button and menu widgets, <select> elements are used in place of menu's.</p>
57 * @class ToolbarButton
58 * @namespace YAHOO.widget
59 * @requires yahoo, dom, element, event
60 * @extends YAHOO.util.Element
64 * @param {String/HTMLElement} el The element to turn into a button.
65 * @param {Object} attrs Object liternal containing configuration parameters.
68 YAHOO.widget.ToolbarButton = function(el, attrs) {
69 YAHOO.log('ToolbarButton Initalizing', 'info', 'ToolbarButton');
70 YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
72 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
75 var local_attrs = (attrs || {});
79 attributes: local_attrs
82 if (!oConfig.attributes.type) {
83 oConfig.attributes.type = 'push';
86 oConfig.element = document.createElement('span');
87 oConfig.element.setAttribute('unselectable', 'on');
88 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
89 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
90 oConfig.element.firstChild.firstChild.tabIndex = '-1';
91 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId());
92 oConfig.element.id = oConfig.attributes.id;
94 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
97 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
99 * @property buttonType
101 * @description Tells if the Button is a Rich Button or a Simple Button
103 buttonType: 'normal',
105 * @method _handleMouseOver
107 * @description Adds classes to the button elements on mouseover (hover)
109 _handleMouseOver: function() {
110 if (!this.get('disabled')) {
111 this.addClass('yui-button-hover');
112 this.addClass('yui-' + this.get('type') + '-button-hover');
116 * @method _handleMouseOut
118 * @description Removes classes from the button elements on mouseout (hover)
120 _handleMouseOut: function() {
121 this.removeClass('yui-button-hover');
122 this.removeClass('yui-' + this.get('type') + '-button-hover');
126 * @param {String} value The value of the option that we want to mark as selected
127 * @description Select an option by value
129 checkValue: function(value) {
130 if (this.get('type') == 'menu') {
131 var opts = this._button.options;
132 for (var i = 0; i < opts.length; i++) {
133 if (opts[i].value == value) {
134 opts.selectedIndex = i;
141 * @description The ToolbarButton class's initialization method
143 init: function(p_oElement, p_oAttributes) {
144 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
146 this.on('mouseover', this._handleMouseOver, this, true);
147 this.on('mouseout', this._handleMouseOut, this, true);
148 this.on('click', function(ev) {
154 * @method initAttributes
155 * @description Initializes all of the configuration attributes used to create
157 * @param {Object} attr Object literal specifying a set of
158 * configuration attributes used to create the toolbar.
160 initAttributes: function(attr) {
161 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
164 * @description The value of the button
167 this.setAttributeConfig('value', {
172 * @description The menu attribute, see YAHOO.widget.Button
175 this.setAttributeConfig('menu', {
176 value: attr.menu || false
180 * @description The type of button to create: push, menu, color, select, spin
183 this.setAttributeConfig('type', {
186 method: function(type) {
189 this._button = this.get('element').getElementsByTagName('a')[0];
194 el = document.createElement('select');
195 el.id = this.get('id');
196 var menu = this.get('menu');
197 for (var i = 0; i < menu.length; i++) {
198 opt = document.createElement('option');
199 opt.innerHTML = menu[i].text;
200 opt.value = menu[i].value;
201 if (menu[i].checked) {
206 this._button.parentNode.replaceChild(el, this._button);
207 Event.on(el, 'change', this._handleSelect, this, true);
215 * @attribute disabled
216 * @description Set the button into a disabled state
219 this.setAttributeConfig('disabled', {
220 value: attr.disabled || false,
221 method: function(disabled) {
223 this.addClass('yui-button-disabled');
224 this.addClass('yui-' + this.get('type') + '-button-disabled');
226 this.removeClass('yui-button-disabled');
227 this.removeClass('yui-' + this.get('type') + '-button-disabled');
229 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) {
230 this._button.disabled = disabled;
237 * @description The text label for the button
240 this.setAttributeConfig('label', {
242 method: function(label) {
244 this._button = this.get('element').getElementsByTagName('a')[0];
246 if (this.get('type') == 'push') {
247 this._button.innerHTML = label;
254 * @description The title of the button
257 this.setAttributeConfig('title', {
263 * @description The container that the button is rendered to, handled by Toolbar
266 this.setAttributeConfig('container', {
269 method: function(cont) {
277 * @method _handleSelect
278 * @description The event fired when a change event gets fired on a select element
279 * @param {Event} ev The change event.
281 _handleSelect: function(ev) {
282 var tar = Event.getTarget(ev);
283 var value = tar.options[tar.selectedIndex].value;
284 this.fireEvent('change', {type: 'change', value: value });
288 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
290 getMenu: function() {
291 return this.get('menu');
295 * @description Destroy the button
297 destroy: function() {
298 Event.purgeElement(this.get('element'), true);
299 this.get('element').parentNode.removeChild(this.get('element'));
300 //Brutal Object Destroy
301 for (var i in this) {
302 if (Lang.hasOwnProperty(this, i)) {
309 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
311 fireEvent: function(p_sType, p_aArgs) {
312 // Disabled buttons should not respond to DOM events
313 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
314 Event.stopEvent(p_aArgs);
318 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
322 * @description Returns a string representing the toolbar.
325 toString: function() {
326 return 'ToolbarButton (' + this.get('id') + ')';
333 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
334 * @namespace YAHOO.widget
335 * @requires yahoo, dom, element, event, toolbarbutton
336 * @optional container_core, dragdrop
339 var Dom = YAHOO.util.Dom,
340 Event = YAHOO.util.Event,
343 var getButton = function(id) {
345 if (Lang.isString(id)) {
346 button = this.getButtonById(id);
348 if (Lang.isNumber(id)) {
349 button = this.getButtonByIndex(id);
351 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
352 button = this.getButtonByValue(id);
354 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
361 * Provides a rich toolbar widget based on the button and menu widgets
364 * @extends YAHOO.util.Element
365 * @param {String/HTMLElement} el The element to turn into a toolbar.
366 * @param {Object} attrs Object liternal containing configuration parameters.
368 YAHOO.widget.Toolbar = function(el, attrs) {
369 YAHOO.log('Toolbar Initalizing', 'info', 'Toolbar');
370 YAHOO.log(arguments.length + ' arguments passed to constructor', 'info', 'Toolbar');
372 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
375 var local_attrs = {};
377 Lang.augmentObject(local_attrs, attrs); //Break the config reference
383 attributes: local_attrs
387 if (Lang.isString(el) && Dom.get(el)) {
388 oConfig.element = Dom.get(el);
389 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
390 oConfig.element = Dom.get(el);
394 if (!oConfig.element) {
395 YAHOO.log('No element defined, creating toolbar container', 'warn', 'Toolbar');
396 oConfig.element = document.createElement('DIV');
397 oConfig.element.id = Dom.generateId();
399 if (local_attrs.container && Dom.get(local_attrs.container)) {
400 YAHOO.log('Container found in config appending to it (' + Dom.get(local_attrs.container).id + ')', 'info', 'Toolbar');
401 Dom.get(local_attrs.container).appendChild(oConfig.element);
406 if (!oConfig.element.id) {
407 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
408 YAHOO.log('No element ID defined for toolbar container, creating..', 'warn', 'Toolbar');
410 YAHOO.log('Initing toolbar with id: ' + oConfig.element.id, 'info', 'Toolbar');
412 var fs = document.createElement('fieldset');
413 var lg = document.createElement('legend');
414 lg.innerHTML = 'Toolbar';
417 var cont = document.createElement('DIV');
418 oConfig.attributes.cont = cont;
419 Dom.addClass(cont, 'yui-toolbar-subcont');
420 fs.appendChild(cont);
421 oConfig.element.appendChild(fs);
423 oConfig.element.tabIndex = -1;
426 oConfig.attributes.element = oConfig.element;
427 oConfig.attributes.id = oConfig.element.id;
429 this._configuredButtons = [];
431 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
435 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
438 * @property _configuredButtons
441 _configuredButtons: null,
443 * @method _addMenuClasses
445 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
446 * @param {String} ev The event that fired.
447 * @param {Array} na Array of event information.
448 * @param {Object} o Button config object.
450 _addMenuClasses: function(ev, na, o) {
451 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
452 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
453 Dom.addClass(this.element, 'yui-toolbar-select-menu');
455 var items = this.getItems();
456 for (var i = 0; i < items.length; i++) {
457 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-').toLowerCase() : items[i]._oText.nodeValue.replace(/ /g, '-').toLowerCase()));
458 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
462 * @property buttonType
463 * @description The default button to use
466 buttonType: YAHOO.widget.ToolbarButton,
469 * @description The DragDrop instance associated with the Toolbar
474 * @property _colorData
475 * @description Object reference containing colors hex and text values.
480 '#111111': 'Obsidian',
481 '#2D2D2D': 'Dark Gray',
485 '#8B8B8B': 'Concrete',
487 '#B9B9B9': 'Titanium',
489 '#D0D0D0': 'Light Gray',
492 '#BFBF00': 'Pumpkin',
495 '#FFFF80': 'Pale Yellow',
497 '#525330': 'Raw Siena',
500 '#7F7F00': 'Paprika',
505 '#80FF00': 'Chartreuse',
507 '#C0FF80': 'Pale Lime',
508 '#DFFFBF': 'Light Mint',
510 '#668F5A': 'Lime Gray',
513 '#8A9B55': 'Pistachio',
514 '#B7C296': 'Light Jade',
515 '#E6EBD5': 'Breakwater',
516 '#00BF00': 'Spring Frost',
517 '#00FF80': 'Pastel Green',
518 '#40FFA0': 'Light Emerald',
519 '#80FFC0': 'Sea Foam',
520 '#BFFFDF': 'Sea Mist',
521 '#033D21': 'Dark Forrest',
523 '#7FA37C': 'Medium Green',
525 '#8DAE94': 'Yellow Gray Green',
526 '#ACC6B5': 'Aqua Lung',
527 '#DDEBE2': 'Sea Vapor',
530 '#40FFFF': 'Turquoise Blue',
531 '#80FFFF': 'Light Aqua',
532 '#BFFFFF': 'Pale Cyan',
533 '#033D3D': 'Dark Teal',
534 '#347D7E': 'Gray Turquoise',
535 '#609A9F': 'Green Blue',
536 '#007F7F': 'Seaweed',
537 '#96BDC4': 'Green Gray',
538 '#B5D1D7': 'Soapstone',
539 '#E2F1F4': 'Light Turquoise',
540 '#0060BF': 'Summer Sky',
541 '#0080FF': 'Sky Blue',
542 '#40A0FF': 'Electric Blue',
543 '#80C0FF': 'Light Azure',
544 '#BFDFFF': 'Ice Blue',
547 '#57708F': 'Dusty Blue',
548 '#00407F': 'Sea Blue',
549 '#7792AC': 'Sky Blue Gray',
550 '#A8BED1': 'Morning Sky',
552 '#0000BF': 'Deep Blue',
554 '#4040FF': 'Cerulean Blue',
555 '#8080FF': 'Evening Blue',
556 '#BFBFFF': 'Light Blue',
557 '#212143': 'Deep Indigo',
558 '#373E68': 'Sea Blue',
559 '#444F75': 'Night Blue',
560 '#00007F': 'Indigo Blue',
561 '#585E82': 'Dockside',
562 '#8687A4': 'Blue Gray',
563 '#D2D1E1': 'Light Blue Gray',
564 '#6000BF': 'Neon Violet',
565 '#8000FF': 'Blue Violet',
566 '#A040FF': 'Violet Purple',
567 '#C080FF': 'Violet Dusk',
568 '#DFBFFF': 'Pale Lavender',
569 '#302449': 'Cool Shale',
570 '#54466F': 'Dark Indigo',
571 '#655A7F': 'Dark Violet',
573 '#726284': 'Smoky Violet',
574 '#9E8FA9': 'Slate Gray',
575 '#DCD1DF': 'Violet White',
576 '#BF00BF': 'Royal Violet',
577 '#FF00FF': 'Fuchsia',
578 '#FF40FF': 'Magenta',
580 '#FFBFFF': 'Pale Magenta',
581 '#4A234A': 'Dark Purple',
582 '#794A72': 'Medium Purple',
583 '#936386': 'Cool Granite',
585 '#9D7292': 'Purple Moon',
586 '#C0A0B6': 'Pale Purple',
587 '#ECDAE5': 'Pink Cloud',
588 '#BF005F': 'Hot Pink',
589 '#FF007F': 'Deep Pink',
591 '#FF80BF': 'Electric Pink',
593 '#451528': 'Purple Red',
594 '#823857': 'Purple Dino',
595 '#A94A76': 'Purple Gray',
597 '#BC6F95': 'Antique Mauve',
598 '#D8A5BB': 'Cool Marble',
599 '#F7DDE9': 'Pink Granite',
601 '#FF0000': 'Fire Truck',
602 '#FF4040': 'Pale Red',
604 '#FFC0C0': 'Warm Pink',
608 '#800000': 'Brick Red',
610 '#D8A3A4': 'Shrimp Pink',
611 '#F8DDDD': 'Shell Pink',
612 '#BF5F00': 'Dark Orange',
614 '#FF9F40': 'Grapefruit',
615 '#FFBF80': 'Canteloupe',
617 '#482C1B': 'Dark Brick',
621 '#C49B71': 'Mustard',
622 '#E1C4A8': 'Pale Tan',
627 * @property _colorPicker
628 * @description The HTML Element containing the colorPicker
633 * @property STR_COLLAPSE
634 * @description String for Toolbar Collapse Button
637 STR_COLLAPSE: 'Collapse Toolbar',
639 * @property STR_EXPAND
640 * @description String for Toolbar Collapse Button - Expand
643 STR_EXPAND: 'Expand Toolbar',
645 * @property STR_SPIN_LABEL
646 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
649 STR_SPIN_LABEL: 'Spin Button with value {VALUE}. Use Control Shift Up Arrow and Control Shift Down arrow keys to increase or decrease the value.',
651 * @property STR_SPIN_UP
652 * @description String for spinbutton up
655 STR_SPIN_UP: 'Click to increase the value of this input',
657 * @property STR_SPIN_DOWN
658 * @description String for spinbutton down
661 STR_SPIN_DOWN: 'Click to decrease the value of this input',
663 * @property _titlebar
664 * @description Object reference to the titlebar
670 * @description Standard browser detection
673 browser: YAHOO.env.ua,
676 * @property _buttonList
677 * @description Internal property list of current buttons in the toolbar
683 * @property _buttonGroupList
684 * @description Internal property list of current button groups in the toolbar
687 _buttonGroupList: null,
691 * @description Internal reference to the separator HTML Element for cloning
697 * @property _sepCount
698 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
704 * @property draghandle
710 * @property _toolbarConfigs
718 * @property CLASS_CONTAINER
719 * @description Default CSS class to apply to the toolbar container element
722 CLASS_CONTAINER: 'yui-toolbar-container',
725 * @property CLASS_DRAGHANDLE
726 * @description Default CSS class to apply to the toolbar's drag handle element
729 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
732 * @property CLASS_SEPARATOR
733 * @description Default CSS class to apply to all separators in the toolbar
736 CLASS_SEPARATOR: 'yui-toolbar-separator',
739 * @property CLASS_DISABLED
740 * @description Default CSS class to apply when the toolbar is disabled
743 CLASS_DISABLED: 'yui-toolbar-disabled',
746 * @property CLASS_PREFIX
747 * @description Default prefix for dynamically created class names
750 CLASS_PREFIX: 'yui-toolbar',
753 * @description The Toolbar class's initialization method
755 init: function(p_oElement, p_oAttributes) {
756 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
759 * @method initAttributes
760 * @description Initializes all of the configuration attributes used to create
762 * @param {Object} attr Object literal specifying a set of
763 * configuration attributes used to create the toolbar.
765 initAttributes: function(attr) {
766 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
767 this.addClass(this.CLASS_CONTAINER);
770 * @attribute buttonType
771 * @description The buttonType to use (advanced or basic)
774 this.setAttributeConfig('buttonType', {
775 value: attr.buttonType || 'basic',
777 validator: function(type) {
785 method: function(type) {
786 if (type == 'advanced') {
787 if (YAHOO.widget.Button) {
788 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
790 YAHOO.log('Can not find YAHOO.widget.Button', 'error', 'Toolbar');
791 this.buttonType = YAHOO.widget.ToolbarButton;
794 this.buttonType = YAHOO.widget.ToolbarButton;
802 * @description Object specifying the buttons to include in the toolbar
806 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
807 * { type: 'separator' },
808 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
810 * { text: "Left", value: 'alignleft' },
811 * { text: "Center", value: 'aligncenter' },
812 * { text: "Right", value: 'alignright' }
820 this.setAttributeConfig('buttons', {
823 method: function(data) {
824 var i, button, buttons, len, b;
826 if (Lang.hasOwnProperty(data, i)) {
827 if (data[i].type == 'separator') {
829 } else if (data[i].group !== undefined) {
830 buttons = this.addButtonGroup(data[i]);
832 len = buttons.length;
833 for(b = 0; b < len; b++) {
835 this._configuredButtons[this._configuredButtons.length] = buttons[b].id;
841 button = this.addButton(data[i]);
843 this._configuredButtons[this._configuredButtons.length] = button.id;
852 * @attribute disabled
853 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
857 this.setAttributeConfig('disabled', {
859 method: function(disabled) {
860 if (this.get('disabled') === disabled) {
864 this.addClass(this.CLASS_DISABLED);
865 this.set('draggable', false);
866 this.disableAllButtons();
868 this.removeClass(this.CLASS_DISABLED);
869 if (this._configs.draggable._initialConfig.value) {
870 //Draggable by default, set it back
871 this.set('draggable', true);
873 this.resetAllButtons();
880 * @description The container for the toolbar.
883 this.setAttributeConfig('cont', {
890 * @attribute grouplabels
891 * @description Boolean indicating if the toolbar should show the group label's text string.
895 this.setAttributeConfig('grouplabels', {
896 value: ((attr.grouplabels === false) ? false : true),
897 method: function(grouplabels) {
899 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
901 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
906 * @attribute titlebar
907 * @description Boolean indicating if the toolbar should have a titlebar. If
908 * passed a string, it will use that as the titlebar text
910 * @type Boolean or String
912 this.setAttributeConfig('titlebar', {
914 method: function(titlebar) {
916 if (this._titlebar && this._titlebar.parentNode) {
917 this._titlebar.parentNode.removeChild(this._titlebar);
919 this._titlebar = document.createElement('DIV');
920 this._titlebar.tabIndex = '-1';
921 Event.on(this._titlebar, 'focus', function() {
924 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
925 if (Lang.isString(titlebar)) {
926 var h2 = document.createElement('h2');
928 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
929 this._titlebar.appendChild(h2);
930 Event.on(h2.firstChild, 'click', function(ev) {
933 Event.on([h2, h2.firstChild], 'focus', function() {
937 if (this.get('firstChild')) {
938 this.insertBefore(this._titlebar, this.get('firstChild'));
940 this.appendChild(this._titlebar);
942 if (this.get('collapse')) {
943 this.set('collapse', true);
945 } else if (this._titlebar) {
946 if (this._titlebar && this._titlebar.parentNode) {
947 this._titlebar.parentNode.removeChild(this._titlebar);
955 * @attribute collapse
956 * @description Boolean indicating if the the titlebar should have a collapse button.
957 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
961 this.setAttributeConfig('collapse', {
963 method: function(collapse) {
964 if (this._titlebar) {
965 var collapseEl = null;
966 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
969 //There is already a collapse button
972 collapseEl = document.createElement('SPAN');
973 collapseEl.innerHTML = 'X';
974 collapseEl.title = this.STR_COLLAPSE;
976 Dom.addClass(collapseEl, 'collapse');
977 this._titlebar.appendChild(collapseEl);
978 Event.addListener(collapseEl, 'click', function() {
979 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
980 this.collapse(false); //Expand Toolbar
982 this.collapse(); //Collapse Toolbar
986 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
988 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
989 //We are closed, reopen the titlebar..
990 this.collapse(false); //Expand Toolbar
992 collapseEl[0].parentNode.removeChild(collapseEl[0]);
1000 * @attribute draggable
1001 * @description Boolean indicating if the toolbar should be draggable.
1006 this.setAttributeConfig('draggable', {
1007 value: (attr.draggable || false),
1008 method: function(draggable) {
1009 if (draggable && !this.get('titlebar')) {
1010 YAHOO.log('Dragging enabled', 'info', 'Toolbar');
1011 if (!this._dragHandle) {
1012 this._dragHandle = document.createElement('SPAN');
1013 this._dragHandle.innerHTML = '|';
1014 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
1015 this._dragHandle.id = this.get('id') + '_draghandle';
1016 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
1017 if (this.get('cont').hasChildNodes()) {
1018 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
1020 this.get('cont').appendChild(this._dragHandle);
1022 this.dd = new YAHOO.util.DD(this.get('id'));
1023 this.dd.setHandleElId(this._dragHandle.id);
1027 YAHOO.log('Dragging disabled', 'info', 'Toolbar');
1028 if (this._dragHandle) {
1029 this._dragHandle.parentNode.removeChild(this._dragHandle);
1030 this._dragHandle = null;
1034 if (this._titlebar) {
1036 this.dd = new YAHOO.util.DD(this.get('id'));
1037 this.dd.setHandleElId(this._titlebar);
1038 Dom.addClass(this._titlebar, 'draggable');
1040 Dom.removeClass(this._titlebar, 'draggable');
1048 validator: function(value) {
1050 if (!YAHOO.util.DD) {
1059 * @method addButtonGroup
1060 * @description Add a new button group to the toolbar. (uses addButton)
1061 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1063 addButtonGroup: function(oGroup) {
1064 if (!this.get('element')) {
1065 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1069 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1070 this.addClass(this.CLASS_PREFIX + '-grouped');
1072 var div = document.createElement('DIV');
1073 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1074 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1076 var label = document.createElement('h3');
1077 label.innerHTML = oGroup.label;
1078 div.appendChild(label);
1080 if (!this.get('grouplabels')) {
1081 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1084 this.get('cont').appendChild(div);
1086 //For accessibility, let's put all of the group buttons in an Unordered List
1087 var ul = document.createElement('ul');
1088 div.appendChild(ul);
1090 if (!this._buttonGroupList) {
1091 this._buttonGroupList = {};
1094 this._buttonGroupList[oGroup.group] = ul;
1096 //An array of the button ids added to this group
1097 //This is used for destruction later...
1098 var addedButtons = [],
1102 for (var i = 0; i < oGroup.buttons.length; i++) {
1103 var li = document.createElement('li');
1104 li.className = this.CLASS_PREFIX + '-groupitem';
1106 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1107 this.addSeparator(li);
1109 oGroup.buttons[i].container = li;
1110 button = this.addButton(oGroup.buttons[i]);
1112 addedButtons[addedButtons.length] = button.id;
1116 return addedButtons;
1119 * @method addButtonToGroup
1120 * @description Add a new button to a toolbar group. Buttons supported:
1121 * push, split, menu, select, color, spin
1122 * @param {Object} oButton Object literal reference to the Button's Config
1123 * @param {String} group The Group identifier passed into the initial config
1124 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1126 addButtonToGroup: function(oButton, group, after) {
1127 var groupCont = this._buttonGroupList[group],
1128 li = document.createElement('li');
1130 li.className = this.CLASS_PREFIX + '-groupitem';
1131 oButton.container = li;
1132 this.addButton(oButton, after);
1133 groupCont.appendChild(li);
1137 * @description Add a new button to the toolbar. Buttons supported:
1138 * push, split, menu, select, color, spin
1139 * @param {Object} oButton Object literal reference to the Button's Config
1140 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1142 addButton: function(oButton, after) {
1143 if (!this.get('element')) {
1144 this._queue[this._queue.length] = ['addButton', arguments];
1147 if (!this._buttonList) {
1148 this._buttonList = [];
1150 YAHOO.log('Adding button of type: ' + oButton.type, 'info', 'Toolbar');
1151 if (!oButton.container) {
1152 oButton.container = this.get('cont');
1155 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1156 if (Lang.isArray(oButton.menu)) {
1157 for (var i in oButton.menu) {
1158 if (Lang.hasOwnProperty(oButton.menu, i)) {
1160 fn: function(ev, x, oMenu) {
1161 if (!oButton.menucmd) {
1162 oButton.menucmd = oButton.value;
1164 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1168 oButton.menu[i].onclick = funcObject;
1173 var _oButton = {}, skip = false;
1174 for (var o in oButton) {
1175 if (Lang.hasOwnProperty(oButton, o)) {
1176 if (!this._toolbarConfigs[o]) {
1177 _oButton[o] = oButton[o];
1181 if (oButton.type == 'select') {
1182 _oButton.type = 'menu';
1184 if (oButton.type == 'spin') {
1185 _oButton.type = 'push';
1187 if (_oButton.type == 'color') {
1188 if (YAHOO.widget.Overlay) {
1189 _oButton = this._makeColorButton(_oButton);
1194 if (_oButton.menu) {
1195 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1196 oButton.menu.showEvent.subscribe(function() {
1197 this._button = _oButton;
1200 for (var m = 0; m < _oButton.menu.length; m++) {
1201 if (!_oButton.menu[m].value) {
1202 _oButton.menu[m].value = _oButton.menu[m].text;
1205 if (this.browser.webkit) {
1206 _oButton.focusmenu = false;
1213 //Add to .get('buttons') manually
1214 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1216 var tmp = new this.buttonType(_oButton);
1217 tmp.get('element').tabIndex = '-1';
1218 tmp.get('element').setAttribute('role', 'button');
1219 tmp._selected = true;
1221 if (this.get('disabled')) {
1222 //Toolbar is disabled, disable the new button too!
1223 tmp.set('disabled', true);
1226 oButton.id = tmp.get('id');
1228 YAHOO.log('Button created (' + oButton.type + ')', 'info', 'Toolbar');
1231 var el = tmp.get('element');
1234 nextSib = after.get('element').nextSibling;
1235 } else if (after.nextSibling) {
1236 nextSib = after.nextSibling;
1239 nextSib.parentNode.insertBefore(el, nextSib);
1242 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1244 var icon = document.createElement('span');
1245 icon.className = this.CLASS_PREFIX + '-icon';
1246 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1247 if (tmp._button.tagName.toLowerCase() == 'button') {
1248 tmp.get('element').setAttribute('unselectable', 'on');
1249 //Replace the Button HTML Element with an a href if it exists
1250 var a = document.createElement('a');
1251 a.innerHTML = tmp._button.innerHTML;
1254 Event.on(a, 'click', function(ev) {
1255 Event.stopEvent(ev);
1257 tmp._button.parentNode.replaceChild(a, tmp._button);
1261 if (oButton.type == 'select') {
1262 if (tmp._button.tagName.toLowerCase() == 'select') {
1263 icon.parentNode.removeChild(icon);
1264 var iel = tmp._button,
1265 parEl = tmp.get('element');
1266 parEl.parentNode.replaceChild(iel, parEl);
1267 //The 'element' value is currently the orphaned element
1268 //In order for "destroy" to execute we need to get('element') to reference the correct node.
1269 //I'm not sure if there is a direct approach to setting this value.
1270 tmp._configs.element.value = iel;
1272 //Don't put a class on it if it's a real select element
1273 tmp.addClass(this.CLASS_PREFIX + '-select');
1276 if (oButton.type == 'spin') {
1277 if (!Lang.isArray(oButton.range)) {
1278 oButton.range = [ 10, 100 ];
1280 this._makeSpinButton(tmp, oButton);
1282 tmp.get('element').setAttribute('title', tmp.get('label'));
1283 if (oButton.type != 'spin') {
1284 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1285 var showPicker = function(ev) {
1287 if (ev.keyCode && (ev.keyCode == 9)) {
1291 if (this._colorPicker) {
1292 this._colorPicker._button = oButton.value;
1294 var menuEL = tmp.getMenu().element;
1295 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1296 tmp.getMenu().show();
1298 tmp.getMenu().hide();
1301 YAHOO.util.Event.stopEvent(ev);
1303 tmp.on('mousedown', showPicker, oButton, this);
1304 tmp.on('keydown', showPicker, oButton, this);
1306 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1307 tmp.on('keypress', this._buttonClick, oButton, this);
1308 tmp.on('mousedown', function(ev) {
1309 YAHOO.util.Event.stopEvent(ev);
1310 this._buttonClick(ev, oButton);
1312 tmp.on('click', function(ev) {
1313 YAHOO.util.Event.stopEvent(ev);
1316 //Stop the mousedown event so we can trap the selection in the editor!
1317 tmp.on('mousedown', function(ev) {
1318 YAHOO.util.Event.stopEvent(ev);
1320 tmp.on('click', function(ev) {
1321 YAHOO.util.Event.stopEvent(ev);
1323 tmp.on('change', function(ev) {
1325 if (!oButton.menucmd) {
1326 oButton.menucmd = oButton.value;
1328 oButton.value = ev.value;
1329 this._buttonClick(ev, oButton);
1334 //Hijack the mousedown event in the menu and make it fire a button click..
1335 tmp.on('appendTo', function() {
1337 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1338 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1339 YAHOO.log('mouseDownEvent', 'warn', 'Toolbar');
1340 var oMenu = args[1];
1341 YAHOO.util.Event.stopEvent(args[0]);
1342 tmp._onMenuClick(args[0], tmp);
1343 if (!oButton.menucmd) {
1344 oButton.menucmd = oButton.value;
1346 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1347 self._buttonClick.call(self, args[1], oButton);
1351 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1352 YAHOO.log('clickEvent', 'warn', 'Toolbar');
1353 YAHOO.util.Event.stopEvent(args[0]);
1355 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1356 YAHOO.log('mouseUpEvent', 'warn', 'Toolbar');
1357 YAHOO.util.Event.stopEvent(args[0]);
1364 //Stop the mousedown event so we can trap the selection in the editor!
1365 tmp.on('mousedown', function(ev) {
1366 YAHOO.util.Event.stopEvent(ev);
1368 tmp.on('click', function(ev) {
1369 YAHOO.util.Event.stopEvent(ev);
1372 if (this.browser.ie) {
1374 //Add a couple of new events for IE
1375 tmp.DOM_EVENTS.focusin = true;
1376 tmp.DOM_EVENTS.focusout = true;
1378 //Stop them so we don't loose focus in the Editor
1379 tmp.on('focusin', function(ev) {
1380 YAHOO.util.Event.stopEvent(ev);
1383 tmp.on('focusout', function(ev) {
1384 YAHOO.util.Event.stopEvent(ev);
1386 tmp.on('click', function(ev) {
1387 YAHOO.util.Event.stopEvent(ev);
1391 if (this.browser.webkit) {
1392 //This will keep the document from gaining focus and the editor from loosing it..
1393 //Forcefully remove the focus calls in button!
1394 tmp.hasFocus = function() {
1398 this._buttonList[this._buttonList.length] = tmp;
1399 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1400 if (Lang.isArray(oButton.menu)) {
1401 YAHOO.log('Button type is (' + oButton.type + '), doing extra renderer work.', 'info', 'Toolbar');
1402 var menu = tmp.getMenu();
1403 if (menu && menu.renderEvent) {
1404 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1405 if (oButton.renderer) {
1406 menu.renderEvent.subscribe(oButton.renderer, tmp);
1415 * @method addSeparator
1416 * @description Add a new button separator to the toolbar.
1417 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1418 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1420 addSeparator: function(cont, after) {
1421 if (!this.get('element')) {
1422 this._queue[this._queue.length] = ['addSeparator', arguments];
1425 var sepCont = ((cont) ? cont : this.get('cont'));
1426 if (!this.get('element')) {
1427 this._queue[this._queue.length] = ['addSeparator', arguments];
1430 if (this._sepCount === null) {
1434 YAHOO.log('Separator does not yet exist, creating', 'info', 'Toolbar');
1435 this._sep = document.createElement('SPAN');
1436 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1437 this._sep.innerHTML = '|';
1439 YAHOO.log('Separator does exist, cloning', 'info', 'Toolbar');
1440 var _sep = this._sep.cloneNode(true);
1442 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1446 nextSib = after.get('element').nextSibling;
1447 } else if (after.nextSibling) {
1448 nextSib = after.nextSibling;
1453 if (nextSib == after) {
1454 nextSib.parentNode.appendChild(_sep);
1456 nextSib.parentNode.insertBefore(_sep, nextSib);
1460 sepCont.appendChild(_sep);
1465 * @method _createColorPicker
1467 * @description Creates the core DOM reference to the color picker menu item.
1468 * @param {String} id the id of the toolbar to prefix this DOM container with.
1470 _createColorPicker: function(id) {
1471 if (Dom.get(id + '_colors')) {
1472 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1474 var picker = document.createElement('div');
1475 picker.className = 'yui-toolbar-colors';
1476 picker.id = id + '_colors';
1477 picker.style.display = 'none';
1478 Event.on(window, 'load', function() {
1479 document.body.appendChild(picker);
1482 this._colorPicker = picker;
1485 for (var i in this._colorData) {
1486 if (Lang.hasOwnProperty(this._colorData, i)) {
1487 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1490 html += '<span><em>X</em><strong></strong></span>';
1491 window.setTimeout(function() {
1492 picker.innerHTML = html;
1495 Event.on(picker, 'mouseover', function(ev) {
1496 var picker = this._colorPicker;
1497 var em = picker.getElementsByTagName('em')[0];
1498 var strong = picker.getElementsByTagName('strong')[0];
1499 var tar = Event.getTarget(ev);
1500 if (tar.tagName.toLowerCase() == 'a') {
1501 em.style.backgroundColor = tar.style.backgroundColor;
1502 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1505 Event.on(picker, 'focus', function(ev) {
1506 Event.stopEvent(ev);
1508 Event.on(picker, 'click', function(ev) {
1509 Event.stopEvent(ev);
1511 Event.on(picker, 'mousedown', function(ev) {
1512 Event.stopEvent(ev);
1513 var tar = Event.getTarget(ev);
1514 if (tar.tagName.toLowerCase() == 'a') {
1515 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1516 if (retVal !== false) {
1518 color: tar.innerHTML,
1519 colorName: this._colorData['#' + tar.innerHTML],
1520 value: this._colorPicker._button
1523 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1525 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1530 * @method _resetColorPicker
1532 * @description Clears the currently selected color or mouseover color in the color picker.
1534 _resetColorPicker: function() {
1535 var em = this._colorPicker.getElementsByTagName('em')[0];
1536 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1537 em.style.backgroundColor = 'transparent';
1538 strong.innerHTML = '';
1541 * @method _makeColorButton
1543 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1544 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1546 _makeColorButton: function(_oButton) {
1547 if (!this._colorPicker) {
1548 this._createColorPicker(this.get('id'));
1550 _oButton.type = 'color';
1551 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1552 _oButton.menu.setBody('');
1553 _oButton.menu.render(this.get('cont'));
1554 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1555 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1556 _oButton.menu.beforeShowEvent.subscribe(function() {
1557 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1558 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1559 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1560 this._resetColorPicker();
1561 var _p = this._colorPicker;
1562 if (_p.parentNode) {
1563 _p.parentNode.removeChild(_p);
1565 _oButton.menu.setBody('');
1566 _oButton.menu.appendToBody(_p);
1567 this._colorPicker.style.display = 'block';
1573 * @method _makeSpinButton
1574 * @description Create a button similar to an OS Spin button.. It has an up/down arrow combo to scroll through a range of int values.
1575 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1576 * @param {Object} oButton Object literal containing the buttons initial config
1578 _makeSpinButton: function(_button, oButton) {
1579 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1581 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1582 range = oButton.range,
1583 _b1 = document.createElement('a'),
1584 _b2 = document.createElement('a');
1587 _b1.tabIndex = '-1';
1588 _b2.tabIndex = '-1';
1590 //Setup the up and down arrows
1591 _b1.className = 'up';
1592 _b1.title = this.STR_SPIN_UP;
1593 _b1.innerHTML = this.STR_SPIN_UP;
1594 _b2.className = 'down';
1595 _b2.title = this.STR_SPIN_DOWN;
1596 _b2.innerHTML = this.STR_SPIN_DOWN;
1598 //Append them to the container
1599 _par.appendChild(_b1);
1600 _par.appendChild(_b2);
1602 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1603 _button.set('title', label);
1605 var cleanVal = function(value) {
1606 value = ((value < range[0]) ? range[0] : value);
1607 value = ((value > range[1]) ? range[1] : value);
1611 var br = this.browser;
1613 var strLabel = this.STR_SPIN_LABEL;
1614 if (this._titlebar && this._titlebar.firstChild) {
1615 tbar = this._titlebar.firstChild;
1618 var _intUp = function(ev) {
1619 YAHOO.util.Event.stopEvent(ev);
1620 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1621 var value = parseInt(_button.get('label'), 10);
1623 value = cleanVal(value);
1624 _button.set('label', ''+value);
1625 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1626 _button.set('title', label);
1627 if (!br.webkit && tbar) {
1628 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1631 self._buttonClick(ev, oButton);
1635 var _intDown = function(ev) {
1636 YAHOO.util.Event.stopEvent(ev);
1637 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1638 var value = parseInt(_button.get('label'), 10);
1640 value = cleanVal(value);
1642 _button.set('label', ''+value);
1643 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1644 _button.set('title', label);
1645 if (!br.webkit && tbar) {
1646 //tbar.focus(); //We do this for accessibility, on the re-focus of the element, a screen reader will re-read the title that was just changed
1649 self._buttonClick(ev, oButton);
1653 var _intKeyUp = function(ev) {
1654 if (ev.keyCode == 38) {
1656 } else if (ev.keyCode == 40) {
1658 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1660 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1665 //Handle arrow keys..
1666 _button.on('keydown', _intKeyUp, this, true);
1668 //Listen for the click on the up button and act on it
1669 //Listen for the click on the down button and act on it
1670 Event.on(_b1, 'mousedown',function(ev) {
1671 Event.stopEvent(ev);
1673 Event.on(_b2, 'mousedown', function(ev) {
1674 Event.stopEvent(ev);
1676 Event.on(_b1, 'click', _intUp, this, true);
1677 Event.on(_b2, 'click', _intDown, this, true);
1681 * @method _buttonClick
1682 * @description Click handler for all buttons in the toolbar.
1683 * @param {String} ev The event that was passed in.
1684 * @param {Object} info Object literal of information about the button that was clicked.
1686 _buttonClick: function(ev, info) {
1689 if (ev && ev.type == 'keypress') {
1690 if (ev.keyCode == 9) {
1692 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1699 var fireNextEvent = true,
1702 info.isSelected = this.isSelected(info.id);
1705 YAHOO.log('fireEvent::' + info.value + 'Click', 'info', 'Toolbar');
1706 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1707 if (retValue === false) {
1708 fireNextEvent = false;
1712 if (info.menucmd && fireNextEvent) {
1713 YAHOO.log('fireEvent::' + info.menucmd + 'Click', 'info', 'Toolbar');
1714 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1715 if (retValue === false) {
1716 fireNextEvent = false;
1719 if (fireNextEvent) {
1720 YAHOO.log('fireEvent::buttonClick', 'info', 'Toolbar');
1721 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1724 if (info.type == 'select') {
1725 var button = this.getButtonById(info.id);
1726 if (button.buttonType == 'rich') {
1727 var txt = info.value;
1728 for (var i = 0; i < info.menu.length; i++) {
1729 if (info.menu[i].value == info.value) {
1730 txt = info.menu[i].text;
1734 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1735 var _items = button.getMenu().getItems();
1736 for (var m = 0; m < _items.length; m++) {
1737 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1738 _items[m].cfg.setProperty('checked', true);
1740 _items[m].cfg.setProperty('checked', false);
1746 Event.stopEvent(ev);
1753 * @description Flag to determine if the arrow nav listeners have been attached
1759 * @property _navCounter
1760 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1766 * @method _navigateButtons
1767 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1768 * @param {Event} ev The Key Event
1770 _navigateButtons: function(ev) {
1771 switch (ev.keyCode) {
1774 if (ev.keyCode == 37) {
1779 if (this._navCounter > (this._buttonList.length - 1)) {
1780 this._navCounter = 0;
1782 if (this._navCounter < 0) {
1783 this._navCounter = (this._buttonList.length - 1);
1785 if (this._buttonList[this._navCounter]) {
1786 var el = this._buttonList[this._navCounter].get('element');
1787 if (this.browser.ie) {
1788 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1790 if (this._buttonList[this._navCounter].get('disabled')) {
1791 this._navigateButtons(ev);
1801 * @method _handleFocus
1802 * @description Sets up the listeners for the arrow key navigation
1804 _handleFocus: function() {
1805 if (!this._keyNav) {
1806 var ev = 'keypress';
1807 if (this.browser.ie) {
1810 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1811 this._keyNav = true;
1812 this._navCounter = -1;
1816 * @method getButtonById
1817 * @description Gets a button instance from the toolbar by is Dom id.
1818 * @param {String} id The Dom id to query for.
1819 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1821 getButtonById: function(id) {
1822 var len = this._buttonList.length;
1823 for (var i = 0; i < len; i++) {
1824 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1825 return this._buttonList[i];
1831 * @method getButtonByValue
1832 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1833 * @param {String} value The button value to query for.
1834 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1836 getButtonByValue: function(value) {
1837 var _buttons = this.get('buttons');
1841 var len = _buttons.length;
1842 for (var i = 0; i < len; i++) {
1843 if (_buttons[i].group !== undefined) {
1844 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1845 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1846 return this.getButtonById(_buttons[i].buttons[m].id);
1848 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1849 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1850 if (_buttons[i].buttons[m].menu[s].value == value) {
1851 return this.getButtonById(_buttons[i].buttons[m].id);
1857 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1858 return this.getButtonById(_buttons[i].id);
1860 if (_buttons[i].menu) { //Menu Button, loop through the values
1861 for (var j = 0; j < _buttons[i].menu.length; j++) {
1862 if (_buttons[i].menu[j].value == value) {
1863 return this.getButtonById(_buttons[i].id);
1872 * @method getButtonByIndex
1873 * @description Gets a button instance from the toolbar by is index in _buttonList.
1874 * @param {Number} index The index of the button in _buttonList.
1875 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1877 getButtonByIndex: function(index) {
1878 if (this._buttonList[index]) {
1879 return this._buttonList[index];
1885 * @method getButtons
1886 * @description Returns an array of buttons in the current toolbar
1889 getButtons: function() {
1890 return this._buttonList;
1893 * @method disableButton
1894 * @description Disables a button in the toolbar.
1895 * @param {String/Number} id Disable a button by it's id, index or value.
1898 disableButton: function(id) {
1899 var button = getButton.call(this, id);
1901 button.set('disabled', true);
1907 * @method enableButton
1908 * @description Enables a button in the toolbar.
1909 * @param {String/Number} id Enable a button by it's id, index or value.
1912 enableButton: function(id) {
1913 if (this.get('disabled')) {
1916 var button = getButton.call(this, id);
1918 if (button.get('disabled')) {
1919 button.set('disabled', false);
1926 * @method isSelected
1927 * @description Tells if a button is selected or not.
1928 * @param {String/Number} id A button by it's id, index or value.
1931 isSelected: function(id) {
1932 var button = getButton.call(this, id);
1934 return button._selected;
1939 * @method selectButton
1940 * @description Selects a button in the toolbar.
1941 * @param {String/Number} id Select a button by it's id, index or value.
1942 * @param {String} value If this is a Menu Button, check this item in the menu
1945 selectButton: function(id, value) {
1946 var button = getButton.call(this, id);
1948 button.addClass('yui-button-selected');
1949 button.addClass('yui-button-' + button.get('value') + '-selected');
1950 button._selected = true;
1952 if (button.buttonType == 'rich') {
1953 var _items = button.getMenu().getItems();
1954 for (var m = 0; m < _items.length; m++) {
1955 if (_items[m].value == value) {
1956 _items[m].cfg.setProperty('checked', true);
1957 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1959 _items[m].cfg.setProperty('checked', false);
1969 * @method deselectButton
1970 * @description Deselects a button in the toolbar.
1971 * @param {String/Number} id Deselect a button by it's id, index or value.
1974 deselectButton: function(id) {
1975 var button = getButton.call(this, id);
1977 button.removeClass('yui-button-selected');
1978 button.removeClass('yui-button-' + button.get('value') + '-selected');
1979 button.removeClass('yui-button-hover');
1980 button._selected = false;
1986 * @method deselectAllButtons
1987 * @description Deselects all buttons in the toolbar.
1990 deselectAllButtons: function() {
1991 var len = this._buttonList.length;
1992 for (var i = 0; i < len; i++) {
1993 this.deselectButton(this._buttonList[i]);
1997 * @method disableAllButtons
1998 * @description Disables all buttons in the toolbar.
2001 disableAllButtons: function() {
2002 if (this.get('disabled')) {
2005 var len = this._buttonList.length;
2006 for (var i = 0; i < len; i++) {
2007 this.disableButton(this._buttonList[i]);
2011 * @method enableAllButtons
2012 * @description Enables all buttons in the toolbar.
2015 enableAllButtons: function() {
2016 if (this.get('disabled')) {
2019 var len = this._buttonList.length;
2020 for (var i = 0; i < len; i++) {
2021 this.enableButton(this._buttonList[i]);
2025 * @method resetAllButtons
2026 * @description Resets all buttons to their initial state.
2027 * @param {Object} _ex Except these buttons
2030 resetAllButtons: function(_ex) {
2031 if (!Lang.isObject(_ex)) {
2034 if (this.get('disabled') || !this._buttonList) {
2037 var len = this._buttonList.length;
2038 for (var i = 0; i < len; i++) {
2039 var _button = this._buttonList[i];
2041 var disabled = _button._configs.disabled._initialConfig.value;
2042 if (_ex[_button.get('id')]) {
2043 this.enableButton(_button);
2044 this.selectButton(_button);
2047 this.disableButton(_button);
2049 this.enableButton(_button);
2051 this.deselectButton(_button);
2057 * @method destroyButton
2058 * @description Destroy a button in the toolbar.
2059 * @param {String/Number} id Destroy a button by it's id or index.
2062 destroyButton: function(id) {
2063 var button = getButton.call(this, id);
2065 var thisID = button.get('id'),
2066 new_list = [], i = 0,
2067 len = this._buttonList.length;
2071 for (i = 0; i < len; i++) {
2072 if (this._buttonList[i].get('id') != thisID) {
2073 new_list[new_list.length]= this._buttonList[i];
2077 this._buttonList = new_list;
2084 * @description Destroys the toolbar, all of it's elements and objects.
2087 destroy: function() {
2088 var len = this._configuredButtons.length, j, i;
2089 for(b = 0; b < len; b++) {
2090 this.destroyButton(this._configuredButtons[b]);
2093 this._configuredButtons = null;
2095 this.get('element').innerHTML = '';
2096 this.get('element').className = '';
2097 //Brutal Object Destroy
2099 if (Lang.hasOwnProperty(this, i)) {
2107 * @description Programatically collapse the toolbar.
2108 * @param {Boolean} collapse True to collapse, false to expand.
2110 collapse: function(collapse) {
2111 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2112 if (collapse === false) {
2113 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2115 Dom.removeClass(el[0], 'collapsed');
2116 el[0].title = this.STR_COLLAPSE;
2118 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2121 Dom.addClass(el[0], 'collapsed');
2122 el[0].title = this.STR_EXPAND;
2124 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2125 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2130 * @description Returns a string representing the toolbar.
2133 toString: function() {
2134 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2138 * @event buttonClick
2139 * @param {Object} o The object passed to this handler is the button config used to create the button.
2140 * @description Fires when any botton receives a click event. Passes back a single object representing the buttons config object. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2141 * @type YAHOO.util.CustomEvent
2145 * @param {Object} o The object passed to this handler is the button config used to create the button.
2146 * @description This is a special dynamic event that is created and dispatched based on the value property
2147 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2151 * { type: 'button', value: 'test', value: 'testButton' }
2154 * With the valueClick event you could subscribe to this buttons click event with this:
2155 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2156 * @type YAHOO.util.CustomEvent
2159 * @event toolbarExpanded
2160 * @description Fires when the toolbar is expanded via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2161 * @type YAHOO.util.CustomEvent
2164 * @event toolbarCollapsed
2165 * @description Fires when the toolbar is collapsed via the collapse button. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2166 * @type YAHOO.util.CustomEvent
2171 * @description <p>The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.</p>
2172 * @namespace YAHOO.widget
2173 * @requires yahoo, dom, element, event, toolbar
2174 * @optional animation, container_core, resize, dragdrop
2178 var Dom = YAHOO.util.Dom,
2179 Event = YAHOO.util.Event,
2181 Toolbar = YAHOO.widget.Toolbar;
2184 * The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.
2186 * @class SimpleEditor
2187 * @extends YAHOO.util.Element
2188 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2189 * @param {Object} attrs Object liternal containing configuration parameters.
2192 YAHOO.widget.SimpleEditor = function(el, attrs) {
2193 YAHOO.log('SimpleEditor Initalizing', 'info', 'SimpleEditor');
2196 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2197 Lang.augmentObject(o, el); //Break the config reference
2198 el = document.createElement('textarea');
2199 this.DOMReady = true;
2201 var c = Dom.get(o.container);
2204 document.body.appendChild(el);
2208 Lang.augmentObject(o, attrs); //Break the config reference
2217 if (Lang.isString(el)) {
2220 if (oConfig.attributes.id) {
2221 id = oConfig.attributes.id;
2223 this.DOMReady = true;
2224 id = Dom.generateId(el);
2227 oConfig.element = el;
2229 var element_cont = document.createElement('DIV');
2230 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2231 id: id + '_container'
2233 var div = document.createElement('div');
2234 Dom.addClass(div, 'first-child');
2235 oConfig.attributes.element_cont.appendChild(div);
2237 if (!oConfig.attributes.toolbar_cont) {
2238 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2239 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2240 div.appendChild(oConfig.attributes.toolbar_cont);
2242 var editorWrapper = document.createElement('DIV');
2243 div.appendChild(editorWrapper);
2244 oConfig.attributes.editor_wrapper = editorWrapper;
2246 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2250 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2253 * @property _resizeConfig
2254 * @description The default config for the Resize Utility
2266 * @method _setupResize
2267 * @description Creates the Resize instance and binds its events.
2269 _setupResize: function() {
2270 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2271 if (this.get('resize')) {
2273 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2274 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2275 this.resize.on('resize', function(args) {
2276 var anim = this.get('animate');
2277 this.set('animate', false);
2278 this.set('width', args.width + 'px');
2279 var h = args.height,
2280 th = (this.toolbar.get('element').clientHeight + 2),
2283 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2285 var newH = (h - th - dh);
2286 this.set('height', newH + 'px');
2287 this.get('element_cont').setStyle('height', '');
2288 this.set('animate', anim);
2294 * @description A reference to the Resize object
2295 * @type YAHOO.util.Resize
2301 * @description Sets up the DD instance used from the 'drag' config option.
2303 _setupDD: function() {
2304 if (!YAHOO.util.DD) { return false; }
2305 if (this.get('drag')) {
2306 YAHOO.log('Attaching DD instance to Editor', 'info', 'SimpleEditor');
2307 var d = this.get('drag'),
2309 if (d === 'proxy') {
2310 dd = YAHOO.util.DDProxy;
2313 this.dd = new dd(this.get('element_cont').get('element'));
2314 this.toolbar.addClass('draggable');
2315 this.dd.setHandleElId(this.toolbar._titlebar);
2320 * @description A reference to the DragDrop object.
2321 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2326 * @property _lastCommand
2327 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2331 _undoNodeChange: function() {},
2332 _storeUndo: function() {},
2336 * @description Checks a keyMap entry against a key event
2337 * @param {Object} k The _keyMap object
2338 * @param {Event} e The Mouse Event
2341 _checkKey: function(k, e) {
2343 if ((e.keyCode === k.key)) {
2344 if (k.mods && (k.mods.length > 0)) {
2346 for (var i = 0; i < k.mods.length; i++) {
2347 if (this.browser.mac) {
2348 if (k.mods[i] == 'ctrl') {
2352 if (e[k.mods[i] + 'Key'] === true) {
2356 if (val === k.mods.length) {
2363 //YAHOO.log('Shortcut Key Check: (' + k.key + ') return: ' + ret, 'info', 'SimpleEditor');
2369 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2370 * This entry shows that when key 87 (W) is found with the modifiers of shift and control, the window will close. You can customize this object to tweak keyboard shortcuts.
2371 * @type {Object/Mixed}
2380 mods: ['shift', 'ctrl']
2391 mods: ['shift', 'ctrl']
2395 mods: ['shift', 'ctrl']
2399 mods: ['shift', 'ctrl']
2403 mods: ['shift', 'ctrl']
2407 mods: ['shift', 'ctrl']
2411 mods: ['shift', 'ctrl']
2419 mods: ['shift', 'ctrl']
2423 mods: ['shift', 'ctrl']
2427 mods: ['shift', 'ctrl']
2431 mods: ['shift', 'ctrl']
2436 * @method _cleanClassName
2437 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2438 * @param {String} str The classname to clean up
2441 _cleanClassName: function(str) {
2442 return str.replace(/ /g, '-').toLowerCase();
2445 * @property _textarea
2446 * @description Flag to determine if we are using a textarea or an HTML Node.
2451 * @property _docType
2452 * @description The DOCTYPE to use in the editable container.
2455 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2457 * @property editorDirty
2458 * @description This flag will be set when certain things in the Editor happen. It is to be used by the developer to check to see if content has changed.
2463 * @property _defaultCSS
2464 * @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' }
2467 _defaultCSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } .warning-localfile { border-bottom: 1px dashed red !important; } .yui-busy { cursor: wait !important; } img.selected { border: 2px dotted #808080; } img { cursor: pointer !important; border: none; } body.ptags.webkit div.yui-wk-p { margin: 11px 0; } body.ptags.webkit div.yui-wk-div { margin: 0; }',
2469 * @property _defaultToolbar
2471 * @description Default toolbar config.
2474 _defaultToolbar: null,
2476 * @property _lastButton
2478 * @description The last button pressed, so we don't disable it.
2483 * @property _baseHREF
2485 * @description The base location of the editable page (this page) so that relative paths for image work.
2488 _baseHREF: function() {
2489 var href = document.location.href;
2490 if (href.indexOf('?') !== -1) { //Remove the query string
2491 href = href.substring(0, href.indexOf('?'));
2493 href = href.substring(0, href.lastIndexOf('/')) + '/';
2497 * @property _lastImage
2499 * @description Safari reference for the last image selected (for styling as selected).
2504 * @property _blankImageLoaded
2506 * @description Don't load the blank image more than once..
2509 _blankImageLoaded: null,
2511 * @property _fixNodesTimer
2513 * @description Holder for the fixNodes timer
2516 _fixNodesTimer: null,
2518 * @property _nodeChangeTimer
2520 * @description Holds a reference to the nodeChange setTimeout call
2523 _nodeChangeTimer: null,
2525 * @property _nodeChangeDelayTimer
2527 * @description Holds a reference to the nodeChangeDelay setTimeout call
2530 _nodeChangeDelayTimer: null,
2532 * @property _lastNodeChangeEvent
2534 * @description Flag to determine the last event that fired a node change
2537 _lastNodeChangeEvent: null,
2539 * @property _lastNodeChange
2541 * @description Flag to determine when the last node change was fired
2546 * @property _rendered
2548 * @description Flag to determine if editor has been rendered or not
2553 * @property DOMReady
2555 * @description Flag to determine if DOM is ready or not
2560 * @property _selection
2562 * @description Holder for caching iframe selections
2569 * @description DOM Element holder for the editor Mask when disabled
2574 * @property _showingHiddenElements
2576 * @description Status of the hidden elements button
2579 _showingHiddenElements: null,
2581 * @property currentWindow
2582 * @description A reference to the currently open EditorWindow
2585 currentWindow: null,
2587 * @property currentEvent
2588 * @description A reference to the current editor event
2593 * @property operaEvent
2595 * @description setTimeout holder for Opera and Image DoubleClick event..
2600 * @property currentFont
2601 * @description A reference to the last font selected from the Toolbar
2606 * @property currentElement
2607 * @description A reference to the current working element in the editor
2610 currentElement: null,
2613 * @description A reference to the dompath container for writing the current working dom path to.
2618 * @property beforeElement
2619 * @description A reference to the H2 placed before the editor for Accessibilty.
2622 beforeElement: null,
2624 * @property afterElement
2625 * @description A reference to the H2 placed after the editor for Accessibilty.
2630 * @property invalidHTML
2631 * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found. If you set the value of a key to "{ keepContents: true }", then the element will be replaced with a yui-non span to be filtered out when cleanHTML is called. The only tag that is ignored here is the span tag as it will force the Editor into a loop and freeze the browser. However.. all of these tags will be removed in the cleanHTML routine.
2649 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2650 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2655 * @property _contentTimer
2656 * @description setTimeout holder for documentReady check
2658 _contentTimer: null,
2661 * @property _contentTimerMax
2662 * @description The number of times the loaded content should be checked before giving up. Default: 500
2664 _contentTimerMax: 500,
2667 * @property _contentTimerCounter
2668 * @description Counter to check the number of times the body is polled for before giving up
2671 _contentTimerCounter: 0,
2674 * @property _disabled
2675 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2678 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2681 * @property _alwaysDisabled
2682 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2685 _alwaysDisabled: { undo: true, redo: true },
2688 * @property _alwaysEnabled
2689 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2692 _alwaysEnabled: { },
2695 * @property _semantic
2696 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2699 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2702 * @property _tag2cmd
2703 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2712 'sup': 'superscript',
2714 'img': 'insertimage',
2716 'ul' : 'insertunorderedlist',
2717 'ol' : 'insertorderedlist'
2721 * @private _createIframe
2722 * @description Creates the DOM and YUI Element for the iFrame editor area.
2723 * @param {String} id The string ID to prefix the iframe with
2724 * @return {Object} iFrame object
2726 _createIframe: function() {
2727 var ifrmDom = document.createElement('iframe');
2728 ifrmDom.id = this.get('id') + '_editor';
2736 allowTransparency: 'true',
2739 if (this.get('autoHeight')) {
2740 config.scrolling = 'no';
2742 for (var i in config) {
2743 if (Lang.hasOwnProperty(config, i)) {
2744 ifrmDom.setAttribute(i, config[i]);
2747 var isrc = 'javascript:;';
2748 if (this.browser.ie) {
2749 //isrc = 'about:blank';
2750 //TODO - Check this, I have changed it before..
2751 isrc = 'javascript:false;';
2753 ifrmDom.setAttribute('src', isrc);
2754 var ifrm = new YAHOO.util.Element(ifrmDom);
2755 ifrm.setStyle('visibility', 'hidden');
2759 * @private _isElement
2760 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2761 * @param {HTMLElement} el The element to check
2762 * @param {String} tag The tag that the element needs to be
2765 _isElement: function(el, tag) {
2766 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2769 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2775 * @private _hasParent
2776 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2777 * @param {HTMLElement} el The element to check
2778 * @param {String} tag The tag that the element needs to be
2779 * @return HTMLElement
2781 _hasParent: function(el, tag) {
2782 if (!el || !el.parentNode) {
2786 while (el.parentNode) {
2787 if (this._isElement(el, tag)) {
2790 if (el.parentNode) {
2801 * @description Get the Document of the IFRAME
2804 _getDoc: function() {
2807 if (this.get('iframe').get('element').contentWindow.document) {
2808 value = this.get('iframe').get('element').contentWindow.document;
2817 * @method _getWindow
2818 * @description Get the Window of the IFRAME
2821 _getWindow: function() {
2822 return this.get('iframe').get('element').contentWindow;
2826 * @description Attempt to set the focus of the iframes window.
2829 this._getWindow().focus();
2833 * @depreciated - This should not be used, moved to this.focus();
2834 * @method _focusWindow
2835 * @description Attempt to set the focus of the iframes window.
2837 _focusWindow: function() {
2838 YAHOO.log('_focusWindow: depreciated in favor of this.focus()', 'warn', 'Editor');
2843 * @method _hasSelection
2844 * @description Determines if there is a selection in the editor document.
2847 _hasSelection: function() {
2848 var sel = this._getSelection();
2849 var range = this._getRange();
2852 if (!sel || !range) {
2857 if (this.browser.ie || this.browser.opera) {
2865 if (this.browser.webkit) {
2866 if (sel+'' !== '') {
2870 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2879 * @method _getSelection
2880 * @description Handles the different selection objects across the A-Grade list.
2881 * @return {Object} Selection Object
2883 _getSelection: function() {
2885 if (this._getDoc() && this._getWindow()) {
2886 if (this._getDoc().selection) {
2887 _sel = this._getDoc().selection;
2889 _sel = this._getWindow().getSelection();
2891 //Handle Safari's lack of Selection Object
2892 if (this.browser.webkit) {
2893 if (_sel.baseNode) {
2894 this._selection = {};
2895 this._selection.baseNode = _sel.baseNode;
2896 this._selection.baseOffset = _sel.baseOffset;
2897 this._selection.extentNode = _sel.extentNode;
2898 this._selection.extentOffset = _sel.extentOffset;
2899 } else if (this._selection !== null) {
2900 _sel = this._getWindow().getSelection();
2901 _sel.setBaseAndExtent(
2902 this._selection.baseNode,
2903 this._selection.baseOffset,
2904 this._selection.extentNode,
2905 this._selection.extentOffset);
2906 this._selection = null;
2914 * @method _selectNode
2915 * @description Places the highlight around a given node
2916 * @param {HTMLElement} node The node to select
2918 _selectNode: function(node, collapse) {
2922 var sel = this._getSelection(),
2925 if (this.browser.ie) {
2926 try { //IE freaks out here sometimes..
2927 range = this._getDoc().body.createTextRange();
2928 range.moveToElementText(node);
2931 YAHOO.log('IE failed to select element: ' + node.tagName, 'warn', 'SimpleEditor');
2933 } else if (this.browser.webkit) {
2935 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2937 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2939 } else if (this.browser.opera) {
2940 sel = this._getWindow().getSelection();
2941 range = this._getDoc().createRange();
2942 range.selectNode(node);
2943 sel.removeAllRanges();
2944 sel.addRange(range);
2946 range = this._getDoc().createRange();
2947 range.selectNodeContents(node);
2948 sel.removeAllRanges();
2949 sel.addRange(range);
2951 //TODO - Check Performance
2957 * @description Handles the different range objects across the A-Grade list.
2958 * @return {Object} Range Object
2960 _getRange: function() {
2961 var sel = this._getSelection();
2967 if (this.browser.webkit && !sel.getRangeAt) {
2968 var _range = this._getDoc().createRange();
2970 _range.setStart(sel.anchorNode, sel.anchorOffset);
2971 _range.setEnd(sel.focusNode, sel.focusOffset);
2973 _range = this._getWindow().getSelection()+'';
2978 if (this.browser.ie || this.browser.opera) {
2980 return sel.createRange();
2986 if (sel.rangeCount > 0) {
2987 return sel.getRangeAt(0);
2993 * @method _setDesignMode
2994 * @description Sets the designMode property of the iFrame document's body.
2995 * @param {String} state This should be either on or off
2997 _setDesignMode: function(state) {
2998 if (this.get('setDesignMode')) {
3000 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on');
3006 * @method _toggleDesignMode
3007 * @description Toggles the designMode property of the iFrame document on and off.
3008 * @return {String} The state that it was set to.
3010 _toggleDesignMode: function() {
3011 YAHOO.log('It is not recommended to use this method and it will be depreciated.', 'warn', 'SimpleEditor');
3012 var _dMode = this._getDoc().designMode,
3013 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on');
3014 this._setDesignMode(_state);
3019 * @property _focused
3020 * @description Holder for trapping focus/blur state and prevent double events
3026 * @method _handleFocus
3027 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
3028 * @param {Event} e The DOM Event
3030 _handleFocus: function(e) {
3031 if (!this._focused) {
3032 //YAHOO.log('Editor Window Focused', 'info', 'SimpleEditor');
3033 this._focused = true;
3034 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
3039 * @method _handleBlur
3040 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
3041 * @param {Event} e The DOM Event
3043 _handleBlur: function(e) {
3044 if (this._focused) {
3045 //YAHOO.log('Editor Window Blurred', 'info', 'SimpleEditor');
3046 this._focused = false;
3047 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
3052 * @method _initEditorEvents
3053 * @description This method sets up the listeners on the Editors document.
3055 _initEditorEvents: function() {
3056 //Setup Listeners on iFrame
3057 var doc = this._getDoc(),
3058 win = this._getWindow();
3060 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
3061 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
3062 Event.on(doc, 'click', this._handleClick, this, true);
3063 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
3064 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
3065 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
3066 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
3067 /* TODO -- Everyone but Opera works here..
3068 Event.on(doc, 'paste', function() {
3069 YAHOO.log('PASTE', 'info', 'SimpleEditor');
3074 Event.on(win, 'focus', this._handleFocus, this, true);
3075 Event.on(win, 'blur', this._handleBlur, this, true);
3079 * @method _removeEditorEvents
3080 * @description This method removes the listeners on the Editors document (for disabling).
3082 _removeEditorEvents: function() {
3083 //Remove Listeners on iFrame
3084 var doc = this._getDoc(),
3085 win = this._getWindow();
3087 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3088 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3089 Event.removeListener(doc, 'click', this._handleClick, this, true);
3090 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3091 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3092 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3093 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3096 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3097 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3099 _fixWebkitDivs: function() {
3100 if (this.browser.webkit) {
3101 var divs = this._getDoc().body.getElementsByTagName('div');
3102 Dom.addClass(divs, 'yui-wk-div');
3107 * @method _initEditor
3108 * @param {Boolean} raw Don't add events.
3109 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3111 _initEditor: function(raw) {
3112 if (this._editorInit) {
3115 this._editorInit = true;
3116 if (this.browser.ie) {
3117 this._getDoc().body.style.margin = '0';
3119 if (!this.get('disabled')) {
3120 this._setDesignMode('on');
3121 this._contentTimerCounter = 0;
3123 if (!this._getDoc().body) {
3124 YAHOO.log('Body is null, check again', 'error', 'SimpleEditor');
3125 this._contentTimerCounter = 0;
3126 this._editorInit = false;
3127 this._checkLoaded();
3131 YAHOO.log('editorLoaded', 'info', 'SimpleEditor');
3133 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3135 if (!this.get('disabled')) {
3136 this._initEditorEvents();
3137 this.toolbar.set('disabled', false);
3141 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this });
3143 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3145 this._fixWebkitDivs();
3146 if (this.get('dompath')) {
3147 YAHOO.log('Delayed DomPath write', 'info', 'SimpleEditor');
3149 setTimeout(function() {
3150 self._writeDomPath.call(self);
3151 self._setupResize.call(self);
3155 for (var i in this.browser) {
3156 if (this.browser[i]) {
3160 if (this.get('ptags')) {
3163 Dom.addClass(this._getDoc().body, br.join(' '));
3164 this.nodeChange(true);
3168 * @method _checkLoaded
3169 * @param {Boolean} raw Don't add events.
3170 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3172 _checkLoaded: function(raw) {
3173 this._editorInit = false;
3174 this._contentTimerCounter++;
3175 if (this._contentTimer) {
3176 clearTimeout(this._contentTimer);
3178 if (this._contentTimerCounter > this._contentTimerMax) {
3179 YAHOO.log('ERROR: Body Did Not load', 'error', 'SimpleEditor');
3184 if (this._getDoc() && this._getDoc().body) {
3185 if (this.browser.ie) {
3186 if (this._getDoc().body.readyState == 'complete') {
3190 if (this._getDoc().body._rteLoaded === true) {
3197 YAHOO.log('checking body (e)' + e, 'error', 'SimpleEditor');
3200 if (init === true) {
3201 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3202 YAHOO.log('Firing _initEditor', 'info', 'SimpleEditor');
3203 this._initEditor(raw);
3206 this._contentTimer = setTimeout(function() {
3207 self._checkLoaded.call(self, raw);
3213 * @method _setInitialContent
3214 * @param {Boolean} raw Don't add events.
3215 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3217 _setInitialContent: function(raw) {
3218 YAHOO.log('Populating editor body with contents of the text area', 'info', 'SimpleEditor');
3220 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3227 var html = Lang.substitute(this.get('html'), {
3228 TITLE: this.STR_TITLE,
3229 CONTENT: this._cleanIncomingHTML(value),
3230 CSS: this.get('css'),
3231 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3232 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3236 html = html.replace(/RIGHT_BRACKET/gi, '{');
3237 html = html.replace(/LEFT_BRACKET/gi, '}');
3239 if (document.compatMode != 'BackCompat') {
3240 YAHOO.log('Adding Doctype to editable area', 'info', 'SimpleEditor');
3241 html = this._docType + "\n" + html;
3243 YAHOO.log('DocType skipped because we are in BackCompat Mode.', 'warn', 'SimpleEditor');
3246 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3247 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3250 if (this.browser.air) {
3251 doc = this._getDoc().implementation.createHTMLDocument();
3252 var origDoc = this._getDoc();
3258 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3259 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3260 origDoc.body._rteLoaded = true;
3262 doc = this._getDoc();
3268 YAHOO.log('Setting doc failed.. (_setInitialContent)', 'error', 'SimpleEditor');
3269 //Safari will only be here if we are hidden
3273 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3274 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3276 this.get('iframe').setStyle('visibility', '');
3278 this._checkLoaded(raw);
3283 * @method _setMarkupType
3284 * @param {String} action The action to take. Possible values are: css, default or semantic
3285 * @description This method will turn on/off the useCSS execCommand.
3287 _setMarkupType: function(action) {
3288 switch (this.get('markup')) {
3290 this._setEditorStyle(true);
3293 this._setEditorStyle(false);
3297 if (this._semantic[action]) {
3298 this._setEditorStyle(false);
3300 this._setEditorStyle(true);
3306 * Set the editor to use CSS instead of HTML
3307 * @param {Booleen} stat True/False
3309 _setEditorStyle: function(stat) {
3311 this._getDoc().execCommand('useCSS', false, !stat);
3317 * @method _getSelectedElement
3318 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3319 * @return {HTMLElement} The currently selected element.
3321 _getSelectedElement: function() {
3322 var doc = this._getDoc(),
3328 if (this.browser.ie) {
3329 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3330 range = this._getRange();
3332 elm = range.item ? range.item(0) : range.parentElement();
3333 if (this._hasSelection()) {
3335 //WTF.. Why can't I get an element reference here?!??!
3337 if (elm === doc.body) {
3341 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3342 elm = Event.getTarget(this.currentEvent);
3345 sel = this._getSelection();
3346 range = this._getRange();
3348 if (!sel || !range) {
3352 if (!this._hasSelection() && this.browser.webkit3) {
3355 if (this.browser.gecko) {
3357 if (range.startContainer) {
3358 if (range.startContainer.nodeType === 3) {
3359 elm = range.startContainer.parentNode;
3360 } else if (range.startContainer.nodeType === 1) {
3361 elm = range.startContainer;
3364 if (this.currentEvent) {
3365 var tar = Event.getTarget(this.currentEvent);
3366 if (!this._isElement(tar, 'html')) {
3376 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3377 if (sel.anchorNode.parentNode) { //next check parentNode
3378 elm = sel.anchorNode.parentNode;
3380 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3381 elm = sel.anchorNode.nextSibling;
3384 if (this._isElement(elm, 'br')) {
3388 elm = range.commonAncestorContainer;
3389 if (!range.collapsed) {
3390 if (range.startContainer == range.endContainer) {
3391 if (range.startOffset - range.endOffset < 2) {
3392 if (range.startContainer.hasChildNodes()) {
3393 elm = range.startContainer.childNodes[range.startOffset];
3402 if (this.currentEvent !== null) {
3404 switch (this.currentEvent.type) {
3408 if (this.browser.webkit) {
3409 elm = Event.getTarget(this.currentEvent);
3417 YAHOO.log('Firefox 1.5 errors here: ' + e, 'error', 'SimpleEditor');
3419 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3420 //TODO is this still needed?
3421 //elm = this.currentElement[0];
3425 if (this.browser.opera || this.browser.webkit) {
3426 if (this.currentEvent && !elm) {
3427 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3430 if (!elm || !elm.tagName) {
3433 if (this._isElement(elm, 'html')) {
3434 //Safari sometimes gives us the HTML node back..
3437 if (this._isElement(elm, 'body')) {
3438 //make sure that body means this body not the parent..
3441 if (elm && !elm.parentNode) { //Not in document
3444 if (elm === undefined) {
3451 * @method _getDomPath
3452 * @description This method will attempt to build the DOM path from the currently selected element.
3453 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3454 * @return {Array} An array of node references that will create the DOM Path.
3456 _getDomPath: function(el) {
3458 el = this._getSelectedElement();
3461 while (el !== null) {
3462 if (el.ownerDocument != this._getDoc()) {
3466 //Check to see if we get el.nodeName and nodeType
3467 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3468 domPath[domPath.length] = el;
3471 if (this._isElement(el, 'body')) {
3477 if (domPath.length === 0) {
3478 if (this._getDoc() && this._getDoc().body) {
3479 domPath[0] = this._getDoc().body;
3482 return domPath.reverse();
3486 * @method _writeDomPath
3487 * @description Write the current DOM path out to the dompath container below the editor.
3489 _writeDomPath: function() {
3490 var path = this._getDomPath(),
3495 for (var i = 0; i < path.length; i++) {
3496 var tag = path[i].tagName.toLowerCase();
3497 if ((tag == 'ol') && (path[i].type)) {
3498 tag += ':' + path[i].type;
3500 if (Dom.hasClass(path[i], 'yui-tag')) {
3501 tag = path[i].getAttribute('tag');
3503 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3505 case 'b': tag = 'strong'; break;
3506 case 'i': tag = 'em'; break;
3509 if (!Dom.hasClass(path[i], 'yui-non')) {
3510 if (Dom.hasClass(path[i], 'yui-tag')) {
3513 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3514 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3517 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3524 if (path[i].getAttribute('href', 2)) {
3525 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3529 var h = path[i].height;
3530 var w = path[i].width;
3531 if (path[i].style.height) {
3532 h = parseInt(path[i].style.height, 10);
3534 if (path[i].style.width) {
3535 w = parseInt(path[i].style.width, 10);
3537 pathStr += '(' + w + 'x' + h + ')';
3541 if (pathStr.length > 10) {
3542 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3544 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3546 pathArr[pathArr.length] = pathStr;
3549 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3550 //Prevent flickering
3551 if (this.dompath.innerHTML != str) {
3552 this.dompath.innerHTML = str;
3558 * @description Fix href and imgs as well as remove invalid HTML.
3560 _fixNodes: function() {
3562 var doc = this._getDoc(),
3565 for (var v in this.invalidHTML) {
3566 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3567 if (v.toLowerCase() != 'span') {
3568 var tags = doc.body.getElementsByTagName(v);
3570 for (var i = 0; i < tags.length; i++) {
3577 for (var h = 0; h < els.length; h++) {
3578 if (els[h].parentNode) {
3579 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3580 this._swapEl(els[h], 'span', function(el) {
3581 el.className = 'yui-non';
3584 els[h].parentNode.removeChild(els[h]);
3588 var imgs = this._getDoc().getElementsByTagName('img');
3589 Dom.addClass(imgs, 'yui-img');
3594 * @method _isNonEditable
3595 * @param Event ev The Dom event being checked
3596 * @description Method is called at the beginning of all event handlers to check if this element or a parent element has the class yui-noedit (this.CLASS_NOEDIT) applied.
3597 * If it does, then this method will stop the event and return true. The event handlers will then return false and stop the nodeChange from occuring. This method will also
3598 * disable and enable the Editor's toolbar based on the noedit state.
3601 _isNonEditable: function(ev) {
3602 if (this.get('allowNoEdit')) {
3603 var el = Event.getTarget(ev);
3604 if (this._isElement(el, 'html')) {
3607 var path = this._getDomPath(el);
3608 for (var i = (path.length - 1); i > -1; i--) {
3609 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3610 //if (this.toolbar.get('disabled') === false) {
3611 // this.toolbar.set('disabled', true);
3614 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3617 Event.stopEvent(ev);
3618 YAHOO.log('CLASS_NOEDIT found in DOM Path, stopping event', 'info', 'SimpleEditor');
3622 //if (this.toolbar.get('disabled') === true) {
3623 //Should only happen once..
3624 //this.toolbar.set('disabled', false);
3626 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3634 * @method _setCurrentEvent
3635 * @param {Event} ev The event to cache
3636 * @description Sets the current event property
3638 _setCurrentEvent: function(ev) {
3639 this.currentEvent = ev;
3643 * @method _handleClick
3644 * @param {Event} ev The event we are working on.
3645 * @description Handles all click events inside the iFrame document.
3647 _handleClick: function(ev) {
3648 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3649 if (ret === false) {
3652 if (this._isNonEditable(ev)) {
3655 this._setCurrentEvent(ev);
3656 if (this.currentWindow) {
3659 if (this.currentWindow) {
3662 if (this.browser.webkit) {
3663 var tar =Event.getTarget(ev);
3664 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3665 Event.stopEvent(ev);
3671 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3675 * @method _handleMouseUp
3676 * @param {Event} ev The event we are working on.
3677 * @description Handles all mouseup events inside the iFrame document.
3679 _handleMouseUp: function(ev) {
3680 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3681 if (ret === false) {
3684 if (this._isNonEditable(ev)) {
3687 //Don't set current event for mouseup.
3688 //It get's fired after a menu is closed and gives up a bogus event to work with
3689 //this._setCurrentEvent(ev);
3691 if (this.browser.opera) {
3693 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3695 * @description This work around traps the MouseUp event and sets a timer to check if another MouseUp event fires in so many seconds. If another event is fired, they we internally fire the DoubleClick event.
3697 var sel = Event.getTarget(ev);
3698 if (this._isElement(sel, 'img')) {
3700 if (this.operaEvent) {
3701 clearTimeout(this.operaEvent);
3702 this.operaEvent = null;
3703 this._handleDoubleClick(ev);
3705 this.operaEvent = window.setTimeout(function() {
3706 self.operaEvent = false;
3711 //This will stop Safari from selecting the entire document if you select all the text in the editor
3712 if (this.browser.webkit || this.browser.opera) {
3713 if (this.browser.webkit) {
3714 Event.stopEvent(ev);
3718 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3722 * @method _handleMouseDown
3723 * @param {Event} ev The event we are working on.
3724 * @description Handles all mousedown events inside the iFrame document.
3726 _handleMouseDown: function(ev) {
3727 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3728 if (ret === false) {
3731 if (this._isNonEditable(ev)) {
3734 this._setCurrentEvent(ev);
3735 var sel = Event.getTarget(ev);
3736 if (this.browser.webkit && this._hasSelection()) {
3737 var _sel = this._getSelection();
3738 if (!this.browser.webkit3) {
3739 _sel.collapse(true);
3741 _sel.collapseToStart();
3744 if (this.browser.webkit && this._lastImage) {
3745 Dom.removeClass(this._lastImage, 'selected');
3746 this._lastImage = null;
3748 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3749 if (this.browser.webkit) {
3750 Event.stopEvent(ev);
3751 if (this._isElement(sel, 'img')) {
3752 Dom.addClass(sel, 'selected');
3753 this._lastImage = sel;
3756 if (this.currentWindow) {
3761 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3765 * @method _handleDoubleClick
3766 * @param {Event} ev The event we are working on.
3767 * @description Handles all doubleclick events inside the iFrame document.
3769 _handleDoubleClick: function(ev) {
3770 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3771 if (ret === false) {
3774 if (this._isNonEditable(ev)) {
3777 this._setCurrentEvent(ev);
3778 var sel = Event.getTarget(ev);
3779 if (this._isElement(sel, 'img')) {
3780 this.currentElement[0] = sel;
3781 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3782 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3783 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3784 this.currentElement[0] = this._hasParent(sel, 'a');
3785 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3786 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3789 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3793 * @method _handleKeyUp
3794 * @param {Event} ev The event we are working on.
3795 * @description Handles all keyup events inside the iFrame document.
3797 _handleKeyUp: function(ev) {
3798 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3799 if (ret === false) {
3802 if (this._isNonEditable(ev)) {
3806 this._setCurrentEvent(ev);
3807 switch (ev.keyCode) {
3808 case this._keyMap.SELECT_ALL.key:
3809 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3813 case 32: //Space Bar
3816 case 37: //Left Arrow
3818 case 39: //Right Arrow
3819 case 40: //Down Arrow
3820 case 46: //Forward Delete
3822 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3823 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3824 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3828 if (!this.browser.ie) {
3829 if (this._nodeChangeTimer) {
3830 clearTimeout(this._nodeChangeTimer);
3833 this._nodeChangeTimer = setTimeout(function() {
3834 self._nodeChangeTimer = null;
3835 self.nodeChange.call(self);
3840 this.editorDirty = true;
3844 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3848 * @method _handleKeyPress
3849 * @param {Event} ev The event we are working on.
3850 * @description Handles all keypress events inside the iFrame document.
3852 _handleKeyPress: function(ev) {
3853 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3854 if (ret === false) {
3858 if (this.get('allowNoEdit')) {
3859 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3860 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3861 //Forward delete key
3862 YAHOO.log('allowNoEdit is set, forward delete key has been disabled', 'warn', 'SimpleEditor');
3863 Event.stopEvent(ev);
3866 if (this._isNonEditable(ev)) {
3869 this._setCurrentEvent(ev);
3871 if (this.browser.opera) {
3872 if (ev.keyCode === 13) {
3873 var tar = this._getSelectedElement();
3874 if (!this._isElement(tar, 'li')) {
3875 this.execCommand('inserthtml', '<br>');
3876 Event.stopEvent(ev);
3880 if (this.browser.webkit) {
3881 if (!this.browser.webkit3) {
3882 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3883 //This is CMD + z (for undo)
3884 if (this._hasParent(this._getSelectedElement(), 'li')) {
3885 YAHOO.log('We are in an LI and we found CMD + z, stopping the event', 'warn', 'SimpleEditor');
3886 Event.stopEvent(ev);
3892 this._fixListDupIds();
3893 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3897 * @method _handleKeyDown
3898 * @param {Event} ev The event we are working on.
3899 * @description Handles all keydown events inside the iFrame document.
3901 _handleKeyDown: function(ev) {
3902 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3903 if (ret === false) {
3906 var tar = null, _range = null;
3907 if (this._isNonEditable(ev)) {
3910 this._setCurrentEvent(ev);
3911 if (this.currentWindow) {
3914 if (this.currentWindow) {
3922 //YAHOO.log('keyCode: ' + ev.keyCode, 'info', 'SimpleEditor');
3924 switch (ev.keyCode) {
3925 case this._keyMap.FOCUS_TOOLBAR.key:
3926 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3927 var h = this.toolbar.getElementsByTagName('h2')[0];
3928 if (h && h.firstChild) {
3929 h.firstChild.focus();
3931 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3932 //Focus After Element - Esc
3933 this.afterElement.focus();
3935 Event.stopEvent(ev);
3939 case this._keyMap.CREATE_LINK.key: //L
3940 if (this._hasSelection()) {
3941 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3942 var makeLink = true;
3943 if (this.get('limitCommands')) {
3944 if (!this.toolbar.getButtonByValue('createlink')) {
3945 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
3950 this.execCommand('createlink', '');
3951 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3952 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3959 case this._keyMap.UNDO.key:
3960 case this._keyMap.REDO.key:
3961 if (this._checkKey(this._keyMap.REDO, ev)) {
3964 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3970 case this._keyMap.BOLD.key:
3971 if (this._checkKey(this._keyMap.BOLD, ev)) {
3976 case this._keyMap.FONT_SIZE_UP.key:
3977 case this._keyMap.FONT_SIZE_DOWN.key:
3978 var uk = false, dk = false;
3979 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3982 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3986 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3987 label = parseInt(fs_button.get('label'), 10),
3988 newValue = (label + 1);
3991 newValue = (label - 1);
3994 action = 'fontsize';
3995 value = newValue + 'px';
4000 case this._keyMap.ITALIC.key:
4001 if (this._checkKey(this._keyMap.ITALIC, ev)) {
4007 case this._keyMap.UNDERLINE.key:
4008 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
4009 action = 'underline';
4014 if (this.browser.ie) {
4015 //Insert a tab in Internet Explorer
4016 _range = this._getRange();
4017 tar = this._getSelectedElement();
4018 if (!this._isElement(tar, 'li')) {
4020 _range.pasteHTML(' ');
4021 _range.collapse(false);
4024 Event.stopEvent(ev);
4028 if (this.browser.gecko > 1.8) {
4029 tar = this._getSelectedElement();
4030 if (this._isElement(tar, 'li')) {
4032 this._getDoc().execCommand('outdent', null, '');
4034 this._getDoc().execCommand('indent', null, '');
4037 } else if (!this._hasSelection()) {
4038 this.execCommand('inserthtml', ' ');
4040 Event.stopEvent(ev);
4044 var p = null, i = 0;
4045 if (this.get('ptags') && !ev.shiftKey) {
4046 if (this.browser.gecko) {
4047 tar = this._getSelectedElement();
4048 if (!this._hasParent(tar, 'li')) {
4049 if (this._hasParent(tar, 'p')) {
4050 p = this._getDoc().createElement('p');
4051 p.innerHTML = ' ';
4052 Dom.insertAfter(p, tar);
4053 this._selectNode(p.firstChild);
4054 } else if (this._isElement(tar, 'body')) {
4055 this.execCommand('insertparagraph', null);
4056 var ps = this._getDoc().body.getElementsByTagName('p');
4057 for (i = 0; i < ps.length; i++) {
4058 if (ps[i].getAttribute('_moz_dirty') !== null) {
4059 p = this._getDoc().createElement('p');
4060 p.innerHTML = ' ';
4061 Dom.insertAfter(p, ps[i]);
4062 this._selectNode(p.firstChild);
4063 ps[i].removeAttribute('_moz_dirty');
4067 YAHOO.log('Something went wrong with paragraphs, please file a bug!!', 'error', 'SimpleEditor');
4069 action = 'insertparagraph';
4071 Event.stopEvent(ev);
4074 if (this.browser.webkit) {
4075 tar = this._getSelectedElement();
4076 if (!this._hasParent(tar, 'li')) {
4077 this.execCommand('insertparagraph', null);
4078 var divs = this._getDoc().body.getElementsByTagName('div');
4079 for (i = 0; i < divs.length; i++) {
4080 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
4081 Dom.addClass(divs[i], 'yui-wk-p');
4084 Event.stopEvent(ev);
4088 if (this.browser.webkit) {
4089 tar = this._getSelectedElement();
4090 if (!this._hasParent(tar, 'li')) {
4091 if (this.browser.webkit4) {
4092 this.execCommand('insertlinebreak');
4094 this.execCommand('inserthtml', '<var id="yui-br"></var>');
4095 var holder = this._getDoc().getElementById('yui-br'),
4096 br = this._getDoc().createElement('br'),
4097 caret = this._getDoc().createElement('span');
4099 holder.parentNode.replaceChild(br, holder);
4100 caret.className = 'yui-non';
4101 caret.innerHTML = ' ';
4102 Dom.insertAfter(caret, br);
4103 this._selectNode(caret);
4105 Event.stopEvent(ev);
4108 if (this.browser.ie) {
4109 YAHOO.log('Stopping P tags', 'info', 'SimpleEditor');
4110 //Insert a <br> instead of a <p></p> in Internet Explorer
4111 _range = this._getRange();
4112 tar = this._getSelectedElement();
4113 if (!this._isElement(tar, 'li')) {
4115 _range.pasteHTML('<br>');
4116 _range.collapse(false);
4119 Event.stopEvent(ev);
4125 if (this.browser.ie) {
4128 if (doExec && action) {
4129 this.execCommand(action, value);
4130 Event.stopEvent(ev);
4134 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4138 * @property _fixListRunning
4140 * @description Keeps more than one _fixListDupIds from running at the same time.
4142 _fixListRunning: null,
4145 * @method _fixListDupIds
4146 * @description Some browsers will duplicate the id of an LI when created in designMode.
4147 * This method will fix the duplicate id issue. However it will only preserve the first element
4148 * in the document list with the unique id.
4150 _fixListDupIds: function() {
4151 if (this._fixListRunning) {
4154 if (this._getDoc()) {
4155 this._fixListRunning = true;
4156 var lis = this._getDoc().body.getElementsByTagName('li'),
4158 for (i = 0; i < lis.length; i++) {
4160 if (ids[lis[i].id]) {
4163 ids[lis[i].id] = true;
4166 this._fixListRunning = false;
4172 * @param {Event} ev The event we are working on.
4173 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4175 _listFix: function(ev) {
4176 //YAHOO.log('Lists Fix (' + ev.keyCode + ')', 'info', 'SimpleEditor');
4177 var testLi = null, par = null, preContent = false, range = null;
4179 if (this.browser.webkit) {
4180 if (ev.keyCode && (ev.keyCode == 13)) {
4181 if (this._hasParent(this._getSelectedElement(), 'li')) {
4182 var tar = this._hasParent(this._getSelectedElement(), 'li');
4183 if (tar.previousSibling) {
4184 if (tar.firstChild && (tar.firstChild.length == 1)) {
4185 this._selectNode(tar);
4192 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4193 testLi = this._getSelectedElement();
4194 if (this._hasParent(testLi, 'li')) {
4195 testLi = this._hasParent(testLi, 'li');
4196 YAHOO.log('We have a SHIFT tab in an LI, reverse it..', 'info', 'SimpleEditor');
4197 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4198 YAHOO.log('We have a double parent, move up a level', 'info', 'SimpleEditor');
4199 par = this._hasParent(testLi, 'ul');
4201 par = this._hasParent(testLi, 'ol');
4203 //YAHOO.log(par.previousSibling + ' :: ' + par.previousSibling.innerHTML);
4204 if (this._isElement(par.previousSibling, 'li')) {
4205 par.removeChild(testLi);
4206 par.parentNode.insertBefore(testLi, par.nextSibling);
4207 if (this.browser.ie) {
4208 range = this._getDoc().body.createTextRange();
4209 range.moveToElementText(testLi);
4210 range.collapse(false);
4213 if (this.browser.webkit) {
4214 this._selectNode(testLi.firstChild);
4216 Event.stopEvent(ev);
4222 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4223 YAHOO.log('List Fix - Tab', 'info', 'SimpleEditor');
4224 var preLi = this._getSelectedElement();
4225 if (this._hasParent(preLi, 'li')) {
4226 preContent = this._hasParent(preLi, 'li').innerHTML;
4228 //YAHOO.log('preLI: ' + preLi.tagName + ' :: ' + preLi.innerHTML);
4229 if (this.browser.webkit) {
4230 this._getDoc().execCommand('inserttext', false, '\t');
4232 testLi = this._getSelectedElement();
4233 if (this._hasParent(testLi, 'li')) {
4234 YAHOO.log('We have a tab in an LI', 'info', 'SimpleEditor');
4235 par = this._hasParent(testLi, 'li');
4236 YAHOO.log('parLI: ' + par.tagName + ' :: ' + par.innerHTML);
4237 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4238 if (this.browser.webkit) {
4239 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4240 //Remove the span element that Safari puts in
4242 par.removeChild(span[0]);
4243 par.innerHTML = Lang.trim(par.innerHTML);
4244 //Put the HTML from the LI into this new LI
4246 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4248 par.innerHTML = '<span class="yui-non"> </span> ';
4253 par.innerHTML = preContent + ' ';
4255 par.innerHTML = ' ';
4259 par.parentNode.replaceChild(newUl, par);
4260 newUl.appendChild(par);
4261 if (this.browser.webkit) {
4262 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4263 if (!this.browser.webkit3) {
4264 par.parentNode.parentNode.style.display = 'list-item';
4265 setTimeout(function() {
4266 par.parentNode.parentNode.style.display = 'block';
4269 } else if (this.browser.ie) {
4270 range = this._getDoc().body.createTextRange();
4271 range.moveToElementText(par);
4272 range.collapse(false);
4275 this._selectNode(par);
4277 Event.stopEvent(ev);
4279 if (this.browser.webkit) {
4280 Event.stopEvent(ev);
4286 * @method nodeChange
4287 * @param {Boolean} force Optional paramenter to skip the threshold counter
4288 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4290 nodeChange: function(force) {
4293 if (this.get('nodeChangeDelay')) {
4294 this._nodeChangeDelayTimer = window.setTimeout(function() {
4295 NCself._nodeChangeDelayTimer = null;
4296 NCself._nodeChange.apply(NCself, arguments);
4304 * @method _nodeChange
4305 * @param {Boolean} force Optional paramenter to skip the threshold counter
4306 * @description Fired from nodeChange in a setTimeout.
4308 _nodeChange: function(force) {
4309 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4310 thisNodeChange = Math.round(new Date().getTime() / 1000),
4313 if (force === true) {
4314 this._lastNodeChange = 0;
4317 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4318 if (this._fixNodesTimer === null) {
4319 this._fixNodesTimer = window.setTimeout(function() {
4320 self._fixNodes.call(self);
4321 self._fixNodesTimer = null;
4325 this._lastNodeChange = thisNodeChange;
4326 if (this.currentEvent) {
4328 this._lastNodeChangeEvent = this.currentEvent.type;
4332 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4333 if (beforeNodeChange === false) {
4336 if (this.get('dompath')) {
4337 window.setTimeout(function() {
4338 self._writeDomPath.call(self);
4341 //Check to see if we are disabled before continuing
4342 if (!this.get('disabled')) {
4343 if (this.STOP_NODE_CHANGE) {
4344 //Reset this var for next action
4345 this.STOP_NODE_CHANGE = false;
4348 var sel = this._getSelection(),
4349 range = this._getRange(),
4350 el = this._getSelectedElement(),
4351 fn_button = this.toolbar.getButtonByValue('fontname'),
4352 fs_button = this.toolbar.getButtonByValue('fontsize'),
4353 undo_button = this.toolbar.getButtonByValue('undo'),
4354 redo_button = this.toolbar.getButtonByValue('redo');
4356 //Handle updating the toolbar with active buttons
4358 if (this._lastButton) {
4359 _ex[this._lastButton.id] = true;
4360 //this._lastButton = null;
4362 if (!this._isElement(el, 'body')) {
4364 _ex[fn_button.get('id')] = true;
4367 _ex[fs_button.get('id')] = true;
4371 delete _ex[redo_button.get('id')];
4373 this.toolbar.resetAllButtons(_ex);
4375 //Handle disabled buttons
4376 for (var d = 0; d < this._disabled.length; d++) {
4377 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4378 if (_button && _button.get) {
4379 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4382 if (!this._hasSelection() && !this.get('insert')) {
4383 switch (this._disabled[d]) {
4388 //No Selection - disable
4389 this.toolbar.disableButton(_button);
4392 if (!this._alwaysDisabled[this._disabled[d]]) {
4393 this.toolbar.enableButton(_button);
4396 if (!this._alwaysEnabled[this._disabled[d]]) {
4397 this.toolbar.deselectButton(_button);
4402 var path = this._getDomPath();
4403 var tag = null, cmd = null;
4404 for (var i = 0; i < path.length; i++) {
4405 tag = path[i].tagName.toLowerCase();
4406 if (path[i].getAttribute('tag')) {
4407 tag = path[i].getAttribute('tag').toLowerCase();
4409 cmd = this._tag2cmd[tag];
4410 if (cmd === undefined) {
4413 if (!Lang.isArray(cmd)) {
4417 //Bold and Italic styles
4418 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4419 cmd[cmd.length] = 'bold';
4421 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4422 cmd[cmd.length] = 'italic';
4424 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4425 cmd[cmd.length] = 'underline';
4427 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4428 cmd[cmd.length] = 'strikethrough';
4430 if (cmd.length > 0) {
4431 for (var j = 0; j < cmd.length; j++) {
4432 this.toolbar.selectButton(cmd[j]);
4433 this.toolbar.enableButton(cmd[j]);
4437 switch (path[i].style.textAlign.toLowerCase()) {
4442 var alignType = path[i].style.textAlign.toLowerCase();
4443 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4446 this.toolbar.selectButton('justify' + alignType);
4447 this.toolbar.enableButton('justify' + alignType);
4453 //Reset Font Family and Size to the inital configs
4455 var family = fn_button._configs.label._initialConfig.value;
4456 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4457 this._updateMenuChecked('fontname', family);
4461 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4464 var hd_button = this.toolbar.getButtonByValue('heading');
4466 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4467 this._updateMenuChecked('heading', 'none');
4469 var img_button = this.toolbar.getButtonByValue('insertimage');
4470 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4471 this.toolbar.disableButton(img_button);
4473 if (this._lastButton && this._lastButton.isSelected) {
4474 this.toolbar.deselectButton(this._lastButton.id);
4476 this._undoNodeChange();
4480 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4484 * @method _updateMenuChecked
4485 * @param {Object} button The command identifier of the button you want to check
4486 * @param {String} value The value of the menu item you want to check
4487 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4488 * @description Gets the menu from a button instance, if the menu is not rendered it will render it. It will then search the menu for the specified value, unchecking all other items and checking the specified on.
4490 _updateMenuChecked: function(button, value, tbar) {
4492 tbar = this.toolbar;
4494 var _button = tbar.getButtonByValue(button);
4495 _button.checkValue(value);
4499 * @method _handleToolbarClick
4500 * @param {Event} ev The event that triggered the button click
4501 * @description This is an event handler attached to the Toolbar's buttonClick event. It will fire execCommand with the command identifier from the Toolbar Button.
4503 _handleToolbarClick: function(ev) {
4506 var cmd = ev.button.value;
4507 if (ev.button.menucmd) {
4509 cmd = ev.button.menucmd;
4511 this._lastButton = ev.button;
4512 if (this.STOP_EXEC_COMMAND) {
4513 YAHOO.log('execCommand skipped because we found the STOP_EXEC_COMMAND flag set to true', 'warn', 'SimpleEditor');
4514 YAHOO.log('NOEXEC::execCommand::(' + cmd + '), (' + value + ')', 'warn', 'SimpleEditor');
4515 this.STOP_EXEC_COMMAND = false;
4518 this.execCommand(cmd, value);
4519 if (!this.browser.webkit) {
4521 setTimeout(function() {
4522 Fself.focus.call(Fself);
4526 Event.stopEvent(ev);
4530 * @method _setupAfterElement
4531 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4533 _setupAfterElement: function() {
4534 if (!this.beforeElement) {
4535 this.beforeElement = document.createElement('h2');
4536 this.beforeElement.className = 'yui-editor-skipheader';
4537 this.beforeElement.tabIndex = '-1';
4538 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4539 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4541 if (!this.afterElement) {
4542 this.afterElement = document.createElement('h2');
4543 this.afterElement.className = 'yui-editor-skipheader';
4544 this.afterElement.tabIndex = '-1';
4545 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4546 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4551 * @method _disableEditor
4552 * @param {Boolean} disabled Pass true to disable, false to enable
4553 * @description Creates a mask to place over the Editor.
4555 _disableEditor: function(disabled) {
4556 var iframe, par, html, height;
4557 if (!this.get('disabled_iframe')) {
4558 iframe = this._createIframe();
4559 iframe.set('id', 'disabled_' + this.get('iframe').get('id'));
4560 iframe.setStyle('height', '100%');
4561 iframe.setStyle('display', 'none');
4562 iframe.setStyle('visibility', 'visible');
4563 this.set('disabled_iframe', iframe);
4564 par = this.get('iframe').get('parentNode');
4565 par.appendChild(iframe.get('element'));
4568 iframe = this.get('disabled_iframe');
4571 this._orgIframe = this.get('iframe');
4574 this.toolbar.set('disabled', true);
4577 html = this.getEditorHTML();
4578 height = this.get('iframe').get('offsetHeight');
4579 iframe.setStyle('visibility', '');
4580 iframe.setStyle('position', '');
4581 iframe.setStyle('top', '');
4582 iframe.setStyle('left', '');
4583 this._orgIframe.setStyle('visibility', 'hidden');
4584 this._orgIframe.setStyle('position', 'absolute');
4585 this._orgIframe.setStyle('top', '-99999px');
4586 this._orgIframe.setStyle('left', '-99999px');
4587 this.set('iframe', iframe);
4588 this._setInitialContent(true);
4591 this._mask = document.createElement('DIV');
4592 Dom.addClass(this._mask, 'yui-editor-masked');
4593 if (this.browser.ie) {
4594 this._mask.style.height = height + 'px';
4596 this.get('iframe').get('parentNode').appendChild(this._mask);
4598 this.on('editorContentReloaded', function() {
4599 this._getDoc().body._rteLoaded = false;
4600 this.setEditorHTML(html);
4601 iframe.setStyle('display', 'block');
4602 this.unsubscribeAll('editorContentReloaded');
4606 this._mask.parentNode.removeChild(this._mask);
4609 this.toolbar.set('disabled', false);
4611 iframe.setStyle('visibility', 'hidden');
4612 iframe.setStyle('position', 'absolute');
4613 iframe.setStyle('top', '-99999px');
4614 iframe.setStyle('left', '-99999px');
4615 this._orgIframe.setStyle('visibility', '');
4616 this._orgIframe.setStyle('position', '');
4617 this._orgIframe.setStyle('top', '');
4618 this._orgIframe.setStyle('left', '');
4619 this.set('iframe', this._orgIframe);
4623 window.setTimeout(function() {
4624 self.nodeChange.call(self);
4630 * @property SEP_DOMPATH
4631 * @description The value to place in between the Dom path items
4636 * @property STR_LEAVE_EDITOR
4637 * @description The accessibility string for the element after the iFrame
4640 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4642 * @property STR_BEFORE_EDITOR
4643 * @description The accessibility string for the element before the iFrame
4646 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Shift + Escape to place focus on the toolbar and navigate between options with your arrow keys. To exit this text editor use the Escape key and continue tabbing. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift L adds an HTML link</li></ul>',
4648 * @property STR_TITLE
4649 * @description The Title of the HTML document that is created in the iFrame
4652 STR_TITLE: 'Rich Text Area.',
4654 * @property STR_IMAGE_HERE
4655 * @description The text to place in the URL textbox when using the blankimage.
4658 STR_IMAGE_HERE: 'Image URL Here',
4660 * @property STR_IMAGE_URL
4661 * @description The label string for Image URL
4664 STR_IMAGE_URL: 'Image URL',
4666 * @property STR_LINK_URL
4667 * @description The label string for the Link URL.
4670 STR_LINK_URL: 'Link URL',
4673 * @property STOP_EXEC_COMMAND
4674 * @description Set to true when you want the default execCommand function to not process anything
4677 STOP_EXEC_COMMAND: false,
4680 * @property STOP_NODE_CHANGE
4681 * @description Set to true when you want the default nodeChange function to not process anything
4684 STOP_NODE_CHANGE: false,
4687 * @property CLASS_NOEDIT
4688 * @description CSS class applied to elements that are not editable.
4691 CLASS_NOEDIT: 'yui-noedit',
4694 * @property CLASS_CONTAINER
4695 * @description Default CSS class to apply to the editors container element
4698 CLASS_CONTAINER: 'yui-editor-container',
4701 * @property CLASS_EDITABLE
4702 * @description Default CSS class to apply to the editors iframe element
4705 CLASS_EDITABLE: 'yui-editor-editable',
4708 * @property CLASS_EDITABLE_CONT
4709 * @description Default CSS class to apply to the editors iframe's parent element
4712 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4715 * @property CLASS_PREFIX
4716 * @description Default prefix for dynamically created class names
4719 CLASS_PREFIX: 'yui-editor',
4722 * @description Standard browser detection
4725 browser: function() {
4726 var br = YAHOO.env.ua;
4728 if (br.webkit >= 420) {
4729 br.webkit3 = br.webkit;
4733 if (br.webkit >= 530) {
4734 br.webkit4 = br.webkit;
4740 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4748 * @description The Editor class' initialization method
4750 init: function(p_oElement, p_oAttributes) {
4751 YAHOO.log('init', 'info', 'SimpleEditor');
4753 if (!this._defaultToolbar) {
4754 this._defaultToolbar = {
4756 titlebar: 'Text Editing Tools',
4759 { group: 'fontstyle', label: 'Font Name and Size',
4761 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4763 { text: 'Arial', checked: true },
4764 { text: 'Arial Black' },
4765 { text: 'Comic Sans MS' },
4766 { text: 'Courier New' },
4767 { text: 'Lucida Console' },
4769 { text: 'Times New Roman' },
4770 { text: 'Trebuchet MS' },
4774 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4777 { type: 'separator' },
4778 { group: 'textstyle', label: 'Font Style',
4780 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4781 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4782 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4783 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4784 { type: 'separator' },
4785 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4786 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4790 { type: 'separator' },
4791 { group: 'indentlist', label: 'Lists',
4793 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4794 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4797 { type: 'separator' },
4798 { group: 'insertitem', label: 'Insert Item',
4800 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4801 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4808 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4809 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4812 this.currentElement = [];
4813 this.on('contentReady', function() {
4814 this.DOMReady = true;
4820 * @method initAttributes
4821 * @description Initializes all of the configuration attributes used to create
4823 * @param {Object} attr Object literal specifying a set of
4824 * configuration attributes used to create the editor.
4826 initAttributes: function(attr) {
4827 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4831 * @config setDesignMode
4832 * @description Should the Editor set designMode on the document. Default: true.
4836 this.setAttributeConfig('setDesignMode', {
4837 value: ((attr.setDesignMode === false) ? false : true)
4840 * @config nodeChangeDelay
4841 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true.
4845 this.setAttributeConfig('nodeChangeDelay', {
4846 value: ((attr.nodeChangeDelay === false) ? false : true)
4850 * @description The max number of undo levels to store.
4854 this.setAttributeConfig('maxUndo', {
4856 value: attr.maxUndo || 30
4861 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4865 this.setAttributeConfig('ptags', {
4867 value: attr.ptags || false
4871 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4875 this.setAttributeConfig('insert', {
4877 value: attr.insert || false,
4878 method: function(insert) {
4886 var tmp = this._defaultToolbar.buttons;
4887 for (var i = 0; i < tmp.length; i++) {
4888 if (tmp[i].buttons) {
4889 for (var a = 0; a < tmp[i].buttons.length; a++) {
4890 if (tmp[i].buttons[a].value) {
4891 if (buttons[tmp[i].buttons[a].value]) {
4892 delete tmp[i].buttons[a].disabled;
4903 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4904 * We will create one and place it in this container. If no container is passed we will append to document.body.
4908 this.setAttributeConfig('container', {
4910 value: attr.container || false
4914 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4918 this.setAttributeConfig('plainText', {
4920 value: attr.plainText || false
4925 * @description Internal config for holding the iframe element.
4929 this.setAttributeConfig('iframe', {
4934 * @config disabled_iframe
4935 * @description Internal config for holding the iframe element used when disabling the Editor.
4939 this.setAttributeConfig('disabled_iframe', {
4944 * @depreciated - No longer used, should use this.get('element')
4946 * @description Internal config for holding the textarea element (replaced with element).
4950 this.setAttributeConfig('textarea', {
4955 * @config nodeChangeThreshold
4956 * @description The number of seconds that need to be in between nodeChange processing
4960 this.setAttributeConfig('nodeChangeThreshold', {
4961 value: attr.nodeChangeThreshold || 3,
4962 validator: YAHOO.lang.isNumber
4965 * @config allowNoEdit
4966 * @description Should the editor check for non-edit fields. It should be noted that this technique is not perfect. If the user does the right things, they will still be able to make changes.
4967 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4971 this.setAttributeConfig('allowNoEdit', {
4972 value: attr.allowNoEdit || false,
4973 validator: YAHOO.lang.isBoolean
4976 * @config limitCommands
4977 * @description Should the Editor limit the allowed execCommands to the ones available in the toolbar. If true, then execCommand and keyboard shortcuts will fail if they are not defined in the toolbar.
4981 this.setAttributeConfig('limitCommands', {
4982 value: attr.limitCommands || false,
4983 validator: YAHOO.lang.isBoolean
4986 * @config element_cont
4987 * @description Internal config for the editors container
4991 this.setAttributeConfig('element_cont', {
4992 value: attr.element_cont
4996 * @config editor_wrapper
4997 * @description The outter wrapper for the entire editor.
5001 this.setAttributeConfig('editor_wrapper', {
5002 value: attr.editor_wrapper || null,
5007 * @description The height of the editor iframe container, not including the toolbar..
5008 * @default Best guessed size of the textarea, for best results use CSS to style the height of the textarea or pass it in as an argument
5011 this.setAttributeConfig('height', {
5012 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
5013 method: function(height) {
5014 if (this._rendered) {
5015 //We have been rendered, change the height
5016 if (this.get('animate')) {
5017 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
5019 to: parseInt(height, 10)
5024 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
5030 * @config autoHeight
5031 * @description Remove the scrollbars from the edit area and resize it to fit the content. It will not go any lower than the current config height.
5033 * @type Boolean || Number
5035 this.setAttributeConfig('autoHeight', {
5036 value: attr.autoHeight || false,
5037 method: function(a) {
5039 if (this.get('iframe')) {
5040 this.get('iframe').get('element').setAttribute('scrolling', 'no');
5042 this.on('afterNodeChange', this._handleAutoHeight, this, true);
5043 this.on('editorKeyDown', this._handleAutoHeight, this, true);
5044 this.on('editorKeyPress', this._handleAutoHeight, this, true);
5046 if (this.get('iframe')) {
5047 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
5049 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
5050 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
5051 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
5057 * @description The width of the editor container.
5058 * @default Best guessed size of the textarea, for best results use CSS to style the width of the textarea or pass it in as an argument
5061 this.setAttributeConfig('width', {
5062 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
5063 method: function(width) {
5064 if (this._rendered) {
5065 //We have been rendered, change the width
5066 if (this.get('animate')) {
5067 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
5069 to: parseInt(width, 10)
5074 this.get('element_cont').setStyle('width', width);
5081 * @attribute blankimage
5082 * @description The URL for the image placeholder to put in when inserting an image.
5083 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
5086 this.setAttributeConfig('blankimage', {
5087 value: attr.blankimage || this._getBlankImage()
5091 * @description The Base CSS used to format the content of the editor
5092 * @default <code><pre>html {
5097 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
5101 text-decoration: underline;
5104 .warning-localfile {
5105 border-bottom: 1px dashed red !important;
5108 cursor: wait !important;
5110 img.selected { //Safari image selection
5111 border: 2px dotted #808080;
5114 cursor: pointer !important;
5120 this.setAttributeConfig('css', {
5121 value: attr.css || this._defaultCSS,
5126 * @description The default HTML to be written to the iframe document before the contents are loaded (Note that the DOCTYPE attr will be added at render item)
5127 * @default This HTML requires a few things if you are to override:
5128 <p><code>{TITLE}, {CSS}, {HIDDEN_CSS}, {EXTRA_CSS}</code> and <code>{CONTENT}</code> need to be there, they are passed to YAHOO.lang.substitute to be replace with other strings.<p>
5129 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
5134 <title>{TITLE}</title>
5135 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5146 <body onload="document.body._rteLoaded = true;">
5154 this.setAttributeConfig('html', {
5155 value: attr.html || '<html><head><title>{TITLE}</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><base href="' + this._baseHREF + '"><style>{CSS}</style><style>{HIDDEN_CSS}</style><style>{EXTRA_CSS}</style></head><body onload="document.body._rteLoaded = true;">{CONTENT}</body></html>',
5160 * @attribute extracss
5161 * @description Extra user defined css to load after the default SimpleEditor CSS
5165 this.setAttributeConfig('extracss', {
5166 value: attr.extracss || '',
5171 * @attribute handleSubmit
5172 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
5173 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
5174 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
5178 this.setAttributeConfig('handleSubmit', {
5179 value: attr.handleSubmit || false,
5180 method: function(exec) {
5181 if (this.get('element').form) {
5182 if (!this._formButtons) {
5183 this._formButtons = [];
5186 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
5187 var i = this.get('element').form.getElementsByTagName('input');
5188 for (var s = 0; s < i.length; s++) {
5189 var type = i[s].getAttribute('type');
5190 if (type && (type.toLowerCase() == 'submit')) {
5191 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
5192 this._formButtons[this._formButtons.length] = i[s];
5196 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
5197 if (this._formButtons) {
5198 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
5205 * @attribute disabled
5206 * @description This will toggle the editor's disabled state. When the editor is disabled, designMode is turned off and a mask is placed over the iframe so no interaction can take place.
5207 All Toolbar buttons are also disabled so they cannot be used.
5212 this.setAttributeConfig('disabled', {
5214 method: function(disabled) {
5215 if (this._rendered) {
5216 this._disableEditor(disabled);
5222 * @description When save HTML is called, this element will be updated as well as the source of data.
5226 this.setAttributeConfig('saveEl', {
5227 value: this.get('element')
5230 * @config toolbar_cont
5231 * @description Internal config for the toolbars container
5235 this.setAttributeConfig('toolbar_cont', {
5240 * @attribute toolbar
5241 * @description The default toolbar config.
5244 this.setAttributeConfig('toolbar', {
5245 value: attr.toolbar || this._defaultToolbar,
5247 method: function(toolbar) {
5248 if (!toolbar.buttonType) {
5249 toolbar.buttonType = this._defaultToolbar.buttonType;
5251 this._defaultToolbar = toolbar;
5255 * @attribute animate
5256 * @description Should the editor animate window movements
5257 * @default false unless Animation is found, then true
5260 this.setAttributeConfig('animate', {
5261 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5262 validator: function(value) {
5264 if (!YAHOO.util.Anim) {
5272 * @description A reference to the panel we are using for windows.
5276 this.setAttributeConfig('panel', {
5279 validator: function(value) {
5281 if (!YAHOO.widget.Overlay) {
5288 * @attribute focusAtStart
5289 * @description Should we focus the window when the content is ready?
5293 this.setAttributeConfig('focusAtStart', {
5294 value: attr.focusAtStart || false,
5296 method: function(fs) {
5298 this.on('editorContentLoaded', function() {
5300 setTimeout(function() {
5301 self.focus.call(self);
5302 self.editorDirty = false;
5309 * @attribute dompath
5310 * @description Toggle the display of the current Dom path below the editor
5314 this.setAttributeConfig('dompath', {
5315 value: attr.dompath || false,
5316 method: function(dompath) {
5317 if (dompath && !this.dompath) {
5318 this.dompath = document.createElement('DIV');
5319 this.dompath.id = this.get('id') + '_dompath';
5320 Dom.addClass(this.dompath, 'dompath');
5321 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5322 if (this.get('iframe')) {
5323 this._writeDomPath();
5325 } else if (!dompath && this.dompath) {
5326 this.dompath.parentNode.removeChild(this.dompath);
5327 this.dompath = null;
5333 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5334 * @default "semantic"
5337 this.setAttributeConfig('markup', {
5338 value: attr.markup || 'semantic',
5339 validator: function(markup) {
5340 switch (markup.toLowerCase()) {
5351 * @attribute removeLineBreaks
5352 * @description Should we remove linebreaks and extra spaces on cleanup
5356 this.setAttributeConfig('removeLineBreaks', {
5357 value: attr.removeLineBreaks || false,
5358 validator: YAHOO.lang.isBoolean
5363 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5364 * @type {Boolean/String}
5366 this.setAttributeConfig('drag', {
5368 value: attr.drag || false
5373 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5374 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5377 this.setAttributeConfig('resize', {
5379 value: attr.resize || false
5383 * @config filterWord
5384 * @description Attempt to filter out MS Word HTML from the Editor's output.
5387 this.setAttributeConfig('filterWord', {
5388 value: attr.filterWord || false,
5389 validator: YAHOO.lang.isBoolean
5395 * @method _getBlankImage
5396 * @description Retrieves the full url of the image to use as the blank image.
5397 * @return {String} The URL to the blank image
5399 _getBlankImage: function() {
5400 if (!this.DOMReady) {
5401 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5405 if (!this._blankImageLoaded) {
5406 if (YAHOO.widget.EditorInfo.blankImage) {
5407 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5408 this._blankImageLoaded = true;
5410 var div = document.createElement('div');
5411 div.style.position = 'absolute';
5412 div.style.top = '-9999px';
5413 div.style.left = '-9999px';
5414 div.className = this.CLASS_PREFIX + '-blankimage';
5415 document.body.appendChild(div);
5416 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5417 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5419 img = img.replace('app:/', '');
5420 this.set('blankimage', img);
5421 this._blankImageLoaded = true;
5422 div.parentNode.removeChild(div);
5423 YAHOO.widget.EditorInfo.blankImage = img;
5426 img = this.get('blankimage');
5432 * @method _handleAutoHeight
5433 * @description Handles resizing the editor's height based on the content
5435 _handleAutoHeight: function() {
5436 var doc = this._getDoc(),
5438 docEl = doc.documentElement;
5440 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5441 var newHeight = body.scrollHeight;
5442 if (this.browser.webkit) {
5443 newHeight = docEl.scrollHeight;
5445 if (newHeight < parseInt(this.get('height'), 10)) {
5446 newHeight = parseInt(this.get('height'), 10);
5448 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5449 var anim = this.get('animate');
5450 this.set('animate', false);
5451 this.set('height', newHeight + 'px');
5452 this.set('animate', anim);
5453 if (this.browser.ie) {
5454 //Internet Explorer needs this
5455 this.get('iframe').setStyle('height', '99%');
5456 this.get('iframe').setStyle('zoom', '1');
5458 window.setTimeout(function() {
5459 self.get('iframe').setStyle('height', '100%');
5466 * @property _formButtons
5467 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5473 * @property _formButtonClicked
5474 * @description The form button that was clicked to submit the form.
5477 _formButtonClicked: null,
5480 * @method _handleFormButtonClick
5481 * @description The click listener assigned to each submit button in the Editor's parent form.
5482 * @param {Event} ev The click event
5484 _handleFormButtonClick: function(ev) {
5485 var tar = Event.getTarget(ev);
5486 this._formButtonClicked = tar;
5490 * @method _handleFormSubmit
5491 * @description Handles the form submission.
5492 * @param {Object} ev The Form Submit Event
5494 _handleFormSubmit: function(ev) {
5497 var form = this.get('element').form,
5498 tar = this._formButtonClicked || false;
5500 Event.removeListener(form, 'submit', this._handleFormSubmit);
5501 if (YAHOO.env.ua.ie) {
5502 //form.fireEvent("onsubmit");
5503 if (tar && !tar.disabled) {
5506 } else { // Gecko, Opera, and Safari
5507 if (tar && !tar.disabled) {
5510 var oEvent = document.createEvent("HTMLEvents");
5511 oEvent.initEvent("submit", true, true);
5512 form.dispatchEvent(oEvent);
5513 if (YAHOO.env.ua.webkit) {
5514 if (YAHOO.lang.isFunction(form.submit)) {
5520 //Removed this, not need since removing Safari 2.x
5521 //Event.stopEvent(ev);
5525 * @method _handleFontSize
5526 * @description Handles the font size button in the toolbar.
5527 * @param {Object} o Object returned from Toolbar's buttonClick Event
5529 _handleFontSize: function(o) {
5530 var button = this.toolbar.getButtonById(o.button.id);
5531 var value = button.get('label') + 'px';
5532 this.execCommand('fontsize', value);
5537 * @description Handles the colorpicker buttons in the toolbar.
5538 * @param {Object} o Object returned from Toolbar's buttonClick Event
5540 _handleColorPicker: function(o) {
5542 var value = '#' + o.color;
5543 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5544 this.execCommand(cmd, value);
5549 * @method _handleAlign
5550 * @description Handles the alignment buttons in the toolbar.
5551 * @param {Object} o Object returned from Toolbar's buttonClick Event
5553 _handleAlign: function(o) {
5555 for (var i = 0; i < o.button.menu.length; i++) {
5556 if (o.button.menu[i].value == o.button.value) {
5557 cmd = o.button.menu[i].value;
5560 var value = this._getSelection();
5562 this.execCommand(cmd, value);
5567 * @method _handleAfterNodeChange
5568 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5570 _handleAfterNodeChange: function() {
5571 var path = this._getDomPath(),
5576 fn_button = this.toolbar.getButtonByValue('fontname'),
5577 fs_button = this.toolbar.getButtonByValue('fontsize'),
5578 hd_button = this.toolbar.getButtonByValue('heading');
5580 for (var i = 0; i < path.length; i++) {
5583 var tag = elm.tagName.toLowerCase();
5586 if (elm.getAttribute('tag')) {
5587 tag = elm.getAttribute('tag');
5590 family = elm.getAttribute('face');
5591 if (Dom.getStyle(elm, 'font-family')) {
5592 family = Dom.getStyle(elm, 'font-family');
5594 family = family.replace(/'/g, '');
5597 if (tag.substring(0, 1) == 'h') {
5599 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5600 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5601 hd_button.set('label', hd_button._configs.menu.value[h].text);
5604 this._updateMenuChecked('heading', tag);
5610 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5611 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5613 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5617 family = fn_button._configs.label._initialConfig.value;
5619 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5620 if (fn_button.get('label') != familyLabel) {
5621 fn_button.set('label', familyLabel);
5622 this._updateMenuChecked('fontname', family);
5627 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5628 if ((fontsize === null) || isNaN(fontsize)) {
5629 fontsize = fs_button._configs.label._initialConfig.value;
5631 fs_button.set('label', ''+fontsize);
5634 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5635 this.toolbar.enableButton(fn_button);
5636 this.toolbar.enableButton(fs_button);
5637 this.toolbar.enableButton('forecolor');
5638 this.toolbar.enableButton('backcolor');
5640 if (this._isElement(elm, 'img')) {
5641 if (YAHOO.widget.Overlay) {
5642 this.toolbar.enableButton('createlink');
5645 if (this._hasParent(elm, 'blockquote')) {
5646 this.toolbar.selectButton('indent');
5647 this.toolbar.disableButton('indent');
5648 this.toolbar.enableButton('outdent');
5650 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5651 this.toolbar.disableButton('indent');
5653 this._lastButton = null;
5658 * @method _handleInsertImageClick
5659 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5661 _handleInsertImageClick: function() {
5662 if (this.get('limitCommands')) {
5663 if (!this.toolbar.getButtonByValue('insertimage')) {
5664 YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'SimpleEditor');
5669 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5670 var _handleAEC = function() {
5671 var el = this.currentElement[0],
5674 el = this._getSelectedElement();
5677 if (el.getAttribute('src')) {
5678 src = el.getAttribute('src', 2);
5679 if (src.indexOf(this.get('blankimage')) != -1) {
5680 src = this.STR_IMAGE_HERE;
5684 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5685 if ((str !== '') && (str !== null)) {
5686 el.setAttribute('src', str);
5687 } else if (str === '') {
5688 el.parentNode.removeChild(el);
5689 this.currentElement = [];
5691 } else if ((str === null)) {
5692 src = el.getAttribute('src', 2);
5693 if (src.indexOf(this.get('blankimage')) != -1) {
5694 el.parentNode.removeChild(el);
5695 this.currentElement = [];
5700 this.toolbar.set('disabled', false);
5701 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5703 this.on('afterExecCommand', _handleAEC, this, true);
5707 * @method _handleInsertImageWindowClose
5708 * @description Handles the closing of the Image Properties Window.
5710 _handleInsertImageWindowClose: function() {
5715 * @method _isLocalFile
5716 * @param {String} url THe url/string to check
5717 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5719 _isLocalFile: function(url) {
5720 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5727 * @method _handleCreateLinkClick
5728 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5730 _handleCreateLinkClick: function() {
5731 if (this.get('limitCommands')) {
5732 if (!this.toolbar.getButtonByValue('createlink')) {
5733 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'SimpleEditor');
5738 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5740 var _handleAEC = function() {
5741 var el = this.currentElement[0],
5745 if (el.getAttribute('href', 2) !== null) {
5746 url = el.getAttribute('href', 2);
5749 var str = prompt(this.STR_LINK_URL + ': ', url);
5750 if ((str !== '') && (str !== null)) {
5752 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5753 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5754 //Found an @ sign, prefix with mailto:
5755 urlValue = 'mailto:' + urlValue;
5757 /* :// not found adding */
5758 if (urlValue.substring(0, 1) != '#') {
5759 //urlValue = 'http:/'+'/' + urlValue;
5763 el.setAttribute('href', urlValue);
5764 } else if (str !== null) {
5765 var _span = this._getDoc().createElement('span');
5766 _span.innerHTML = el.innerHTML;
5767 Dom.addClass(_span, 'yui-non');
5768 el.parentNode.replaceChild(_span, el);
5771 this.toolbar.set('disabled', false);
5772 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5774 this.on('afterExecCommand', _handleAEC, this);
5779 * @method _handleCreateLinkWindowClose
5780 * @description Handles the closing of the Link Properties Window.
5782 _handleCreateLinkWindowClose: function() {
5784 this.currentElement = [];
5788 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5790 render: function() {
5791 if (this._rendered) {
5794 YAHOO.log('Render', 'info', 'SimpleEditor');
5795 if (!this.DOMReady) {
5796 YAHOO.log('!DOMReady', 'info', 'SimpleEditor');
5797 this._queue[this._queue.length] = ['render', arguments];
5800 if (this.get('element')) {
5801 if (this.get('element').tagName) {
5802 this._textarea = true;
5803 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5804 this._textarea = false;
5807 YAHOO.log('No Valid Element', 'error', 'SimpleEditor');
5811 YAHOO.log('No Element', 'error', 'SimpleEditor');
5814 this._rendered = true;
5816 window.setTimeout(function() {
5817 self._render.call(self);
5823 * @description Causes the toolbar and the editor to render and replace the textarea.
5825 _render: function() {
5827 this.set('textarea', this.get('element'));
5829 this.get('element_cont').setStyle('display', 'none');
5830 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5832 this.set('iframe', this._createIframe());
5834 window.setTimeout(function() {
5835 self._setInitialContent.call(self);
5838 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5840 if (this.get('disabled')) {
5841 this._disableEditor(true);
5844 var tbarConf = this.get('toolbar');
5845 //Create Toolbar instance
5846 if (tbarConf instanceof Toolbar) {
5847 this.toolbar = tbarConf;
5848 //Set the toolbar to disabled until content is loaded
5849 this.toolbar.set('disabled', true);
5851 //Set the toolbar to disabled until content is loaded
5852 tbarConf.disabled = true;
5853 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5856 YAHOO.log('fireEvent::toolbarLoaded', 'info', 'SimpleEditor');
5857 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5860 this.toolbar.on('toolbarCollapsed', function() {
5861 if (this.currentWindow) {
5865 this.toolbar.on('toolbarExpanded', function() {
5866 if (this.currentWindow) {
5870 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5872 this.toolbar.on('colorPickerClicked', function(o) {
5873 this._handleColorPicker(o);
5874 return false; //Stop the buttonClick event
5877 this.toolbar.on('alignClick', this._handleAlign, this, true);
5878 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5879 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5880 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5881 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5882 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5885 //Replace Textarea with editable area
5886 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5889 this.setStyle('visibility', 'hidden');
5890 this.setStyle('position', 'absolute');
5891 this.setStyle('top', '-9999px');
5892 this.setStyle('left', '-9999px');
5893 this.get('element_cont').appendChild(this.get('element'));
5894 this.get('element_cont').setStyle('display', 'block');
5897 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5898 this.get('iframe').addClass(this.CLASS_EDITABLE);
5900 //Set height and width of editor container
5901 this.get('element_cont').setStyle('width', this.get('width'));
5902 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5904 this.get('iframe').setStyle('width', '100%'); //WIDTH
5905 this.get('iframe').setStyle('height', '100%');
5909 window.setTimeout(function() {
5910 self._setupAfterElement.call(self);
5912 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5915 * @method execCommand
5916 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5917 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5918 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5920 execCommand: function(action, value) {
5921 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5922 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5923 this.STOP_EXEC_COMMAND = false;
5926 this._lastCommand = action;
5927 this._setMarkupType(action);
5928 if (this.browser.ie) {
5929 this._getWindow().focus();
5933 if (this.get('limitCommands')) {
5934 if (!this.toolbar.getButtonByValue(action)) {
5935 YAHOO.log('Toolbar Button for (' + action + ') was not found, skipping exec.', 'info', 'SimpleEditor');
5940 this.editorDirty = true;
5942 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5943 YAHOO.log('Found execCommand override method: (cmd_' + action.toLowerCase() + ')', 'info', 'SimpleEditor');
5944 var retValue = this['cmd_' + action.toLowerCase()](value);
5947 action = retValue[1];
5950 value = retValue[2];
5954 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'SimpleEditor');
5956 this._getDoc().execCommand(action, false, value);
5958 YAHOO.log('execCommand Failed', 'error', 'SimpleEditor');
5961 YAHOO.log('OVERRIDE::execCommand::(' + action + '),(' + value + ') skipped', 'warn', 'SimpleEditor');
5963 this.on('afterExecCommand', function() {
5964 this.unsubscribeAll('afterExecCommand');
5967 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5970 /* {{{ Command Overrides */
5974 * @param value Value passed from the execCommand method
5975 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5977 cmd_bold: function(value) {
5978 if (!this.browser.webkit) {
5979 var el = this._getSelectedElement();
5980 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5981 if (el.style.fontWeight == 'bold') {
5982 el.style.fontWeight = '';
5983 var b = this._getDoc().createElement('b'),
5984 par = el.parentNode;
5985 par.replaceChild(b, el);
5993 * @method cmd_italic
5994 * @param value Value passed from the execCommand method
5995 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5998 cmd_italic: function(value) {
5999 if (!this.browser.webkit) {
6000 var el = this._getSelectedElement();
6001 if (el && this._isElement(el, 'span') && this._hasSelection()) {
6002 if (el.style.fontStyle == 'italic') {
6003 el.style.fontStyle = '';
6004 var i = this._getDoc().createElement('i'),
6005 par = el.parentNode;
6006 par.replaceChild(i, el);
6016 * @method cmd_underline
6017 * @param value Value passed from the execCommand method
6018 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
6020 cmd_underline: function(value) {
6021 if (!this.browser.webkit) {
6022 var el = this._getSelectedElement();
6023 if (el && this._isElement(el, 'span')) {
6024 if (el.style.textDecoration == 'underline') {
6025 el.style.textDecoration = 'none';
6027 el.style.textDecoration = 'underline';
6035 * @method cmd_backcolor
6036 * @param value Value passed from the execCommand method
6037 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
6039 cmd_backcolor: function(value) {
6041 el = this._getSelectedElement(),
6042 action = 'backcolor';
6044 if (this.browser.gecko || this.browser.opera) {
6045 this._setEditorStyle(true);
6046 action = 'hilitecolor';
6049 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6050 el.style.backgroundColor = value;
6051 this._selectNode(el);
6054 if (this.get('insert')) {
6055 el = this._createInsertElement({ backgroundColor: value });
6057 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
6058 this._selectNode(this.currentElement[0]);
6063 return [exec, action];
6066 * @method cmd_forecolor
6067 * @param value Value passed from the execCommand method
6068 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
6070 cmd_forecolor: function(value) {
6072 el = this._getSelectedElement();
6074 if (!this._isElement(el, 'body') && !this._hasSelection()) {
6075 Dom.setStyle(el, 'color', value);
6076 this._selectNode(el);
6079 if (this.get('insert')) {
6080 el = this._createInsertElement({ color: value });
6082 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
6083 this._selectNode(this.currentElement[0]);
6090 * @method cmd_unlink
6091 * @param value Value passed from the execCommand method
6092 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
6094 cmd_unlink: function(value) {
6095 this._swapEl(this.currentElement[0], 'span', function(el) {
6096 el.className = 'yui-non';
6101 * @method cmd_createlink
6102 * @param value Value passed from the execCommand method
6103 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
6105 cmd_createlink: function(value) {
6106 var el = this._getSelectedElement(), _a = null;
6107 if (this._hasParent(el, 'a')) {
6108 this.currentElement[0] = this._hasParent(el, 'a');
6109 } else if (this._isElement(el, 'li')) {
6110 _a = this._getDoc().createElement('a');
6111 _a.innerHTML = el.innerHTML;
6114 this.currentElement[0] = _a;
6115 } else if (!this._isElement(el, 'a')) {
6116 this._createCurrentElement('a');
6117 _a = this._swapEl(this.currentElement[0], 'a');
6118 this.currentElement[0] = _a;
6120 this.currentElement[0] = el;
6125 * @method cmd_insertimage
6126 * @param value Value passed from the execCommand method
6127 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
6129 cmd_insertimage: function(value) {
6130 var exec = true, _img = null, action = 'insertimage',
6131 el = this._getSelectedElement();
6134 value = this.get('blankimage');
6138 * @knownissue Safari Cursor Position
6139 * @browser Safari 2.x
6140 * @description The issue here is that we have no way of knowing where the cursor position is
6141 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6144 YAHOO.log('InsertImage: ' + el.tagName, 'info', 'SimpleEditor');
6145 if (this._isElement(el, 'img')) {
6146 this.currentElement[0] = el;
6149 if (this._getDoc().queryCommandEnabled(action)) {
6150 this._getDoc().execCommand(action, false, value);
6151 var imgs = this._getDoc().getElementsByTagName('img');
6152 for (var i = 0; i < imgs.length; i++) {
6153 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
6154 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
6155 this.currentElement[0] = imgs[i];
6160 if (el == this._getDoc().body) {
6161 _img = this._getDoc().createElement('img');
6162 _img.setAttribute('src', value);
6163 YAHOO.util.Dom.addClass(_img, 'yui-img');
6164 this._getDoc().body.appendChild(_img);
6166 this._createCurrentElement('img');
6167 _img = this._getDoc().createElement('img');
6168 _img.setAttribute('src', value);
6169 YAHOO.util.Dom.addClass(_img, 'yui-img');
6170 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
6172 this.currentElement[0] = _img;
6179 * @method cmd_inserthtml
6180 * @param value Value passed from the execCommand method
6181 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
6183 cmd_inserthtml: function(value) {
6184 var exec = true, action = 'inserthtml', _span = null, _range = null;
6186 * @knownissue Safari cursor position
6187 * @browser Safari 2.x
6188 * @description The issue here is that we have no way of knowing where the cursor position is
6189 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6191 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
6192 YAHOO.log('More Safari DOM tricks (inserthtml)', 'info', 'EditorSafari');
6193 this._createCurrentElement('img');
6194 _span = this._getDoc().createElement('span');
6195 _span.innerHTML = value;
6196 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
6198 } else if (this.browser.ie) {
6199 _range = this._getRange();
6201 _range.item(0).outerHTML = value;
6203 _range.pasteHTML(value);
6211 * @param tag The tag of the list you want to create (eg, ul or ol)
6212 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
6214 cmd_list: function(tag) {
6215 var exec = true, list = null, li = 0, el = null, str = '',
6216 selEl = this._getSelectedElement(), action = 'insertorderedlist';
6218 action = 'insertunorderedlist';
6221 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
6222 * @browser Safari 2.x
6223 * The issue with this workaround is that when applied to a set of text
6224 * that has BR's in it, Safari may or may not pick up the individual items as
6225 * list items. This is fixed in WebKit (Safari 3)
6226 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
6228 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
6229 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) {
6230 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
6231 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6232 el = selEl.parentNode;
6233 list = this._getDoc().createElement('span');
6234 YAHOO.util.Dom.addClass(list, 'yui-non');
6236 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div');
6237 for (li = 0; li < lis.length; li++) {
6238 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>';
6240 list.innerHTML = str;
6241 this.currentElement[0] = el;
6242 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6244 YAHOO.log('Create list item', 'info', 'SimpleEditor');
6245 this._createCurrentElement(tag.toLowerCase());
6246 list = this._getDoc().createElement(tag);
6247 for (li = 0; li < this.currentElement.length; li++) {
6248 var newli = this._getDoc().createElement('li');
6249 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
6250 list.appendChild(newli);
6252 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
6255 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'),
6256 items = list.firstChild.innerHTML.split(b_tag), i, item;
6257 if (items.length > 0) {
6258 list.innerHTML = '';
6259 for (i = 0; i < items.length; i++) {
6260 item = this._getDoc().createElement('li');
6261 item.innerHTML = items[i];
6262 list.appendChild(item);
6266 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6267 this.currentElement[0] = list;
6268 var _h = this.currentElement[0].firstChild;
6269 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6270 if (this.browser.webkit) {
6271 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6276 el = this._getSelectedElement();
6277 YAHOO.log(el.tagName);
6278 if (this._isElement(el, 'li') && this._isElement(el.parentNode, tag) || (this.browser.ie && this._isElement(this._getRange().parentElement, 'li')) || (this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { //we are in a list..
6279 YAHOO.log('We already have a list, undo it', 'info', 'SimpleEditor');
6280 if (this.browser.ie) {
6281 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6282 el = el.getElementsByTagName('li')[0];
6284 YAHOO.log('Undo IE', 'info', 'SimpleEditor');
6286 var lis2 = el.parentNode.getElementsByTagName('li');
6287 for (var j = 0; j < lis2.length; j++) {
6288 str += lis2[j].innerHTML + '<br>';
6290 var newEl = this._getDoc().createElement('span');
6291 newEl.innerHTML = str;
6292 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6295 this._getDoc().execCommand(action, '', el.parentNode);
6300 if (this.browser.opera) {
6302 window.setTimeout(function() {
6303 var liso = self._getDoc().getElementsByTagName('li');
6304 for (var i = 0; i < liso.length; i++) {
6305 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6306 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6311 if (this.browser.ie && exec) {
6313 if (this._getRange().html) {
6314 html = '<li>' + this._getRange().html+ '</li>';
6316 var t = this._getRange().text.split('\n');
6319 for (var ie = 0; ie < t.length; ie++) {
6320 html += '<li>' + t[ie] + '</li>';
6323 var txt = this._getRange().text;
6325 html = '<li id="new_list_item">' + txt + '</li>';
6327 html = '<li>' + txt + '</li>';
6331 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6332 var new_item = this._getDoc().getElementById('new_list_item');
6334 var range = this._getDoc().body.createTextRange();
6335 range.moveToElementText(new_item);
6336 range.collapse(false);
6346 * @method cmd_insertorderedlist
6347 * @param value Value passed from the execCommand method
6348 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6350 cmd_insertorderedlist: function(value) {
6351 return [this.cmd_list('ol')];
6354 * @method cmd_insertunorderedlist
6355 * @param value Value passed from the execCommand method
6356 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6358 cmd_insertunorderedlist: function(value) {
6359 return [this.cmd_list('ul')];
6362 * @method cmd_fontname
6363 * @param value Value passed from the execCommand method
6364 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6366 cmd_fontname: function(value) {
6368 selEl = this._getSelectedElement();
6370 this.currentFont = value;
6371 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6372 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6374 } else if (this.get('insert') && !this._hasSelection()) {
6375 YAHOO.log('No selection and no selected element and we are in insert mode', 'info', 'SimpleEditor');
6376 var el = this._createInsertElement({ fontFamily: value });
6382 * @method cmd_fontsize
6383 * @param value Value passed from the execCommand method
6384 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6386 cmd_fontsize: function(value) {
6387 var el = null, go = true;
6388 el = this._getSelectedElement();
6389 if (this.browser.webkit) {
6390 if (this.currentElement[0]) {
6391 if (el == this.currentElement[0]) {
6393 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6394 this._selectNode(el);
6395 this.currentElement[0] = el;
6400 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6401 el = this._getSelectedElement();
6402 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6403 if (this.get('insert') && this.browser.ie) {
6404 var r = this._getRange();
6408 this._selectNode(el);
6410 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6411 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6413 if (this.get('insert') && !this._hasSelection()) {
6414 el = this._createInsertElement({ fontSize: value });
6415 this.currentElement[0] = el;
6416 this._selectNode(this.currentElement[0]);
6418 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6419 this._selectNode(this.currentElement[0]);
6429 * @param {HTMLElement} el The element to swap with
6430 * @param {String} tagName The tagname of the element that you wish to create
6431 * @param {Function} callback (optional) A function to run on the element after it is created, but before it is replaced. An element reference is passed to this function.
6432 * @description This function will create a new element in the DOM and populate it with the contents of another element. Then it will assume it's place.
6434 _swapEl: function(el, tagName, callback) {
6435 var _el = this._getDoc().createElement(tagName);
6437 _el.innerHTML = el.innerHTML;
6439 if (typeof callback == 'function') {
6440 callback.call(this, _el);
6443 el.parentNode.replaceChild(_el, el);
6449 * @method _createInsertElement
6450 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6451 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6452 * @return {HTMLElement}
6454 _createInsertElement: function(css) {
6455 this._createCurrentElement('span', css);
6456 var el = this.currentElement[0];
6457 if (this.browser.webkit) {
6458 //Little Safari Hackery here..
6459 el.innerHTML = '<span class="yui-non"> </span>';
6461 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6462 } else if (this.browser.ie || this.browser.opera) {
6463 el.innerHTML = ' ';
6466 this._selectNode(el, true);
6471 * @method _createCurrentElement
6472 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6473 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6474 * @description This is a work around for the various browser issues with execCommand. This method will run <code>execCommand('fontname', false, 'yui-tmp')</code> on the given selection.
6475 * It will then search the document for an element with the font-family set to <strong>yui-tmp</strong> and replace that with another span that has other information in it, then assign the new span to the
6476 * <code>this.currentElement</code> array, so we now have element references to the elements that were just modified. At this point we can use standard DOM manipulation to change them as we see fit.
6478 _createCurrentElement: function(tagName, tagStyle) {
6479 tagName = ((tagName) ? tagName : 'a');
6482 _doc = this._getDoc();
6484 if (this.currentFont) {
6488 tagStyle.fontFamily = this.currentFont;
6489 this.currentFont = null;
6491 this.currentElement = [];
6493 var _elCreate = function(tagName, tagStyle) {
6495 tagName = ((tagName) ? tagName : 'span');
6496 tagName = tagName.toLowerCase();
6504 el = _doc.createElement(tagName);
6507 el = _doc.createElement(tagName);
6508 if (tagName === 'span') {
6509 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6510 YAHOO.util.Dom.addClass(el, 'yui-tag');
6511 el.setAttribute('tag', tagName);
6514 for (var k in tagStyle) {
6515 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6516 el.style[k] = tagStyle[k];
6524 if (!this._hasSelection()) {
6525 if (this._getDoc().queryCommandEnabled('insertimage')) {
6526 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6527 var imgs = this._getDoc().getElementsByTagName('img');
6528 for (var j = 0; j < imgs.length; j++) {
6529 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6530 el = _elCreate(tagName, tagStyle);
6531 imgs[j].parentNode.replaceChild(el, imgs[j]);
6532 this.currentElement[this.currentElement.length] = el;
6536 if (this.currentEvent) {
6537 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6540 tar = this._getDoc().body;
6545 * @knownissue Safari Cursor Position
6546 * @browser Safari 2.x
6547 * @description The issue here is that we have no way of knowing where the cursor position is
6548 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6550 el = _elCreate(tagName, tagStyle);
6551 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6552 if (this._isElement(tar, 'html')) {
6553 tar = this._getDoc().body;
6555 tar.appendChild(el);
6556 } else if (tar.nextSibling) {
6557 tar.parentNode.insertBefore(el, tar.nextSibling);
6559 tar.parentNode.appendChild(el);
6561 //this.currentElement = el;
6562 this.currentElement[this.currentElement.length] = el;
6563 this.currentEvent = null;
6564 if (this.browser.webkit) {
6565 //Force Safari to focus the new element
6566 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6567 if (this.browser.webkit3) {
6568 this._getSelection().collapseToStart();
6570 this._getSelection().collapse(true);
6575 //Force CSS Styling for this action...
6576 this._setEditorStyle(true);
6577 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6578 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6580 if (!this._isElement(this._getSelectedElement(), 'body')) {
6581 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6582 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6584 for (var _els = 0; _els < __els.length; _els++) {
6585 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6586 for (var e = 0; e < _tmp1.length; e++) {
6587 _tmp[_tmp.length] = _tmp1[e];
6592 for (var i = 0; i < _tmp.length; i++) {
6593 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6594 if (tagName !== 'span') {
6595 el = _elCreate(tagName, tagStyle);
6597 el = _elCreate(_tmp[i].tagName, tagStyle);
6599 el.innerHTML = _tmp[i].innerHTML;
6600 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6601 var fc = _tmp[i].getElementsByTagName('li')[0];
6602 _tmp[i].style.fontFamily = 'inherit';
6603 fc.style.fontFamily = 'inherit';
6604 el.innerHTML = fc.innerHTML;
6607 this.currentElement[this.currentElement.length] = el;
6608 } else if (this._isElement(_tmp[i], 'li')) {
6609 _tmp[i].innerHTML = '';
6610 _tmp[i].appendChild(el);
6611 _tmp[i].style.fontFamily = 'inherit';
6612 this.currentElement[this.currentElement.length] = el;
6614 if (_tmp[i].parentNode) {
6615 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6616 this.currentElement[this.currentElement.length] = el;
6617 this.currentEvent = null;
6618 if (this.browser.webkit) {
6619 //Force Safari to focus the new element
6620 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6621 if (this.browser.webkit3) {
6622 this._getSelection().collapseToStart();
6624 this._getSelection().collapse(true);
6627 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6628 this._getSelection().empty();
6630 if (this.browser.gecko) {
6631 this._getSelection().collapseToStart();
6637 var len = this.currentElement.length;
6638 for (var o = 0; o < len; o++) {
6639 if ((o + 1) != len) { //Skip the last one in the list
6640 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6641 if (this._isElement(this.currentElement[o], 'br')) {
6642 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6651 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6654 saveHTML: function() {
6655 var html = this.cleanHTML();
6656 if (this._textarea) {
6657 this.get('element').value = html;
6659 this.get('element').innerHTML = html;
6661 if (this.get('saveEl') !== this.get('element')) {
6662 var out = this.get('saveEl');
6663 if (Lang.isString(out)) {
6667 if (out.tagName.toLowerCase() === 'textarea') {
6670 out.innerHTML = html;
6677 * @method setEditorHTML
6678 * @param {String} incomingHTML The html content to load into the editor
6679 * @description Loads HTML into the editors body
6681 setEditorHTML: function(incomingHTML) {
6682 var html = this._cleanIncomingHTML(incomingHTML);
6683 html = html.replace(/RIGHT_BRACKET/gi, '{');
6684 html = html.replace(/LEFT_BRACKET/gi, '}');
6685 this._getDoc().body.innerHTML = html;
6689 * @method getEditorHTML
6690 * @description Gets the unprocessed/unfiltered HTML from the editor
6692 getEditorHTML: function() {
6694 var b = this._getDoc().body;
6696 YAHOO.log('Body is null, returning null.', 'error', 'SimpleEditor');
6699 return this._getDoc().body.innerHTML;
6706 * @description This method needs to be called if the Editor was hidden (like in a TabView or Panel). It is used to reset the editor after being in a container that was set to display none.
6709 if (this.browser.gecko) {
6710 this._setDesignMode('on');
6713 if (this.browser.webkit) {
6715 window.setTimeout(function() {
6716 self._setInitialContent.call(self);
6719 //Adding this will close all other Editor window's when showing this one.
6720 if (this.currentWindow) {
6723 //Put the iframe back in place
6724 this.get('iframe').setStyle('position', 'static');
6725 this.get('iframe').setStyle('left', '');
6729 * @description This method needs to be called if the Editor is to be hidden (like in a TabView or Panel). It should be called to clear timeouts and close open editor windows.
6732 //Adding this will close all other Editor window's.
6733 if (this.currentWindow) {
6736 if (this._fixNodesTimer) {
6737 clearTimeout(this._fixNodesTimer);
6738 this._fixNodesTimer = null;
6740 if (this._nodeChangeTimer) {
6741 clearTimeout(this._nodeChangeTimer);
6742 this._nodeChangeTimer = null;
6744 this._lastNodeChange = 0;
6745 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6746 this.get('iframe').setStyle('position', 'absolute');
6747 this.get('iframe').setStyle('left', '-9999px');
6750 * @method _cleanIncomingHTML
6751 * @param {String} html The unfiltered HTML
6752 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6753 * @return {String} The filtered HTML
6755 _cleanIncomingHTML: function(html) {
6756 html = html.replace(/{/gi, 'RIGHT_BRACKET');
6757 html = html.replace(/}/gi, 'LEFT_BRACKET');
6759 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6760 html = html.replace(/<\/strong>/gi, '</b>');
6762 //replace embed before em check
6763 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6764 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6766 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6767 html = html.replace(/<\/em>/gi, '</i>');
6768 html = html.replace(/_moz_dirty=""/gi, '');
6770 //Put embed tags back in..
6771 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6772 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6773 if (this.get('plainText')) {
6774 YAHOO.log('Filtering as plain text', 'info', 'SimpleEditor');
6775 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6776 html = html.replace(/ /gi, ' '); //Replace all double spaces
6777 html = html.replace(/\t/gi, ' '); //Replace all tabs
6779 //Removing Script Tags from the Editor
6780 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6781 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6782 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6783 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6784 //Replace the line feeds
6785 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6787 //Remove Bad HTML elements (used to be script nodes)
6788 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6789 //Replace the lines feeds
6790 html = html.replace(/<YUI_LF>/g, '\n');
6795 * @param {String} html The unfiltered HTML
6796 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6797 * @return {String} The filtered HTML
6799 cleanHTML: function(html) {
6800 //Start Filtering Output
6803 html = this.getEditorHTML();
6805 var markup = this.get('markup');
6806 //Make some backups...
6807 html = this.pre_filter_linebreaks(html, markup);
6810 html = this.filter_msword(html);
6812 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6813 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6815 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6816 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6818 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6819 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6820 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6821 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6823 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6824 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6826 //Convert b and i tags to strong and em tags
6827 if ((markup == 'semantic') || (markup == 'xhtml')) {
6828 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6829 html = html.replace(/<\/i>/gi, '</em>');
6830 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6831 html = html.replace(/<\/b>/gi, '</strong>');
6834 html = html.replace(/_moz_dirty=""/gi, '');
6836 //normalize strikethrough
6837 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6838 html = html.replace(/\/strike>/gi, '/span>');
6842 if (this.browser.ie) {
6843 html = html.replace(/text-decoration/gi, 'text-decoration');
6844 html = html.replace(/font-weight/gi, 'font-weight');
6845 html = html.replace(/_width="([^>]*)"/gi, '');
6846 html = html.replace(/_height="([^>]*)"/gi, '');
6847 //Cleanup Image URL's
6848 var url = this._baseHREF.replace(/\//gi, '\\/'),
6849 re = new RegExp('src="' + url, 'gi');
6850 html = html.replace(re, 'src="');
6852 html = html.replace(/<font/gi, '<font');
6853 html = html.replace(/<\/font>/gi, '</font>');
6854 html = html.replace(/<span/gi, '<span');
6855 html = html.replace(/<\/span>/gi, '</span>');
6856 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6857 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6858 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6859 if (this.browser.webkit) {
6860 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6861 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6863 html = html.replace(/\/u>/gi, '/span>');
6864 if (markup == 'css') {
6865 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6866 html = html.replace(/<\/em>/gi, '</i>');
6867 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6868 html = html.replace(/<\/strong>/gi, '</b>');
6869 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6870 html = html.replace(/\/b>/gi, '/span>');
6871 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6872 html = html.replace(/\/i>/gi, '/span>');
6874 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6876 html = html.replace(/<u/gi, '<u');
6877 html = html.replace(/\/u>/gi, '/u>');
6879 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6880 html = html.replace(/\/ol>/gi, '/ol>');
6881 html = html.replace(/<li/gi, '<li');
6882 html = html.replace(/\/li>/gi, '/li>');
6883 html = this.filter_safari(html);
6885 html = this.filter_internals(html);
6887 html = this.filter_all_rgb(html);
6889 //Replace our backups with the real thing
6890 html = this.post_filter_linebreaks(html, markup);
6892 if (markup == 'xhtml') {
6893 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6894 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6896 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6897 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6899 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6900 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6902 html = this.filter_invalid_lists(html);
6904 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6905 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6907 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6908 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6910 //This should fix &'s in URL's
6911 html = html.replace(/ & /gi, ' YUI_AMP ');
6912 html = html.replace(/ &/gi, ' YUI_AMP_F ');
6913 html = html.replace(/& /gi, ' YUI_AMP_R ');
6914 html = html.replace(/&/gi, '&');
6915 html = html.replace(/ YUI_AMP /gi, ' & ');
6916 html = html.replace(/ YUI_AMP_F /gi, ' &');
6917 html = html.replace(/ YUI_AMP_R /gi, '& ');
6919 //Trim the output, removing whitespace from the beginning and end
6920 html = YAHOO.lang.trim(html);
6922 if (this.get('removeLineBreaks')) {
6923 html = html.replace(/\n/g, '').replace(/\r/g, '');
6924 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6927 for (var v in this.invalidHTML) {
6928 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6929 if (Lang.isObject(v) && v.keepContents) {
6930 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6932 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6937 /* LATER -- Add DOM manipulation
6939 var frag = document.createDocumentFragment();
6940 frag.innerHTML = html;
6942 var ps = frag.getElementsByTagName('p'),
6944 for (var i = 0; i < len; i++) {
6945 var ps2 = ps[i].getElementsByTagName('p');
6951 html = frag.innerHTML;
6955 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6960 * @method filter_msword
6961 * @param String html The HTML string to filter
6962 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6964 filter_msword: function(html) {
6965 if (!this.get('filterWord')) {
6968 //Remove the ms o: tags
6969 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6970 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6972 //Remove the ms w: tags
6973 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6975 //Remove mso-? styles.
6976 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6978 //Remove more bogus MS styles.
6979 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6980 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6981 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6982 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6983 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6984 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6985 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6986 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6988 //Remove XML declarations
6989 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6992 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6994 //Remove language tags
6995 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6997 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6998 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6999 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
7004 * @method filter_invalid_lists
7005 * @param String html The HTML string to filter
7006 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
7008 filter_invalid_lists: function(html) {
7009 html = html.replace(/<\/li>\n/gi, '</li>');
7011 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
7012 html = html.replace(/<\/ol>/gi, '</ol></li>');
7013 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
7015 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
7016 html = html.replace(/<\/ul>/gi, '</ul></li>');
7017 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
7019 html = html.replace(/<\/li>/gi, "</li>");
7020 html = html.replace(/<\/ol>/gi, "</ol>");
7021 html = html.replace(/<ol>/gi, "<ol>");
7022 html = html.replace(/<ul>/gi, "<ul>");
7026 * @method filter_safari
7027 * @param String html The HTML string to filter
7028 * @description Filters strings specific to Safari
7031 filter_safari: function(html) {
7032 if (this.browser.webkit) {
7033 //<span class="Apple-tab-span" style="white-space:pre"> </span>
7034 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
7035 html = html.replace(/Apple-style-span/gi, '');
7036 html = html.replace(/style="line-height: normal;"/gi, '');
7037 html = html.replace(/yui-wk-div/gi, '');
7038 html = html.replace(/yui-wk-p/gi, '');
7042 html = html.replace(/<li><\/li>/gi, '');
7043 html = html.replace(/<li> <\/li>/gi, '');
7044 html = html.replace(/<li> <\/li>/gi, '');
7045 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
7046 if (this.get('ptags')) {
7047 html = html.replace(/<div([^>]*)>/g, '<p$1>');
7048 html = html.replace(/<\/div>/gi, '</p>');
7050 //html = html.replace(/<div>/gi, '<br>');
7051 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>');
7052 html = html.replace(/<\/div>/gi, '');
7058 * @method filter_internals
7059 * @param String html The HTML string to filter
7060 * @description Filters internal RTE strings and bogus attrs we don't want
7063 filter_internals: function(html) {
7064 html = html.replace(/\r/g, '');
7065 //Fix stuff we don't want
7066 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
7068 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
7070 html = html.replace(/yui-tag-span/gi, '');
7071 html = html.replace(/yui-tag/gi, '');
7072 html = html.replace(/yui-non/gi, '');
7073 html = html.replace(/yui-img/gi, '');
7074 html = html.replace(/ tag="span"/gi, '');
7075 html = html.replace(/ class=""/gi, '');
7076 html = html.replace(/ style=""/gi, '');
7077 html = html.replace(/ class=" "/gi, '');
7078 html = html.replace(/ class=" "/gi, '');
7079 html = html.replace(/ target=""/gi, '');
7080 html = html.replace(/ title=""/gi, '');
7082 if (this.browser.ie) {
7083 html = html.replace(/ class= /gi, '');
7084 html = html.replace(/ class= >/gi, '');
7090 * @method filter_all_rgb
7091 * @param String str The HTML string to filter
7092 * @description Converts all RGB color strings found in passed string to a hex color, example: style="color: rgb(0, 255, 0)" converts to style="color: #00ff00"
7095 filter_all_rgb: function(str) {
7096 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
7097 var arr = str.match(exp);
7098 if (Lang.isArray(arr)) {
7099 for (var i = 0; i < arr.length; i++) {
7100 var color = this.filter_rgb(arr[i]);
7101 str = str.replace(arr[i].toString(), color);
7108 * @method filter_rgb
7109 * @param String css The CSS string containing rgb(#,#,#);
7110 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
7113 filter_rgb: function(css) {
7114 if (css.toLowerCase().indexOf('rgb') != -1) {
7115 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
7116 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
7118 if (rgb.length == 5) {
7119 var r = parseInt(rgb[1], 10).toString(16);
7120 var g = parseInt(rgb[2], 10).toString(16);
7121 var b = parseInt(rgb[3], 10).toString(16);
7123 r = r.length == 1 ? '0' + r : r;
7124 g = g.length == 1 ? '0' + g : g;
7125 b = b.length == 1 ? '0' + b : b;
7127 css = "#" + r + g + b;
7133 * @method pre_filter_linebreaks
7134 * @param String html The HTML to filter
7135 * @param String markup The markup type to filter to
7136 * @description HTML Pre Filter
7139 pre_filter_linebreaks: function(html, markup) {
7140 if (this.browser.webkit) {
7141 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
7142 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
7144 html = html.replace(/<br>/gi, '<YUI_BR>');
7145 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
7146 html = html.replace(/<br\/>/gi, '<YUI_BR>');
7147 html = html.replace(/<br \/>/gi, '<YUI_BR>');
7148 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
7149 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
7150 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
7151 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
7153 html = html.replace(/<YUI_BR>$/, '');
7155 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
7156 if (this.browser.ie) {
7157 html = html.replace(/ /g, '\t');
7162 * @method post_filter_linebreaks
7163 * @param String html The HTML to filter
7164 * @param String markup The markup type to filter to
7165 * @description HTML Pre Filter
7168 post_filter_linebreaks: function(html, markup) {
7169 if (markup == 'xhtml') {
7170 html = html.replace(/<YUI_BR>/g, '<br />');
7172 html = html.replace(/<YUI_BR>/g, '<br>');
7177 * @method clearEditorDoc
7178 * @description Clear the doc of the Editor
7180 clearEditorDoc: function() {
7181 this._getDoc().body.innerHTML = ' ';
7184 * @method openWindow
7185 * @description Override Method for Advanced Editor
7187 openWindow: function(win) {
7190 * @method moveWindow
7191 * @description Override Method for Advanced Editor
7193 moveWindow: function() {
7197 * @method _closeWindow
7198 * @description Override Method for Advanced Editor
7200 _closeWindow: function() {
7203 * @method closeWindow
7204 * @description Override Method for Advanced Editor
7206 closeWindow: function() {
7207 //this.unsubscribeAll('afterExecCommand');
7208 this.toolbar.resetAllButtons();
7213 * @description Destroys the editor, all of it's elements and objects.
7216 destroy: function() {
7217 if (this._nodeChangeDelayTimer) {
7218 clearTimeout(this._nodeChangeDelayTimer);
7222 YAHOO.log('Destroying Editor', 'warn', 'SimpleEditor');
7224 YAHOO.log('Destroying Resize', 'warn', 'SimpleEditor');
7225 this.resize.destroy();
7228 YAHOO.log('Unreg DragDrop Instance', 'warn', 'SimpleEditor');
7231 if (this.get('panel')) {
7232 YAHOO.log('Destroying Editor Panel', 'warn', 'SimpleEditor');
7233 this.get('panel').destroy();
7236 this.toolbar.destroy();
7237 YAHOO.log('Restoring TextArea', 'info', 'SimpleEditor');
7238 this.setStyle('visibility', 'visible');
7239 this.setStyle('position', 'static');
7240 this.setStyle('top', '');
7241 this.setStyle('left', '');
7242 var textArea = this.get('element');
7243 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
7244 this.get('element_cont').get('element').innerHTML = '';
7245 this.set('handleSubmit', false); //Remove the submit handler
7250 * @description Returns a string representing the editor.
7253 toString: function() {
7254 var str = 'SimpleEditor';
7255 if (this.get && this.get('element_cont')) {
7256 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
7263 * @event toolbarLoaded
7264 * @description Event is fired during the render process directly after the Toolbar is loaded. Allowing you to attach events to the toolbar. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7265 * @type YAHOO.util.CustomEvent
7269 * @description Event is fired after the cleanHTML method is called.
7270 * @type YAHOO.util.CustomEvent
7273 * @event afterRender
7274 * @description Event is fired after the render process finishes. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7275 * @type YAHOO.util.CustomEvent
7278 * @event editorContentLoaded
7279 * @description Event is fired after the editor iframe's document fully loads and fires it's onload event. From here you can start injecting your own things into the document. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7280 * @type YAHOO.util.CustomEvent
7283 * @event beforeNodeChange
7284 * @description Event fires at the beginning of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7285 * @type YAHOO.util.CustomEvent
7288 * @event afterNodeChange
7289 * @description Event fires at the end of the nodeChange process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7290 * @type YAHOO.util.CustomEvent
7293 * @event beforeExecCommand
7294 * @description Event fires at the beginning of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7295 * @type YAHOO.util.CustomEvent
7298 * @event afterExecCommand
7299 * @description Event fires at the end of the execCommand process. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7300 * @type YAHOO.util.CustomEvent
7303 * @event editorMouseUp
7304 * @param {Event} ev The DOM Event that occured
7305 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7306 * @type YAHOO.util.CustomEvent
7309 * @event editorMouseDown
7310 * @param {Event} ev The DOM Event that occured
7311 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7312 * @type YAHOO.util.CustomEvent
7315 * @event editorDoubleClick
7316 * @param {Event} ev The DOM Event that occured
7317 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7318 * @type YAHOO.util.CustomEvent
7321 * @event editorClick
7322 * @param {Event} ev The DOM Event that occured
7323 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7324 * @type YAHOO.util.CustomEvent
7327 * @event editorKeyUp
7328 * @param {Event} ev The DOM Event that occured
7329 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7330 * @type YAHOO.util.CustomEvent
7333 * @event editorKeyPress
7334 * @param {Event} ev The DOM Event that occured
7335 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7336 * @type YAHOO.util.CustomEvent
7339 * @event editorKeyDown
7340 * @param {Event} ev The DOM Event that occured
7341 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7342 * @type YAHOO.util.CustomEvent
7345 * @event beforeEditorMouseUp
7346 * @param {Event} ev The DOM Event that occured
7347 * @description Fires before editor event, returning false will stop the internal processing.
7348 * @type YAHOO.util.CustomEvent
7351 * @event beforeEditorMouseDown
7352 * @param {Event} ev The DOM Event that occured
7353 * @description Fires before editor event, returning false will stop the internal processing.
7354 * @type YAHOO.util.CustomEvent
7357 * @event beforeEditorDoubleClick
7358 * @param {Event} ev The DOM Event that occured
7359 * @description Fires before editor event, returning false will stop the internal processing.
7360 * @type YAHOO.util.CustomEvent
7363 * @event beforeEditorClick
7364 * @param {Event} ev The DOM Event that occured
7365 * @description Fires before editor event, returning false will stop the internal processing.
7366 * @type YAHOO.util.CustomEvent
7369 * @event beforeEditorKeyUp
7370 * @param {Event} ev The DOM Event that occured
7371 * @description Fires before editor event, returning false will stop the internal processing.
7372 * @type YAHOO.util.CustomEvent
7375 * @event beforeEditorKeyPress
7376 * @param {Event} ev The DOM Event that occured
7377 * @description Fires before editor event, returning false will stop the internal processing.
7378 * @type YAHOO.util.CustomEvent
7381 * @event beforeEditorKeyDown
7382 * @param {Event} ev The DOM Event that occured
7383 * @description Fires before editor event, returning false will stop the internal processing.
7384 * @type YAHOO.util.CustomEvent
7388 * @event editorWindowFocus
7389 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7390 * @type YAHOO.util.CustomEvent
7393 * @event editorWindowBlur
7394 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7395 * @type YAHOO.util.CustomEvent
7400 * @description Singleton object used to track the open window objects and panels across the various open editors
7404 YAHOO.widget.EditorInfo = {
7407 * @property _instances
7408 * @description A reference to all editors on the page.
7414 * @property blankImage
7415 * @description A reference to the blankImage url
7422 * @description A reference to the currently open window object in any editor on the page.
7423 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7429 * @description A reference to the currently open panel in any editor on the page.
7430 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7434 * @method getEditorById
7435 * @description Returns a reference to the Editor object associated with the given textarea
7436 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7437 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7439 getEditorById: function(id) {
7440 if (!YAHOO.lang.isString(id)) {
7441 //Not a string, assume a node Reference
7444 if (this._instances[id]) {
7445 return this._instances[id];
7451 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved.
7452 * @param {HTMLElement} form The form to check if this Editor instance belongs to
7454 saveAll: function(form) {
7455 var i, e, items = YAHOO.widget.EditorInfo._instances;
7458 if (Lang.hasOwnProperty(items, i)) {
7460 if (e.get('element').form && (e.get('element').form == form)) {
7467 if (Lang.hasOwnProperty(items, i)) {
7468 items[i].saveHTML();
7475 * @description Returns a string representing the EditorInfo.
7478 toString: function() {
7480 for (var i in this._instances) {
7481 if (Lang.hasOwnProperty(this._instances, i)) {
7485 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7495 * @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>
7496 * @namespace YAHOO.widget
7497 * @requires yahoo, dom, element, event, container_core, simpleeditor
7498 * @optional dragdrop, animation, menu, button, resize
7502 var Dom = YAHOO.util.Dom,
7503 Event = YAHOO.util.Event,
7505 Toolbar = YAHOO.widget.Toolbar;
7508 * 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.
7511 * @extends YAHOO.widget.SimpleEditor
7512 * @param {String/HTMLElement} el The textarea element to turn into an editor.
7513 * @param {Object} attrs Object liternal containing configuration parameters.
7516 YAHOO.widget.Editor = function(el, attrs) {
7517 YAHOO.log('Editor Initalizing', 'info', 'Editor');
7518 YAHOO.widget.Editor.superclass.constructor.call(this, el, attrs);
7521 YAHOO.extend(YAHOO.widget.Editor, YAHOO.widget.SimpleEditor, {
7524 * @property _undoCache
7525 * @description An Array hash of the Undo Levels.
7531 * @property _undoLevel
7532 * @description The index of the current undo state.
7538 * @method _hasUndoLevel
7539 * @description Checks to see if we have an undo level available
7542 _hasUndoLevel: function() {
7543 return ((this._undoCache.length > 1) && this._undoLevel);
7547 * @method _undoNodeChange
7548 * @description nodeChange listener for undo processing
7550 _undoNodeChange: function() {
7551 var undo_button = this.toolbar.getButtonByValue('undo'),
7552 redo_button = this.toolbar.getButtonByValue('redo');
7553 if (undo_button && redo_button) {
7554 if (this._hasUndoLevel()) {
7555 this.toolbar.enableButton(undo_button);
7557 if (this._undoLevel < this._undoCache.length) {
7558 this.toolbar.enableButton(redo_button);
7561 this._lastCommand = null;
7565 * @method _checkUndo
7566 * @description Prunes the undo cache when it reaches the maxUndo config
7568 _checkUndo: function() {
7569 var len = this._undoCache.length,
7571 if (len >= this.get('maxUndo')) {
7572 //YAHOO.log('Undo cache too large (' + len + '), pruning..', 'info', 'SimpleEditor');
7573 for (var i = (len - this.get('maxUndo')); i < len; i++) {
7574 tmp.push(this._undoCache[i]);
7576 this._undoCache = tmp;
7582 * @description Puts the content of the Editor into the _undoCache.
7583 * //TODO Convert the hash to a series of TEXTAREAS to store state in.
7584 * @param {String} str The content of the Editor
7586 _putUndo: function(str) {
7587 if (this._undoLevel === this._undoCache.length) {
7588 this._undoCache.push(str);
7589 this._undoLevel = this._undoCache.length;
7591 var str = this.getEditorHTML();
7592 var last = this._undoCache[this._undoLevel];
7595 this._undoCache = [];
7596 this._undoLevel = 0;
7604 * @description Get's a level from the undo cache.
7605 * @param {Number} index The index of the undo level we want to get.
7608 _getUndo: function(index) {
7609 this._undoLevel = index;
7610 return this._undoCache[index];
7614 * @method _storeUndo
7615 * @description Method to call when you want to store an undo state. Currently called from nodeChange and _handleKeyUp
7617 _storeUndo: function() {
7618 if (this._lastCommand === 'undo' || this._lastCommand === 'redo') {
7621 if (!this._undoCache) {
7622 this._undoCache = [];
7623 this._undoLevel = 0;
7626 var str = this.getEditorHTML();
7627 //var last = this._undoCache[this._undoCache.length - 1];
7628 var last = this._undoCache[this._undoLevel - 1];
7631 //YAHOO.log('Storing Undo', 'info', 'SimpleEditor');
7635 //YAHOO.log('Storing Undo', 'info', 'SimpleEditor');
7638 this._undoNodeChange();
7641 * @property STR_BEFORE_EDITOR
7642 * @description The accessibility string for the element before the iFrame
7645 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>',
7647 * @property STR_CLOSE_WINDOW
7648 * @description The Title of the close button in the Editor Window
7651 STR_CLOSE_WINDOW: 'Close Window',
7653 * @property STR_CLOSE_WINDOW_NOTE
7654 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
7657 STR_CLOSE_WINDOW_NOTE: 'To close this window use the Control + Shift + W key',
7659 * @property STR_IMAGE_PROP_TITLE
7660 * @description The title for the Image Property Editor Window
7663 STR_IMAGE_PROP_TITLE: 'Image Options',
7665 * @property STR_IMAGE_TITLE
7666 * @description The label string for Image Description
7669 STR_IMAGE_TITLE: 'Description',
7671 * @property STR_IMAGE_SIZE
7672 * @description The label string for Image Size
7675 STR_IMAGE_SIZE: 'Size',
7677 * @property STR_IMAGE_ORIG_SIZE
7678 * @description The label string for Original Image Size
7681 STR_IMAGE_ORIG_SIZE: 'Original Size',
7683 * @property STR_IMAGE_COPY
7684 * @description The label string for the image copy and paste message for Opera and Safari
7687 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>',
7689 * @property STR_IMAGE_PADDING
7690 * @description The label string for the image padding.
7693 STR_IMAGE_PADDING: 'Padding',
7695 * @property STR_IMAGE_BORDER
7696 * @description The label string for the image border.
7699 STR_IMAGE_BORDER: 'Border',
7701 * @property STR_IMAGE_BORDER_SIZE
7702 * @description The label string for the image border size.
7705 STR_IMAGE_BORDER_SIZE: 'Border Size',
7707 * @property STR_IMAGE_BORDER_TYPE
7708 * @description The label string for the image border type.
7711 STR_IMAGE_BORDER_TYPE: 'Border Type',
7713 * @property STR_IMAGE_TEXTFLOW
7714 * @description The label string for the image text flow.
7717 STR_IMAGE_TEXTFLOW: 'Text Flow',
7719 * @property STR_LOCAL_FILE_WARNING
7720 * @description The label string for the local file warning.
7723 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>',
7725 * @property STR_LINK_PROP_TITLE
7726 * @description The label string for the Link Property Editor Window.
7729 STR_LINK_PROP_TITLE: 'Link Options',
7731 * @property STR_LINK_PROP_REMOVE
7732 * @description The label string for the Remove link from text link inside the property editor.
7735 STR_LINK_PROP_REMOVE: 'Remove link from text',
7737 * @property STR_LINK_NEW_WINDOW
7738 * @description The string for the open in a new window label.
7741 STR_LINK_NEW_WINDOW: 'Open in a new window.',
7743 * @property STR_LINK_TITLE
7744 * @description The string for the link description.
7747 STR_LINK_TITLE: 'Description',
7749 * @property STR_NONE
7750 * @description The string for the word none.
7756 * @property CLASS_LOCAL_FILE
7757 * @description CSS class applied to an element when it's found to have a local url.
7760 CLASS_LOCAL_FILE: 'warning-localfile',
7763 * @property CLASS_HIDDEN
7764 * @description CSS class applied to the body when the hiddenelements button is pressed.
7767 CLASS_HIDDEN: 'yui-hidden',
7770 * @description The Editor class' initialization method
7772 init: function(p_oElement, p_oAttributes) {
7773 YAHOO.log('init', 'info', 'Editor');
7776 if (!this._defaultToolbar) {
7777 this._defaultToolbar = {
7779 titlebar: 'Text Editing Tools',
7781 buttonType: 'advanced',
7783 { group: 'fontstyle', label: 'Font Name and Size',
7785 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
7787 { text: 'Arial', checked: true },
7788 { text: 'Arial Black' },
7789 { text: 'Comic Sans MS' },
7790 { text: 'Courier New' },
7791 { text: 'Lucida Console' },
7793 { text: 'Times New Roman' },
7794 { text: 'Trebuchet MS' },
7798 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
7801 { type: 'separator' },
7802 { group: 'textstyle', label: 'Font Style',
7804 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
7805 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
7806 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
7807 { type: 'separator' },
7808 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true },
7809 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true }
7812 { type: 'separator' },
7813 { group: 'textstyle2', label: ' ',
7815 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
7816 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true },
7817 { type: 'separator' },
7818 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true },
7819 { type: 'push', label: 'Show/Hide Hidden Elements', value: 'hiddenelements' }
7822 { type: 'separator' },
7823 { group: 'undoredo', label: 'Undo/Redo',
7825 { type: 'push', label: 'Undo', value: 'undo', disabled: true },
7826 { type: 'push', label: 'Redo', value: 'redo', disabled: true }
7830 { type: 'separator' },
7831 { group: 'alignment', label: 'Alignment',
7833 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
7834 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
7835 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
7836 { type: 'push', label: 'Justify', value: 'justifyfull' }
7839 { type: 'separator' },
7840 { group: 'parastyle', label: 'Paragraph Style',
7842 { type: 'select', label: 'Normal', value: 'heading', disabled: true,
7844 { text: 'Normal', value: 'none', checked: true },
7845 { text: 'Header 1', value: 'h1' },
7846 { text: 'Header 2', value: 'h2' },
7847 { text: 'Header 3', value: 'h3' },
7848 { text: 'Header 4', value: 'h4' },
7849 { text: 'Header 5', value: 'h5' },
7850 { text: 'Header 6', value: 'h6' }
7855 { type: 'separator' },
7857 { group: 'indentlist2', label: 'Indenting and Lists',
7859 { type: 'push', label: 'Indent', value: 'indent', disabled: true },
7860 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
7861 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
7862 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
7865 { type: 'separator' },
7866 { group: 'insertitem', label: 'Insert Item',
7868 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
7869 { type: 'push', label: 'Insert Image', value: 'insertimage' }
7876 if (!this._defaultImageToolbarConfig) {
7877 this._defaultImageToolbarConfig = {
7878 buttonType: this._defaultToolbar.buttonType,
7880 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
7882 { type: 'push', label: 'Left', value: 'left' },
7883 { type: 'push', label: 'Inline', value: 'inline' },
7884 { type: 'push', label: 'Block', value: 'block' },
7885 { type: 'push', label: 'Right', value: 'right' }
7888 { type: 'separator' },
7889 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
7891 { type: 'spin', label: '0', value: 'padding', range: [0, 50] }
7894 { type: 'separator' },
7895 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
7897 { type: 'select', label: this.STR_IMAGE_BORDER_SIZE, value: 'bordersize',
7899 { text: 'none', value: '0', checked: true },
7900 { text: '1px', value: '1' },
7901 { text: '2px', value: '2' },
7902 { text: '3px', value: '3' },
7903 { text: '4px', value: '4' },
7904 { text: '5px', value: '5' }
7907 { type: 'select', label: this.STR_IMAGE_BORDER_TYPE, value: 'bordertype', disabled: true,
7909 { text: 'Solid', value: 'solid', checked: true },
7910 { text: 'Dashed', value: 'dashed' },
7911 { text: 'Dotted', value: 'dotted' }
7914 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
7921 YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes);
7923 _render: function() {
7924 YAHOO.widget.Editor.superclass._render.apply(this, arguments);
7926 //Render the panel in another thread and delay it a little..
7927 window.setTimeout(function() {
7928 self._renderPanel.call(self);
7932 * @method initAttributes
7933 * @description Initializes all of the configuration attributes used to create
7935 * @param {Object} attr Object literal specifying a set of
7936 * configuration attributes used to create the editor.
7938 initAttributes: function(attr) {
7939 YAHOO.widget.Editor.superclass.initAttributes.call(this, attr);
7942 * @attribute localFileWarning
7943 * @description Should we throw the warning if we detect a file that is local to their machine?
7947 this.setAttributeConfig('localFileWarning', {
7948 value: attr.locaFileWarning || true
7952 * @attribute hiddencss
7953 * @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>
7954 * @default <code><pre>
7955 .yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u,
7956 .yui-hidden div, .yui-hidden p, .yui-hidden span, .yui-hidden img, .yui-hidden ul, .yui-hidden ol,
7957 .yui-hidden li, .yui-hidden table {
7958 border: 1px dotted #ccc;
7960 .yui-hidden .yui-non {
7968 this.setAttributeConfig('hiddencss', {
7969 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; }',
7977 * @description A reference to the HTML elements used for the body of Editor Windows.
7982 * @method _defaultImageToolbar
7983 * @description A reference to the Toolbar Object inside Image Editor Window.
7985 _defaultImageToolbar: null,
7988 * @method _defaultImageToolbarConfig
7989 * @description Config to be used for the default Image Editor Window.
7991 _defaultImageToolbarConfig: null,
7995 * @description Fix href and imgs as well as remove invalid HTML.
7997 _fixNodes: function() {
7998 YAHOO.widget.Editor.superclass._fixNodes.call(this);
8002 var imgs = this._getDoc().getElementsByTagName('img');
8003 for (var im = 0; im < imgs.length; im++) {
8004 if (imgs[im].getAttribute('href', 2)) {
8005 url = imgs[im].getAttribute('src', 2);
8006 if (this._isLocalFile(url)) {
8007 Dom.addClass(imgs[im], this.CLASS_LOCAL_FILE);
8009 Dom.removeClass(imgs[im], this.CLASS_LOCAL_FILE);
8013 var fakeAs = this._getDoc().body.getElementsByTagName('a');
8014 for (var a = 0; a < fakeAs.length; a++) {
8015 if (fakeAs[a].getAttribute('href', 2)) {
8016 url = fakeAs[a].getAttribute('href', 2);
8017 if (this._isLocalFile(url)) {
8018 Dom.addClass(fakeAs[a], this.CLASS_LOCAL_FILE);
8020 Dom.removeClass(fakeAs[a], this.CLASS_LOCAL_FILE);
8028 * @property _disabled
8029 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
8032 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent' ],
8035 * @property _alwaysDisabled
8036 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
8039 _alwaysDisabled: { 'outdent': true },
8042 * @property _alwaysEnabled
8043 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
8046 _alwaysEnabled: { hiddenelements: true },
8049 * @method _handleKeyDown
8050 * @param {Event} ev The event we are working on.
8051 * @description Override method that handles some new keydown events inside the iFrame document.
8053 _handleKeyDown: function(ev) {
8054 YAHOO.widget.Editor.superclass._handleKeyDown.call(this, ev);
8059 switch (ev.keyCode) {
8061 case this._keyMap.JUSTIFY_LEFT.key: //Left
8062 if (this._checkKey(this._keyMap.JUSTIFY_LEFT, ev)) {
8063 action = 'justifyleft';
8067 //case 220: //Center
8068 case this._keyMap.JUSTIFY_CENTER.key:
8069 if (this._checkKey(this._keyMap.JUSTIFY_CENTER, ev)) {
8070 action = 'justifycenter';
8075 case this._keyMap.JUSTIFY_RIGHT.key:
8076 if (this._checkKey(this._keyMap.JUSTIFY_RIGHT, ev)) {
8077 action = 'justifyright';
8082 if (doExec && action) {
8083 this.execCommand(action, null);
8084 Event.stopEvent(ev);
8090 * @method _renderCreateLinkWindow
8091 * @description Pre renders the CreateLink window so we get faster window opening.
8093 _renderCreateLinkWindow: function() {
8094 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>';
8095 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>';
8096 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>';
8098 var body = document.createElement('div');
8099 body.innerHTML = str;
8101 var unlinkCont = document.createElement('div');
8102 unlinkCont.className = 'removeLink';
8103 var unlink = document.createElement('a');
8105 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
8106 unlink.title = this.STR_LINK_PROP_REMOVE;
8107 Event.on(unlink, 'click', function(ev) {
8108 Event.stopEvent(ev);
8109 this.unsubscribeAll('afterExecCommand');
8110 this.execCommand('unlink');
8113 unlinkCont.appendChild(unlink);
8114 body.appendChild(unlinkCont);
8116 this._windows.createlink = {};
8117 this._windows.createlink.body = body;
8118 //body.style.display = 'none';
8119 Event.on(body, 'keyup', function(e) {
8120 Event.stopPropagation(e);
8122 this.get('panel').editor_form.appendChild(body);
8123 this.fireEvent('windowCreateLinkRender', { type: 'windowCreateLinkRender', panel: this.get('panel'), body: body });
8126 _handleCreateLinkClick: function() {
8127 var el = this._getSelectedElement();
8128 if (this._isElement(el, 'img')) {
8129 this.STOP_EXEC_COMMAND = true;
8130 this.currentElement[0] = el;
8131 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
8132 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
8135 if (this.get('limitCommands')) {
8136 if (!this.toolbar.getButtonByValue('createlink')) {
8137 YAHOO.log('Toolbar Button for (createlink) was not found, skipping exec.', 'info', 'Editor');
8142 this.on('afterExecCommand', function() {
8143 var win = new YAHOO.widget.EditorWindow('createlink', {
8147 var el = this.currentElement[0],
8154 if (el.getAttribute('href', 2) !== null) {
8155 url = el.getAttribute('href', 2);
8156 if (this._isLocalFile(url)) {
8157 //Local File throw Warning
8158 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8159 win.setFooter(this.STR_LOCAL_FILE_WARNING);
8165 if (el.getAttribute('title') !== null) {
8166 title = el.getAttribute('title');
8168 if (el.getAttribute('target') !== null) {
8169 target = el.getAttribute('target');
8173 if (this._windows.createlink && this._windows.createlink.body) {
8174 body = this._windows.createlink.body;
8176 body = this._renderCreateLinkWindow();
8179 win.setHeader(this.STR_LINK_PROP_TITLE);
8182 Event.purgeElement(this.get('id') + '_createlink_url');
8184 Dom.get(this.get('id') + '_createlink_url').value = url;
8185 Dom.get(this.get('id') + '_createlink_title').value = title;
8186 Dom.get(this.get('id') + '_createlink_target').checked = ((target) ? true : false);
8189 Event.onAvailable(this.get('id') + '_createlink_url', function() {
8190 var id = this.get('id');
8191 window.setTimeout(function() {
8193 YAHOO.util.Dom.get(id + '_createlink_url').focus();
8197 if (this._isLocalFile(url)) {
8198 //Local File throw Warning
8199 Dom.addClass(this.get('id') + '_createlink_url', 'warning');
8200 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8201 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8203 Dom.removeClass(this.get('id') + '_createlink_url', 'warning');
8204 this.get('panel').setFooter(' ');
8206 Event.on(this.get('id') + '_createlink_url', 'blur', function() {
8207 var url = Dom.get(this.get('id') + '_createlink_url');
8208 if (this._isLocalFile(url.value)) {
8209 //Local File throw Warning
8210 Dom.addClass(url, 'warning');
8211 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8212 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8214 Dom.removeClass(url, 'warning');
8215 this.get('panel').setFooter(' ');
8220 this.openWindow(win);
8226 * @method _handleCreateLinkWindowClose
8227 * @description Handles the closing of the Link Properties Window.
8229 _handleCreateLinkWindowClose: function() {
8231 var url = Dom.get(this.get('id') + '_createlink_url'),
8232 target = Dom.get(this.get('id') + '_createlink_target'),
8233 title = Dom.get(this.get('id') + '_createlink_title'),
8234 el = arguments[0].win.el,
8237 if (url && url.value) {
8238 var urlValue = url.value;
8239 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8240 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8241 //Found an @ sign, prefix with mailto:
8242 urlValue = 'mailto:' + urlValue;
8244 // :// not found adding
8245 if (urlValue.substring(0, 1) != '#') {
8246 urlValue = 'http:/'+'/' + urlValue;
8251 el.setAttribute('href', urlValue);
8252 if (target.checked) {
8253 el.setAttribute('target', target.value);
8255 el.setAttribute('target', '');
8257 el.setAttribute('title', ((title.value) ? title.value : ''));
8260 var _span = this._getDoc().createElement('span');
8261 _span.innerHTML = el.innerHTML;
8262 Dom.addClass(_span, 'yui-non');
8263 el.parentNode.replaceChild(_span, el);
8265 Dom.removeClass(url, 'warning');
8266 Dom.get(this.get('id') + '_createlink_url').value = '';
8267 Dom.get(this.get('id') + '_createlink_title').value = '';
8268 Dom.get(this.get('id') + '_createlink_target').checked = false;
8270 this.currentElement = [];
8275 * @method _renderInsertImageWindow
8276 * @description Pre renders the InsertImage window so we get faster window opening.
8278 _renderInsertImageWindow: function() {
8279 var el = this.currentElement[0];
8280 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>';
8281 var body = document.createElement('div');
8282 body.innerHTML = str;
8284 var tbarCont = document.createElement('div');
8285 tbarCont.id = this.get('id') + '_img_toolbar';
8286 body.appendChild(tbarCont);
8288 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>';
8289 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>';
8290 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>';
8291 var div = document.createElement('div');
8292 div.innerHTML = str2;
8293 body.appendChild(div);
8296 Lang.augmentObject(o, this._defaultImageToolbarConfig); //Break the config reference
8298 var tbar = new YAHOO.widget.Toolbar(tbarCont, o);
8299 tbar.editor_el = el;
8300 this._defaultImageToolbar = tbar;
8302 var cont = tbar.get('cont');
8303 var hw = document.createElement('div');
8304 hw.className = 'yui-toolbar-group yui-toolbar-group-height-width height-width';
8305 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
8306 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>';
8307 cont.insertBefore(hw, cont.firstChild);
8309 Event.onAvailable(this.get('id') + '_insertimage_width', function() {
8310 Event.on(this.get('id') + '_insertimage_width', 'blur', function() {
8311 var value = parseInt(Dom.get(this.get('id') + '_insertimage_width').value, 10);
8313 this._defaultImageToolbar.editor_el.style.width = value + 'px';
8314 //Removed moveWindow call so the window doesn't jump
8315 //this.moveWindow();
8319 Event.onAvailable(this.get('id') + '_insertimage_height', function() {
8320 Event.on(this.get('id') + '_insertimage_height', 'blur', function() {
8321 var value = parseInt(Dom.get(this.get('id') + '_insertimage_height').value, 10);
8323 this._defaultImageToolbar.editor_el.style.height = value + 'px';
8324 //Removed moveWindow call so the window doesn't jump
8325 //this.moveWindow();
8331 tbar.on('colorPickerClicked', function(o) {
8332 var size = '1', type = 'solid', color = 'black', el = this._defaultImageToolbar.editor_el;
8334 if (el.style.borderLeftWidth) {
8335 size = parseInt(el.style.borderLeftWidth, 10);
8337 if (el.style.borderLeftStyle) {
8338 type = el.style.borderLeftStyle;
8340 if (el.style.borderLeftColor) {
8341 color = el.style.borderLeftColor;
8343 var borderString = size + 'px ' + type + ' #' + o.color;
8344 el.style.border = borderString;
8347 tbar.on('buttonClick', function(o) {
8348 var value = o.button.value,
8349 el = this._defaultImageToolbar.editor_el,
8351 if (o.button.menucmd) {
8352 value = o.button.menucmd;
8354 var size = '1', type = 'solid', color = 'black';
8356 /* All border calcs are done on the left border
8357 since our default interface only supports
8358 one border size/type and color */
8359 if (el.style.borderLeftWidth) {
8360 size = parseInt(el.style.borderLeftWidth, 10);
8362 if (el.style.borderLeftStyle) {
8363 type = el.style.borderLeftStyle;
8365 if (el.style.borderLeftColor) {
8366 color = el.style.borderLeftColor;
8370 if (this.browser.webkit && this._lastImage) {
8371 Dom.removeClass(this._lastImage, 'selected');
8372 this._lastImage = null;
8375 borderString = parseInt(o.button.value, 10) + 'px ' + type + ' ' + color;
8376 el.style.border = borderString;
8377 if (parseInt(o.button.value, 10) > 0) {
8378 tbar.enableButton('bordertype');
8379 tbar.enableButton('bordercolor');
8381 tbar.disableButton('bordertype');
8382 tbar.disableButton('bordercolor');
8386 if (this.browser.webkit && this._lastImage) {
8387 Dom.removeClass(this._lastImage, 'selected');
8388 this._lastImage = null;
8390 borderString = size + 'px ' + o.button.value + ' ' + color;
8391 el.style.border = borderString;
8395 tbar.deselectAllButtons();
8396 el.style.display = '';
8397 el.align = o.button.value;
8400 tbar.deselectAllButtons();
8401 el.style.display = '';
8405 tbar.deselectAllButtons();
8406 el.style.display = 'block';
8407 el.align = 'center';
8410 var _button = tbar.getButtonById(o.button.id);
8411 el.style.margin = _button.get('label') + 'px';
8414 tbar.selectButton(o.button.value);
8415 if (value !== 'padding') {
8422 if (this.get('localFileWarning')) {
8423 Event.on(this.get('id') + '_insertimage_link', 'blur', function() {
8424 var url = Dom.get(this.get('id') + '_insertimage_link');
8425 if (this._isLocalFile(url.value)) {
8426 //Local File throw Warning
8427 Dom.addClass(url, 'warning');
8428 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8429 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8431 Dom.removeClass(url, 'warning');
8432 this.get('panel').setFooter(' ');
8434 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8435 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8441 Event.on(this.get('id') + '_insertimage_url', 'blur', function() {
8442 var url = Dom.get(this.get('id') + '_insertimage_url'),
8443 el = this.currentElement[0];
8445 if (url.value && el) {
8446 if (url.value == el.getAttribute('src', 2)) {
8447 YAHOO.log('Images are the same, bail on blur handler', 'info', 'Editor');
8451 YAHOO.log('Images are different, process blur handler', 'info', 'Editor');
8452 if (this._isLocalFile(url.value)) {
8453 //Local File throw Warning
8454 Dom.addClass(url, 'warning');
8455 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
8456 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING);
8457 } else if (this.currentElement[0]) {
8458 Dom.removeClass(url, 'warning');
8459 this.get('panel').setFooter(' ');
8461 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8462 this.get('panel').setFooter(this.STR_IMAGE_COPY);
8465 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8466 this.currentElement[0].setAttribute('src', url.value);
8470 img.onerror = function() {
8471 url.value = self.STR_IMAGE_HERE;
8472 img.setAttribute('src', self.get('blankimage'));
8473 self.currentElement[0].setAttribute('src', self.get('blankimage'));
8474 YAHOO.util.Dom.get(self.get('id') + '_insertimage_height').value = img.height;
8475 YAHOO.util.Dom.get(self.get('id') + '_insertimage_width').value = img.width;
8477 var id = this.get('id');
8478 window.setTimeout(function() {
8479 YAHOO.util.Dom.get(id + '_insertimage_height').value = img.height;
8480 YAHOO.util.Dom.get(id + '_insertimage_width').value = img.width;
8481 if (self.currentElement && self.currentElement[0]) {
8482 if (!self.currentElement[0]._height) {
8483 self.currentElement[0]._height = img.height;
8485 if (!self.currentElement[0]._width) {
8486 self.currentElement[0]._width = img.width;
8489 //Removed moveWindow call so the window doesn't jump
8490 //self.moveWindow();
8491 }, 800); //Bumped the timeout up to account for larger images..
8493 if (url.value != this.STR_IMAGE_HERE) {
8494 img.src = url.value;
8502 this._windows.insertimage = {};
8503 this._windows.insertimage.body = body;
8504 //body.style.display = 'none';
8505 this.get('panel').editor_form.appendChild(body);
8506 this.fireEvent('windowInsertImageRender', { type: 'windowInsertImageRender', panel: this.get('panel'), body: body, toolbar: tbar });
8511 * @method _handleInsertImageClick
8512 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
8514 _handleInsertImageClick: function() {
8515 if (this.get('limitCommands')) {
8516 if (!this.toolbar.getButtonByValue('insertimage')) {
8517 YAHOO.log('Toolbar Button for (insertimage) was not found, skipping exec.', 'info', 'Editor');
8521 this.on('afterExecCommand', function() {
8522 YAHOO.log('afterExecCommand :: _handleInsertImageClick', 'info', 'Editor');
8523 var el = this.currentElement[0],
8537 win = new YAHOO.widget.EditorWindow('insertimage', {
8542 el = this._getSelectedElement();
8546 if (el.getAttribute('src')) {
8547 src = el.getAttribute('src', 2);
8548 if (src.indexOf(this.get('blankimage')) != -1) {
8549 src = this.STR_IMAGE_HERE;
8553 if (el.getAttribute('alt', 2)) {
8554 title = el.getAttribute('alt', 2);
8556 if (el.getAttribute('title', 2)) {
8557 title = el.getAttribute('title', 2);
8560 if (el.parentNode && this._isElement(el.parentNode, 'a')) {
8561 link = el.parentNode.getAttribute('href', 2);
8562 if (el.parentNode.getAttribute('target') !== null) {
8563 target = el.parentNode.getAttribute('target');
8566 height = parseInt(el.height, 10);
8567 width = parseInt(el.width, 10);
8568 if (el.style.height) {
8569 height = parseInt(el.style.height, 10);
8571 if (el.style.width) {
8572 width = parseInt(el.style.width, 10);
8574 if (el.style.margin) {
8575 padding = parseInt(el.style.margin, 10);
8579 el._height = height;
8584 oheight = el._height;
8588 if (this._windows.insertimage && this._windows.insertimage.body) {
8589 body = this._windows.insertimage.body;
8590 this._defaultImageToolbar.resetAllButtons();
8592 body = this._renderInsertImageWindow();
8595 tbar = this._defaultImageToolbar;
8596 tbar.editor_el = el;
8602 if (el.style.borderLeftWidth) {
8603 bsize = parseInt(el.style.borderLeftWidth, 10);
8605 if (el.style.borderLeftStyle) {
8606 btype = el.style.borderLeftStyle;
8608 var bs_button = tbar.getButtonByValue('bordersize'),
8609 bSizeStr = ((parseInt(bsize, 10) > 0) ? '' : this.STR_NONE);
8610 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">' + bSizeStr + '</span>');
8611 this._updateMenuChecked('bordersize', bsize, tbar);
8613 var bt_button = tbar.getButtonByValue('bordertype');
8614 bt_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">asdfa</span>');
8615 this._updateMenuChecked('bordertype', btype, tbar);
8616 if (parseInt(bsize, 10) > 0) {
8617 tbar.enableButton(bt_button);
8618 tbar.enableButton(bs_button);
8619 tbar.enableButton('bordercolor');
8622 if ((el.align == 'right') || (el.align == 'left')) {
8623 tbar.selectButton(el.align);
8624 } else if (el.style.display == 'block') {
8625 tbar.selectButton('block');
8627 tbar.selectButton('inline');
8629 if (parseInt(el.style.marginLeft, 10) > 0) {
8630 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft, 10));
8632 if (el.style.borderSize) {
8633 tbar.selectButton('bordersize');
8634 tbar.selectButton(parseInt(el.style.borderSize, 10));
8636 tbar.getButtonByValue('padding').set('label', ''+padding);
8640 win.setHeader(this.STR_IMAGE_PROP_TITLE);
8643 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) {
8644 win.setFooter(this.STR_IMAGE_COPY);
8646 this.openWindow(win);
8647 Dom.get(this.get('id') + '_insertimage_url').value = src;
8648 Dom.get(this.get('id') + '_insertimage_title').value = title;
8649 Dom.get(this.get('id') + '_insertimage_link').value = link;
8650 Dom.get(this.get('id') + '_insertimage_target').checked = ((target) ? true : false);
8651 Dom.get(this.get('id') + '_insertimage_width').value = width;
8652 Dom.get(this.get('id') + '_insertimage_height').value = height;
8655 if (((height != oheight) || (width != owidth)) && (!blankimage)) {
8656 var s = document.createElement('span');
8657 s.className = 'info';
8658 s.innerHTML = this.STR_IMAGE_ORIG_SIZE + ': ('+ owidth +' x ' + oheight + ')';
8659 if (Dom.get(this.get('id') + '_insertimage_height').nextSibling) {
8660 var old = Dom.get(this.get('id') + '_insertimage_height').nextSibling;
8661 old.parentNode.removeChild(old);
8663 Dom.get(this.get('id') + '_insertimage_height').parentNode.appendChild(s);
8666 this.toolbar.selectButton('insertimage');
8667 var id = this.get('id');
8668 window.setTimeout(function() {
8670 YAHOO.util.Dom.get(id + '_insertimage_url').focus();
8672 YAHOO.util.Dom.get(id + '_insertimage_url').select();
8681 * @method _handleInsertImageWindowClose
8682 * @description Handles the closing of the Image Properties Window.
8684 _handleInsertImageWindowClose: function() {
8685 var url = Dom.get(this.get('id') + '_insertimage_url'),
8686 title = Dom.get(this.get('id') + '_insertimage_title'),
8687 link = Dom.get(this.get('id') + '_insertimage_link'),
8688 target = Dom.get(this.get('id') + '_insertimage_target'),
8689 el = arguments[0].win.el;
8691 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
8692 el.setAttribute('src', url.value);
8693 el.setAttribute('title', title.value);
8694 el.setAttribute('alt', title.value);
8695 var par = el.parentNode;
8697 var urlValue = link.value;
8698 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8699 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
8700 //Found an @ sign, prefix with mailto:
8701 urlValue = 'mailto:' + urlValue;
8703 // :// not found adding
8704 urlValue = 'http:/'+'/' + urlValue;
8707 if (par && this._isElement(par, 'a')) {
8708 par.setAttribute('href', urlValue);
8709 if (target.checked) {
8710 par.setAttribute('target', target.value);
8712 par.setAttribute('target', '');
8715 var _a = this._getDoc().createElement('a');
8716 _a.setAttribute('href', urlValue);
8717 if (target.checked) {
8718 _a.setAttribute('target', target.value);
8720 _a.setAttribute('target', '');
8722 el.parentNode.replaceChild(_a, el);
8726 if (par && this._isElement(par, 'a')) {
8727 par.parentNode.replaceChild(el, par);
8731 //No url/src given, remove the node from the document
8732 el.parentNode.removeChild(el);
8734 Dom.get(this.get('id') + '_insertimage_url').value = '';
8735 Dom.get(this.get('id') + '_insertimage_title').value = '';
8736 Dom.get(this.get('id') + '_insertimage_link').value = '';
8737 Dom.get(this.get('id') + '_insertimage_target').checked = false;
8738 Dom.get(this.get('id') + '_insertimage_width').value = 0;
8739 Dom.get(this.get('id') + '_insertimage_height').value = 0;
8740 this._defaultImageToolbar.resetAllButtons();
8741 this.currentElement = [];
8745 * @property EDITOR_PANEL_ID
8746 * @description HTML id to give the properties window in the DOM.
8749 EDITOR_PANEL_ID: '-panel',
8752 * @method _renderPanel
8753 * @description Renders the panel used for Editor Windows to the document so we can start using it..
8754 * @return {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>}
8756 _renderPanel: function() {
8757 var panelEl = document.createElement('div');
8758 Dom.addClass(panelEl, 'yui-editor-panel');
8759 panelEl.id = this.get('id') + this.EDITOR_PANEL_ID;
8760 panelEl.style.position = 'absolute';
8761 panelEl.style.top = '-9999px';
8762 panelEl.style.left = '-9999px';
8763 document.body.appendChild(panelEl);
8764 this.get('element_cont').insertBefore(panelEl, this.get('element_cont').get('firstChild'));
8768 var panel = new YAHOO.widget.Overlay(this.get('id') + this.EDITOR_PANEL_ID, {
8776 this.set('panel', panel);
8778 panel.setBody('---');
8779 panel.setHeader(' ');
8780 panel.setFooter(' ');
8783 var body = document.createElement('div');
8784 body.className = this.CLASS_PREFIX + '-body-cont';
8785 for (var b in this.browser) {
8786 if (this.browser[b]) {
8787 Dom.addClass(body, b);
8791 Dom.addClass(body, ((YAHOO.widget.Button && (this._defaultToolbar.buttonType == 'advanced')) ? 'good-button' : 'no-button'));
8793 var _note = document.createElement('h3');
8794 _note.className = 'yui-editor-skipheader';
8795 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
8796 body.appendChild(_note);
8797 var form = document.createElement('fieldset');
8798 panel.editor_form = form;
8800 body.appendChild(form);
8801 var _close = document.createElement('span');
8802 _close.innerHTML = 'X';
8803 _close.title = this.STR_CLOSE_WINDOW;
8804 _close.className = 'close';
8806 Event.on(_close, 'click', this.closeWindow, this, true);
8808 var _knob = document.createElement('span');
8809 _knob.innerHTML = '^';
8810 _knob.className = 'knob';
8811 panel.editor_knob = _knob;
8813 var _header = document.createElement('h3');
8814 panel.editor_header = _header;
8815 _header.innerHTML = '<span></span>';
8817 panel.setHeader(' '); //Clear the current header
8818 panel.appendToHeader(_header);
8819 _header.appendChild(_close);
8820 _header.appendChild(_knob);
8821 panel.setBody(' '); //Clear the current body
8822 panel.setFooter(' '); //Clear the current footer
8823 panel.appendToBody(body); //Append the new DOM node to it
8825 Event.on(panel.element, 'click', function(ev) {
8826 Event.stopPropagation(ev);
8829 var fireShowEvent = function() {
8831 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
8832 this._handleWindowInputs(false);
8834 panel.showEvent.subscribe(fireShowEvent, this, true);
8835 panel.hideEvent.subscribe(function() {
8836 this._handleWindowInputs(true);
8838 panel.renderEvent.subscribe(function() {
8839 this._renderInsertImageWindow();
8840 this._renderCreateLinkWindow();
8841 this.fireEvent('windowRender', { type: 'windowRender', panel: panel });
8842 this._handleWindowInputs(true);
8845 if (this.DOMReady) {
8846 this.get('panel').render();
8848 Event.onDOMReady(function() {
8849 this.get('panel').render();
8852 return this.get('panel');
8855 * @method _handleWindowInputs
8856 * @param {Boolean} disable The state to set all inputs in all Editor windows to. Defaults to: false.
8857 * @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.
8859 _handleWindowInputs: function(disable) {
8860 if (!Lang.isBoolean(disable)) {
8863 var inputs = this.get('panel').element.getElementsByTagName('input');
8864 for (var i = 0; i < inputs.length; i++) {
8866 inputs[i].disabled = disable;
8871 * @method openWindow
8872 * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance
8873 * @description Opens a new "window/panel"
8875 openWindow: function(win) {
8877 YAHOO.log('openWindow: ' + win.name, 'info', 'Editor');
8879 window.setTimeout(function() {
8880 self.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
8882 Event.on(document, 'keydown', this._closeWindow, this, true);
8884 if (this.currentWindow) {
8888 var xy = Dom.getXY(this.currentElement[0]),
8889 elXY = Dom.getXY(this.get('iframe').get('element')),
8890 panel = this.get('panel'),
8891 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8892 wWidth = (parseInt(win.attrs.width, 10) / 2),
8896 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
8898 var form = panel.editor_form;
8900 var wins = this._windows;
8901 for (var b in wins) {
8902 if (Lang.hasOwnProperty(wins, b)) {
8903 if (wins[b] && wins[b].body) {
8904 if (b == win.name) {
8905 Dom.setStyle(wins[b].body, 'display', 'block');
8907 Dom.setStyle(wins[b].body, 'display', 'none');
8913 if (this._windows[win.name].body) {
8914 Dom.setStyle(this._windows[win.name].body, 'display', 'block');
8915 form.appendChild(this._windows[win.name].body);
8917 if (Lang.isObject(win.body)) { //Assume it's a reference
8918 form.appendChild(win.body);
8919 } else { //Assume it's a string
8920 var _tmp = document.createElement('div');
8921 _tmp.innerHTML = win.body;
8922 form.appendChild(_tmp);
8925 panel.editor_header.firstChild.innerHTML = win.header;
8926 if (win.footer !== null) {
8927 panel.setFooter(win.footer);
8929 panel.cfg.setProperty('width', win.attrs.width);
8931 this.currentWindow = win;
8932 this.moveWindow(true);
8934 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
8937 * @method moveWindow
8938 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
8939 * @description Realign the window with the currentElement and reposition the knob above the panel.
8941 moveWindow: function(force) {
8942 if (!this.currentWindow) {
8945 var win = this.currentWindow,
8946 xy = Dom.getXY(this.currentElement[0]),
8947 elXY = Dom.getXY(this.get('iframe').get('element')),
8948 panel = this.get('panel'),
8949 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
8950 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
8951 wWidth = (parseInt(win.attrs.width, 10) / 2),
8953 orgXY = panel.cfg.getProperty('xy') || [0,0],
8954 _knob = panel.editor_knob,
8960 newXY[0] = ((newXY[0] - wWidth) + 20);
8961 //Account for the Scroll bars in a scrolled editor window.
8962 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
8963 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
8965 if (this._isElement(this.currentElement[0], 'img')) {
8966 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
8967 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
8968 newXY[1] = (newXY[1] + 75); //Placeholder sizea
8970 var w = parseInt(this.currentElement[0].width, 10);
8971 var h = parseInt(this.currentElement[0].height, 10);
8972 newXY[0] = (newXY[0] + (w / 2));
8973 newXY[1] = (newXY[1] + h);
8975 newXY[1] = newXY[1] + 15;
8977 var fs = Dom.getStyle(this.currentElement[0], 'fontSize');
8978 if (fs && fs.indexOf && fs.indexOf('px') != -1) {
8979 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize'), 10) + 5;
8981 newXY[1] = newXY[1] + 20;
8984 if (newXY[0] < elXY[0]) {
8985 newXY[0] = elXY[0] + 5;
8989 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10))) {
8990 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10)) - (wWidth * 2) - 5);
8995 xDiff = (newXY[0] - orgXY[0]);
8996 yDiff = (newXY[1] - orgXY[1]);
9000 var iTop = elXY[1] + parseInt(this.get('height'), 10);
9001 var iLeft = elXY[0] + parseInt(this.get('width'), 10);
9002 if (newXY[1] > iTop) {
9005 if (newXY[0] > iLeft) {
9006 newXY[0] = (iLeft / 2);
9009 //Convert negative numbers to positive so we can get the difference in distance
9010 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
9011 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
9013 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)
9017 if (this.currentElement[0].width) {
9018 elW = (parseInt(this.currentElement[0].width, 10) / 2);
9021 var leftOffset = xy[0] + elXY[0] + elW;
9022 _knobLeft = leftOffset - newXY[0];
9023 //Check to see if the knob will go off either side & reposition it
9024 if (_knobLeft > (parseInt(win.attrs.width, 10) - 1)) {
9025 _knobLeft = ((parseInt(win.attrs.width, 10) - 30) - 1);
9026 } else if (_knobLeft < 40) {
9029 if (isNaN(_knobLeft)) {
9034 _knob.style.left = _knobLeft + 'px';
9036 //Removed Animation from a forced move..
9037 panel.cfg.setProperty('xy', newXY);
9039 if (this.get('animate')) {
9040 anim = new YAHOO.util.Anim(panel.element, {}, 0.5, YAHOO.util.Easing.easeOut);
9049 anim.onComplete.subscribe(function() {
9050 panel.cfg.setProperty('xy', newXY);
9052 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
9053 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, 0.5, YAHOO.util.Easing.easeOut);
9055 var _knobAnim = new YAHOO.util.Anim(_knob, {
9059 }, 0.6, YAHOO.util.Easing.easeOut);
9061 iframeAnim.animate();
9062 _knobAnim.animate();
9064 _knob.style.left = _knobLeft + 'px';
9065 panel.cfg.setProperty('xy', newXY);
9072 * @method _closeWindow
9073 * @description Close the currently open EditorWindow with the Escape key.
9074 * @param {Event} ev The keypress Event that we are trapping
9076 _closeWindow: function(ev) {
9077 //if ((ev.charCode == 87) && ev.shiftKey && ev.ctrlKey) {
9078 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
9079 if (this.currentWindow) {
9085 * @method closeWindow
9086 * @description Close the currently open EditorWindow.
9088 closeWindow: function(keepOpen) {
9089 YAHOO.log('closeWindow: ' + this.currentWindow.name, 'info', 'Editor');
9090 this.fireEvent('window' + this.currentWindow.name + 'Close', { type: 'window' + this.currentWindow.name + 'Close', win: this.currentWindow, el: this.currentElement[0] });
9091 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
9092 this.currentWindow = null;
9093 this.get('panel').hide();
9094 this.get('panel').cfg.setProperty('xy', [-900,-900]);
9095 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
9096 this.unsubscribeAll('afterExecCommand');
9097 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
9098 this.toolbar.resetAllButtons();
9100 Event.removeListener(document, 'keydown', this._closeWindow);
9103 /* {{{ Command Overrides - These commands are only over written when we are using the advanced version */
9107 * @description Pulls an item from the Undo stack and updates the Editor
9108 * @param value Value passed from the execCommand method
9110 cmd_undo: function(value) {
9111 if (this._hasUndoLevel()) {
9112 var c_html = this.getEditorHTML(), html;
9113 if (!this._undoLevel) {
9114 this._undoLevel = this._undoCache.length;
9116 this._undoLevel = (this._undoLevel - 1);
9117 if (this._undoCache[this._undoLevel]) {
9118 html = this._getUndo(this._undoLevel);
9119 if (html != c_html) {
9120 this.setEditorHTML(html);
9122 this._undoLevel = (this._undoLevel - 1);
9123 html = this._getUndo(this._undoLevel);
9124 if (html != c_html) {
9125 this.setEditorHTML(html);
9129 this._undoLevel = 0;
9130 this.toolbar.disableButton('undo');
9138 * @description Pulls an item from the Undo stack and updates the Editor
9139 * @param value Value passed from the execCommand method
9141 cmd_redo: function(value) {
9142 this._undoLevel = this._undoLevel + 1;
9143 if (this._undoLevel >= this._undoCache.length) {
9144 this._undoLevel = this._undoCache.length;
9146 YAHOO.log(this._undoLevel + ' :: ' + this._undoCache.length, 'warn', 'SimpleEditor');
9147 if (this._undoCache[this._undoLevel]) {
9148 var html = this._getUndo(this._undoLevel);
9149 this.setEditorHTML(html);
9151 this.toolbar.disableButton('redo');
9157 * @method cmd_heading
9158 * @param value Value passed from the execCommand method
9159 * @description This is an execCommand override method. It is called from execCommand when the execCommand('heading') is used.
9161 cmd_heading: function(value) {
9165 _sel = this._getSelection(),
9166 _selEl = this._getSelectedElement();
9172 if (this.browser.ie) {
9173 action = 'formatblock';
9175 if (value == this.STR_NONE) {
9176 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'))) {
9177 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
9178 _sel = _sel.parentNode;
9180 if (this._isElement(_sel, 'html')) {
9183 el = this._swapEl(_selEl, 'span', function(el) {
9184 el.className = 'yui-non';
9186 this._selectNode(el);
9187 this.currentElement[0] = el;
9191 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')) {
9192 el = this._swapEl(_selEl, value);
9193 this._selectNode(el);
9194 this.currentElement[0] = el;
9196 this._createCurrentElement(value);
9197 this._selectNode(this.currentElement[0]);
9201 return [exec, action];
9204 * @method cmd_hiddenelements
9205 * @param value Value passed from the execCommand method
9206 * @description This is an execCommand override method. It is called from execCommand when the execCommand('hiddenelements') is used.
9208 cmd_hiddenelements: function(value) {
9209 if (this._showingHiddenElements) {
9210 //Don't auto highlight the hidden button
9211 this._lastButton = null;
9212 YAHOO.log('Enabling hidden CSS File', 'info', 'SimpleEditor');
9213 this._showingHiddenElements = false;
9214 this.toolbar.deselectButton('hiddenelements');
9215 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN);
9217 YAHOO.log('Disabling hidden CSS File', 'info', 'SimpleEditor');
9218 this._showingHiddenElements = true;
9219 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN);
9220 this.toolbar.selectButton('hiddenelements');
9225 * @method cmd_removeformat
9226 * @param value Value passed from the execCommand method
9227 * @description This is an execCommand override method. It is called from execCommand when the execCommand('removeformat') is used.
9229 cmd_removeformat: function(value) {
9232 * @knownissue Remove Format issue
9233 * @browser Safari 2.x
9234 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
9235 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
9236 * So here we are making the best possible guess and acting on it.
9238 if (this.browser.webkit && !this._getDoc().queryCommandEnabled('removeformat')) {
9239 var _txt = this._getSelection()+'';
9240 this._createCurrentElement('span');
9241 this.currentElement[0].className = 'yui-non';
9242 this.currentElement[0].innerHTML = _txt;
9243 for (var i = 1; i < this.currentElement.length; i++) {
9244 this.currentElement[i].parentNode.removeChild(this.currentElement[i]);
9252 * @method cmd_script
9253 * @param action action passed from the execCommand method
9254 * @param value Value passed from the execCommand method
9255 * @description This is a combined execCommand override method. It is called from the cmd_superscript and cmd_subscript methods.
9257 cmd_script: function(action, value) {
9258 var exec = true, tag = action.toLowerCase().substring(0, 3),
9259 _span = null, _selEl = this._getSelectedElement();
9261 if (this.browser.webkit) {
9262 YAHOO.log('Safari dom fun again (' + action + ')..', 'info', 'EditorSafari');
9263 if (this._isElement(_selEl, tag)) {
9264 YAHOO.log('we are a child of tag (' + tag + '), reverse process', 'info', 'EditorSafari');
9265 _span = this._swapEl(this.currentElement[0], 'span', function(el) {
9266 el.className = 'yui-non';
9268 this._selectNode(_span);
9270 this._createCurrentElement(tag);
9271 var _sub = this._swapEl(this.currentElement[0], tag);
9272 this._selectNode(_sub);
9273 this.currentElement[0] = _sub;
9280 * @method cmd_superscript
9281 * @param value Value passed from the execCommand method
9282 * @description This is an execCommand override method. It is called from execCommand when the execCommand('superscript') is used.
9284 cmd_superscript: function(value) {
9285 return [this.cmd_script('superscript', value)];
9288 * @method cmd_subscript
9289 * @param value Value passed from the execCommand method
9290 * @description This is an execCommand override method. It is called from execCommand when the execCommand('subscript') is used.
9292 cmd_subscript: function(value) {
9293 return [this.cmd_script('subscript', value)];
9296 * @method cmd_indent
9297 * @param value Value passed from the execCommand method
9298 * @description This is an execCommand override method. It is called from execCommand when the execCommand('indent') is used.
9300 cmd_indent: function(value) {
9301 var exec = true, selEl = this._getSelectedElement(), _bq = null;
9303 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9304 //if (this.browser.webkit || this.browser.ie) {
9305 if (this.browser.ie) {
9306 if (this._isElement(selEl, 'blockquote')) {
9307 _bq = this._getDoc().createElement('blockquote');
9308 _bq.innerHTML = selEl.innerHTML;
9309 selEl.innerHTML = '';
9310 selEl.appendChild(_bq);
9311 this._selectNode(_bq);
9313 _bq = this._getDoc().createElement('blockquote');
9314 var html = this._getRange().htmlText;
9315 _bq.innerHTML = html;
9316 this._createCurrentElement('blockquote');
9318 for (var i = 0; i < this.currentElement.length; i++) {
9319 _bq = this._getDoc().createElement('blockquote');
9320 _bq.innerHTML = this.currentElement[i].innerHTML;
9321 this.currentElement[i].parentNode.replaceChild(_bq, this.currentElement[i]);
9322 this.currentElement[i] = _bq;
9325 this.currentElement[0].parentNode.replaceChild(_bq, this.currentElement[0]);
9326 this.currentElement[0] = _bq;
9327 this._selectNode(this.currentElement[0]);
9331 value = 'blockquote';
9333 return [exec, 'formatblock', value];
9336 * @method cmd_outdent
9337 * @param value Value passed from the execCommand method
9338 * @description This is an execCommand override method. It is called from execCommand when the execCommand('outdent') is used.
9340 cmd_outdent: function(value) {
9341 var exec = true, selEl = this._getSelectedElement(), _bq = null, _span = null;
9342 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) {
9343 if (this.browser.webkit || this.browser.ie) {
9344 //if (this.browser.ie) {
9345 selEl = this._getSelectedElement();
9346 if (this._isElement(selEl, 'blockquote')) {
9347 var par = selEl.parentNode;
9348 if (this._isElement(selEl.parentNode, 'blockquote')) {
9349 par.innerHTML = selEl.innerHTML;
9350 this._selectNode(par);
9352 _span = this._getDoc().createElement('span');
9353 _span.innerHTML = selEl.innerHTML;
9354 YAHOO.util.Dom.addClass(_span, 'yui-non');
9355 par.replaceChild(_span, selEl);
9356 this._selectNode(_span);
9359 YAHOO.log('Can not outdent, we are not inside a blockquote', 'warn', 'Editor');
9365 return [exec, 'outdent', value];
9368 * @method cmd_justify
9369 * @param dir The direction to justify
9370 * @description This is a factory method for the justify family of commands.
9372 cmd_justify: function(dir) {
9373 if (this.browser.ie) {
9374 if (this._hasSelection()) {
9375 this._createCurrentElement('span');
9376 this._swapEl(this.currentElement[0], 'div', function(el) {
9377 el.style.textAlign = dir;
9383 return [true, 'justify' + dir, ''];
9386 * @method cmd_justifycenter
9387 * @param value Value passed from the execCommand method
9388 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifycenter') is used.
9390 cmd_justifycenter: function() {
9391 return [this.cmd_justify('center')];
9394 * @method cmd_justifyleft
9395 * @param value Value passed from the execCommand method
9396 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyleft') is used.
9398 cmd_justifyleft: function() {
9399 return [this.cmd_justify('left')];
9402 * @method cmd_justifyright
9403 * @param value Value passed from the execCommand method
9404 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyright') is used.
9406 cmd_justifyright: function() {
9407 return [this.cmd_justify('right')];
9412 * @description Returns a string representing the editor.
9415 toString: function() {
9417 if (this.get && this.get('element_cont')) {
9418 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
9424 * @event beforeOpenWindow
9425 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9426 * @param {Overlay} panel The Overlay object that is used to create the window.
9427 * @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.
9428 * @type YAHOO.util.CustomEvent
9431 * @event afterOpenWindow
9432 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9433 * @param {Overlay} panel The Overlay object that is used to create the window.
9434 * @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.
9435 * @type YAHOO.util.CustomEvent
9438 * @event closeWindow
9439 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9440 * @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.
9441 * @type YAHOO.util.CustomEvent
9444 * @event windowCMDOpen
9445 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9446 * @param {Overlay} panel The Overlay object that is used to create the window.
9447 * @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.
9448 * @type YAHOO.util.CustomEvent
9451 * @event windowCMDClose
9452 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9453 * @param {Overlay} panel The Overlay object that is used to create the window.
9454 * @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.
9455 * @type YAHOO.util.CustomEvent
9458 * @event windowRender
9459 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
9460 * @param {Overlay} panel The Overlay object that is used to create the window.
9461 * @description Event fired when the initial Overlay is rendered. Can be used to manipulate the content of the panel.
9462 * @type YAHOO.util.CustomEvent
9465 * @event windowInsertImageRender
9466 * @param {Overlay} panel The Overlay object that is used to create the window.
9467 * @param {HTMLElement} body The HTML element used as the body of the window..
9468 * @param {Toolbar} toolbar A reference to the toolbar object used inside this window.
9469 * @description Event fired when the pre render of the Insert Image window has finished.
9470 * @type YAHOO.util.CustomEvent
9473 * @event windowCreateLinkRender
9474 * @param {Overlay} panel The Overlay object that is used to create the window.
9475 * @param {HTMLElement} body The HTML element used as the body of the window..
9476 * @description Event fired when the pre render of the Create Link window has finished.
9477 * @type YAHOO.util.CustomEvent
9483 * @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.
9484 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
9485 * @class EditorWindow
9486 * @param {String} name The name of the window.
9487 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
9489 YAHOO.widget.EditorWindow = function(name, attrs) {
9493 * @description A unique name for the window
9495 this.name = name.replace(' ', '_');
9499 * @description The window attributes
9504 YAHOO.widget.EditorWindow.prototype = {
9508 * @description Holder for the header of the window, used in Editor.openWindow
9514 * @description Holder for the body of the window, used in Editor.openWindow
9520 * @description Holder for the footer of the window, used in Editor.openWindow
9525 * @description Sets the header for the window.
9526 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
9528 setHeader: function(str) {
9533 * @description Sets the body for the window.
9534 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
9536 setBody: function(str) {
9541 * @description Sets the footer for the window.
9542 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
9544 setFooter: function(str) {
9549 * @description Returns a string representing the EditorWindow.
9552 toString: function() {
9553 return 'Editor Window (' + this.name + ')';
9557 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.8.0r4", build: "2449"});