2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 var Dom = YAHOO.util.Dom,
9 Event = YAHOO.util.Event,
13 * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
14 * @class ToolbarButtonAdvanced
15 * @namespace YAHOO.widget
16 * @requires yahoo, dom, element, event, container_core, menu, button
18 * Provides a toolbar button based on the button and menu widgets.
20 * @class ToolbarButtonAdvanced
21 * @param {String/HTMLElement} el The element to turn into a button.
22 * @param {Object} attrs Object liternal containing configuration parameters.
24 if (YAHOO.widget.Button) {
25 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button;
27 * @property buttonType
29 * @description Tells if the Button is a Rich Button or a Simple Button
31 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich';
34 * @param {String} value The value of the option that we want to mark as selected
35 * @description Select an option by value
37 YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) {
38 var _menuItems = this.getMenu().getItems();
39 if (_menuItems.length === 0) {
40 this.getMenu()._onBeforeShow();
41 _menuItems = this.getMenu().getItems();
43 for (var i = 0; i < _menuItems.length; i++) {
44 _menuItems[i].cfg.setProperty('checked', false);
45 if (_menuItems[i].value == value) {
46 _menuItems[i].cfg.setProperty('checked', true);
51 YAHOO.widget.ToolbarButtonAdvanced = function() {};
56 * @description <p>Creates a basic custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p><p>Provides a toolbar button based on the button and menu widgets, <select> elements are used in place of menu's.</p>
57 * @class ToolbarButton
58 * @namespace YAHOO.widget
59 * @requires yahoo, dom, element, event
60 * @extends YAHOO.util.Element
64 * @param {String/HTMLElement} el The element to turn into a button.
65 * @param {Object} attrs Object liternal containing configuration parameters.
68 YAHOO.widget.ToolbarButton = function(el, attrs) {
70 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
73 var local_attrs = (attrs || {});
77 attributes: local_attrs
80 if (!oConfig.attributes.type) {
81 oConfig.attributes.type = 'push';
84 oConfig.element = document.createElement('span');
85 oConfig.element.setAttribute('unselectable', 'on');
86 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
87 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
88 oConfig.element.firstChild.firstChild.tabIndex = '-1';
89 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId());
90 oConfig.element.id = oConfig.attributes.id;
92 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
95 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
97 * @property buttonType
99 * @description Tells if the Button is a Rich Button or a Simple Button
101 buttonType: 'normal',
103 * @method _handleMouseOver
105 * @description Adds classes to the button elements on mouseover (hover)
107 _handleMouseOver: function() {
108 if (!this.get('disabled')) {
109 this.addClass('yui-button-hover');
110 this.addClass('yui-' + this.get('type') + '-button-hover');
114 * @method _handleMouseOut
116 * @description Removes classes from the button elements on mouseout (hover)
118 _handleMouseOut: function() {
119 this.removeClass('yui-button-hover');
120 this.removeClass('yui-' + this.get('type') + '-button-hover');
124 * @param {String} value The value of the option that we want to mark as selected
125 * @description Select an option by value
127 checkValue: function(value) {
128 if (this.get('type') == 'menu') {
129 var opts = this._button.options;
130 for (var i = 0; i < opts.length; i++) {
131 if (opts[i].value == value) {
132 opts.selectedIndex = i;
139 * @description The ToolbarButton class's initialization method
141 init: function(p_oElement, p_oAttributes) {
142 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
144 this.on('mouseover', this._handleMouseOver, this, true);
145 this.on('mouseout', this._handleMouseOut, this, true);
146 this.on('click', function(ev) {
152 * @method initAttributes
153 * @description Initializes all of the configuration attributes used to create
155 * @param {Object} attr Object literal specifying a set of
156 * configuration attributes used to create the toolbar.
158 initAttributes: function(attr) {
159 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
162 * @description The value of the button
165 this.setAttributeConfig('value', {
170 * @description The menu attribute, see YAHOO.widget.Button
173 this.setAttributeConfig('menu', {
174 value: attr.menu || false
178 * @description The type of button to create: push, menu, color, select, spin
181 this.setAttributeConfig('type', {
184 method: function(type) {
187 this._button = this.get('element').getElementsByTagName('a')[0];
192 el = document.createElement('select');
193 var menu = this.get('menu');
194 for (var i = 0; i < menu.length; i++) {
195 opt = document.createElement('option');
196 opt.innerHTML = menu[i].text;
197 opt.value = menu[i].value;
198 if (menu[i].checked) {
203 this._button.parentNode.replaceChild(el, this._button);
204 Event.on(el, 'change', this._handleSelect, this, true);
212 * @attribute disabled
213 * @description Set the button into a disabled state
216 this.setAttributeConfig('disabled', {
217 value: attr.disabled || false,
218 method: function(disabled) {
220 this.addClass('yui-button-disabled');
221 this.addClass('yui-' + this.get('type') + '-button-disabled');
223 this.removeClass('yui-button-disabled');
224 this.removeClass('yui-' + this.get('type') + '-button-disabled');
226 if (this.get('type') == 'menu') {
227 this._button.disabled = disabled;
234 * @description The text label for the button
237 this.setAttributeConfig('label', {
239 method: function(label) {
241 this._button = this.get('element').getElementsByTagName('a')[0];
243 if (this.get('type') == 'push') {
244 this._button.innerHTML = label;
251 * @description The title of the button
254 this.setAttributeConfig('title', {
260 * @description The container that the button is rendered to, handled by Toolbar
263 this.setAttributeConfig('container', {
266 method: function(cont) {
274 * @method _handleSelect
275 * @description The event fired when a change event gets fired on a select element
276 * @param {Event} ev The change event.
278 _handleSelect: function(ev) {
279 var tar = Event.getTarget(ev);
280 var value = tar.options[tar.selectedIndex].value;
281 this.fireEvent('change', {type: 'change', value: value });
285 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
287 getMenu: function() {
288 return this.get('menu');
292 * @description Destroy the button
294 destroy: function() {
295 Event.purgeElement(this.get('element'), true);
296 this.get('element').parentNode.removeChild(this.get('element'));
297 //Brutal Object Destroy
298 for (var i in this) {
299 if (Lang.hasOwnProperty(this, i)) {
306 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
308 fireEvent: function(p_sType, p_aArgs) {
309 // Disabled buttons should not respond to DOM events
310 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
311 Event.stopEvent(p_aArgs);
315 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
319 * @description Returns a string representing the toolbar.
322 toString: function() {
323 return 'ToolbarButton (' + this.get('id') + ')';
330 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
331 * @namespace YAHOO.widget
332 * @requires yahoo, dom, element, event, toolbarbutton
333 * @optional container_core, dragdrop
336 var Dom = YAHOO.util.Dom,
337 Event = YAHOO.util.Event,
340 var getButton = function(id) {
342 if (Lang.isString(id)) {
343 button = this.getButtonById(id);
345 if (Lang.isNumber(id)) {
346 button = this.getButtonByIndex(id);
348 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) {
349 button = this.getButtonByValue(id);
351 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) {
358 * Provides a rich toolbar widget based on the button and menu widgets
361 * @extends YAHOO.util.Element
362 * @param {String/HTMLElement} el The element to turn into a toolbar.
363 * @param {Object} attrs Object liternal containing configuration parameters.
365 YAHOO.widget.Toolbar = function(el, attrs) {
367 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
370 var local_attrs = {};
372 Lang.augmentObject(local_attrs, attrs); //Break the config reference
378 attributes: local_attrs
382 if (Lang.isString(el) && Dom.get(el)) {
383 oConfig.element = Dom.get(el);
384 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
385 oConfig.element = Dom.get(el);
389 if (!oConfig.element) {
390 oConfig.element = document.createElement('DIV');
391 oConfig.element.id = Dom.generateId();
393 if (local_attrs.container && Dom.get(local_attrs.container)) {
394 Dom.get(local_attrs.container).appendChild(oConfig.element);
399 if (!oConfig.element.id) {
400 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
403 var fs = document.createElement('fieldset');
404 var lg = document.createElement('legend');
405 lg.innerHTML = 'Toolbar';
408 var cont = document.createElement('DIV');
409 oConfig.attributes.cont = cont;
410 Dom.addClass(cont, 'yui-toolbar-subcont');
411 fs.appendChild(cont);
412 oConfig.element.appendChild(fs);
414 oConfig.element.tabIndex = -1;
417 oConfig.attributes.element = oConfig.element;
418 oConfig.attributes.id = oConfig.element.id;
420 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
424 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
426 * @method _addMenuClasses
428 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
429 * @param {String} ev The event that fired.
430 * @param {Array} na Array of event information.
431 * @param {Object} o Button config object.
433 _addMenuClasses: function(ev, na, o) {
434 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
435 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
436 Dom.addClass(this.element, 'yui-toolbar-select-menu');
438 var items = this.getItems();
439 for (var i = 0; i < items.length; i++) {
440 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()));
441 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
445 * @property buttonType
446 * @description The default button to use
449 buttonType: YAHOO.widget.ToolbarButton,
452 * @description The DragDrop instance associated with the Toolbar
457 * @property _colorData
458 * @description Object reference containing colors hex and text values.
463 '#111111': 'Obsidian',
464 '#2D2D2D': 'Dark Gray',
468 '#8B8B8B': 'Concrete',
470 '#B9B9B9': 'Titanium',
472 '#D0D0D0': 'Light Gray',
475 '#BFBF00': 'Pumpkin',
478 '#FFFF80': 'Pale Yellow',
480 '#525330': 'Raw Siena',
483 '#7F7F00': 'Paprika',
488 '#80FF00': 'Chartreuse',
490 '#C0FF80': 'Pale Lime',
491 '#DFFFBF': 'Light Mint',
493 '#668F5A': 'Lime Gray',
496 '#8A9B55': 'Pistachio',
497 '#B7C296': 'Light Jade',
498 '#E6EBD5': 'Breakwater',
499 '#00BF00': 'Spring Frost',
500 '#00FF80': 'Pastel Green',
501 '#40FFA0': 'Light Emerald',
502 '#80FFC0': 'Sea Foam',
503 '#BFFFDF': 'Sea Mist',
504 '#033D21': 'Dark Forrest',
506 '#7FA37C': 'Medium Green',
508 '#8DAE94': 'Yellow Gray Green',
509 '#ACC6B5': 'Aqua Lung',
510 '#DDEBE2': 'Sea Vapor',
513 '#40FFFF': 'Turquoise Blue',
514 '#80FFFF': 'Light Aqua',
515 '#BFFFFF': 'Pale Cyan',
516 '#033D3D': 'Dark Teal',
517 '#347D7E': 'Gray Turquoise',
518 '#609A9F': 'Green Blue',
519 '#007F7F': 'Seaweed',
520 '#96BDC4': 'Green Gray',
521 '#B5D1D7': 'Soapstone',
522 '#E2F1F4': 'Light Turquoise',
523 '#0060BF': 'Summer Sky',
524 '#0080FF': 'Sky Blue',
525 '#40A0FF': 'Electric Blue',
526 '#80C0FF': 'Light Azure',
527 '#BFDFFF': 'Ice Blue',
530 '#57708F': 'Dusty Blue',
531 '#00407F': 'Sea Blue',
532 '#7792AC': 'Sky Blue Gray',
533 '#A8BED1': 'Morning Sky',
535 '#0000BF': 'Deep Blue',
537 '#4040FF': 'Cerulean Blue',
538 '#8080FF': 'Evening Blue',
539 '#BFBFFF': 'Light Blue',
540 '#212143': 'Deep Indigo',
541 '#373E68': 'Sea Blue',
542 '#444F75': 'Night Blue',
543 '#00007F': 'Indigo Blue',
544 '#585E82': 'Dockside',
545 '#8687A4': 'Blue Gray',
546 '#D2D1E1': 'Light Blue Gray',
547 '#6000BF': 'Neon Violet',
548 '#8000FF': 'Blue Violet',
549 '#A040FF': 'Violet Purple',
550 '#C080FF': 'Violet Dusk',
551 '#DFBFFF': 'Pale Lavender',
552 '#302449': 'Cool Shale',
553 '#54466F': 'Dark Indigo',
554 '#655A7F': 'Dark Violet',
556 '#726284': 'Smoky Violet',
557 '#9E8FA9': 'Slate Gray',
558 '#DCD1DF': 'Violet White',
559 '#BF00BF': 'Royal Violet',
560 '#FF00FF': 'Fuchsia',
561 '#FF40FF': 'Magenta',
563 '#FFBFFF': 'Pale Magenta',
564 '#4A234A': 'Dark Purple',
565 '#794A72': 'Medium Purple',
566 '#936386': 'Cool Granite',
568 '#9D7292': 'Purple Moon',
569 '#C0A0B6': 'Pale Purple',
570 '#ECDAE5': 'Pink Cloud',
571 '#BF005F': 'Hot Pink',
572 '#FF007F': 'Deep Pink',
574 '#FF80BF': 'Electric Pink',
576 '#451528': 'Purple Red',
577 '#823857': 'Purple Dino',
578 '#A94A76': 'Purple Gray',
580 '#BC6F95': 'Antique Mauve',
581 '#D8A5BB': 'Cool Marble',
582 '#F7DDE9': 'Pink Granite',
584 '#FF0000': 'Fire Truck',
585 '#FF4040': 'Pale Red',
587 '#FFC0C0': 'Warm Pink',
591 '#800000': 'Brick Red',
593 '#D8A3A4': 'Shrimp Pink',
594 '#F8DDDD': 'Shell Pink',
595 '#BF5F00': 'Dark Orange',
597 '#FF9F40': 'Grapefruit',
598 '#FFBF80': 'Canteloupe',
600 '#482C1B': 'Dark Brick',
604 '#C49B71': 'Mustard',
605 '#E1C4A8': 'Pale Tan',
610 * @property _colorPicker
611 * @description The HTML Element containing the colorPicker
616 * @property STR_COLLAPSE
617 * @description String for Toolbar Collapse Button
620 STR_COLLAPSE: 'Collapse Toolbar',
622 * @property STR_SPIN_LABEL
623 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
626 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.',
628 * @property STR_SPIN_UP
629 * @description String for spinbutton up
632 STR_SPIN_UP: 'Click to increase the value of this input',
634 * @property STR_SPIN_DOWN
635 * @description String for spinbutton down
638 STR_SPIN_DOWN: 'Click to decrease the value of this input',
640 * @property _titlebar
641 * @description Object reference to the titlebar
647 * @description Standard browser detection
650 browser: YAHOO.env.ua,
653 * @property _buttonList
654 * @description Internal property list of current buttons in the toolbar
660 * @property _buttonGroupList
661 * @description Internal property list of current button groups in the toolbar
664 _buttonGroupList: null,
668 * @description Internal reference to the separator HTML Element for cloning
674 * @property _sepCount
675 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
681 * @property draghandle
687 * @property _toolbarConfigs
695 * @property CLASS_CONTAINER
696 * @description Default CSS class to apply to the toolbar container element
699 CLASS_CONTAINER: 'yui-toolbar-container',
702 * @property CLASS_DRAGHANDLE
703 * @description Default CSS class to apply to the toolbar's drag handle element
706 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
709 * @property CLASS_SEPARATOR
710 * @description Default CSS class to apply to all separators in the toolbar
713 CLASS_SEPARATOR: 'yui-toolbar-separator',
716 * @property CLASS_DISABLED
717 * @description Default CSS class to apply when the toolbar is disabled
720 CLASS_DISABLED: 'yui-toolbar-disabled',
723 * @property CLASS_PREFIX
724 * @description Default prefix for dynamically created class names
727 CLASS_PREFIX: 'yui-toolbar',
730 * @description The Toolbar class's initialization method
732 init: function(p_oElement, p_oAttributes) {
733 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
737 * @method initAttributes
738 * @description Initializes all of the configuration attributes used to create
740 * @param {Object} attr Object literal specifying a set of
741 * configuration attributes used to create the toolbar.
743 initAttributes: function(attr) {
744 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
745 this.addClass(this.CLASS_CONTAINER);
748 * @attribute buttonType
749 * @description The buttonType to use (advanced or basic)
752 this.setAttributeConfig('buttonType', {
753 value: attr.buttonType || 'basic',
755 validator: function(type) {
763 method: function(type) {
764 if (type == 'advanced') {
765 if (YAHOO.widget.Button) {
766 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced;
768 this.buttonType = YAHOO.widget.ToolbarButton;
771 this.buttonType = YAHOO.widget.ToolbarButton;
779 * @description Object specifying the buttons to include in the toolbar
783 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
784 * { type: 'separator' },
785 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
787 * { text: "Left", value: 'alignleft' },
788 * { text: "Center", value: 'aligncenter' },
789 * { text: "Right", value: 'alignright' }
797 this.setAttributeConfig('buttons', {
800 method: function(data) {
801 for (var i in data) {
802 if (Lang.hasOwnProperty(data, i)) {
803 if (data[i].type == 'separator') {
805 } else if (data[i].group !== undefined) {
806 this.addButtonGroup(data[i]);
808 this.addButton(data[i]);
816 * @attribute disabled
817 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
821 this.setAttributeConfig('disabled', {
823 method: function(disabled) {
824 if (this.get('disabled') === disabled) {
828 this.addClass(this.CLASS_DISABLED);
829 this.set('draggable', false);
830 this.disableAllButtons();
832 this.removeClass(this.CLASS_DISABLED);
833 if (this._configs.draggable._initialConfig.value) {
834 //Draggable by default, set it back
835 this.set('draggable', true);
837 this.resetAllButtons();
844 * @description The container for the toolbar.
847 this.setAttributeConfig('cont', {
854 * @attribute grouplabels
855 * @description Boolean indicating if the toolbar should show the group label's text string.
859 this.setAttributeConfig('grouplabels', {
860 value: ((attr.grouplabels === false) ? false : true),
861 method: function(grouplabels) {
863 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
865 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels'));
870 * @attribute titlebar
871 * @description Boolean indicating if the toolbar should have a titlebar. If
872 * passed a string, it will use that as the titlebar text
874 * @type Boolean or String
876 this.setAttributeConfig('titlebar', {
878 method: function(titlebar) {
880 if (this._titlebar && this._titlebar.parentNode) {
881 this._titlebar.parentNode.removeChild(this._titlebar);
883 this._titlebar = document.createElement('DIV');
884 this._titlebar.tabIndex = '-1';
885 Event.on(this._titlebar, 'focus', function() {
888 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
889 if (Lang.isString(titlebar)) {
890 var h2 = document.createElement('h2');
892 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>';
893 this._titlebar.appendChild(h2);
894 Event.on(h2.firstChild, 'click', function(ev) {
897 Event.on([h2, h2.firstChild], 'focus', function() {
901 if (this.get('firstChild')) {
902 this.insertBefore(this._titlebar, this.get('firstChild'));
904 this.appendChild(this._titlebar);
906 if (this.get('collapse')) {
907 this.set('collapse', true);
909 } else if (this._titlebar) {
910 if (this._titlebar && this._titlebar.parentNode) {
911 this._titlebar.parentNode.removeChild(this._titlebar);
919 * @attribute collapse
920 * @description Boolean indicating if the the titlebar should have a collapse button.
921 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
925 this.setAttributeConfig('collapse', {
927 method: function(collapse) {
928 if (this._titlebar) {
929 var collapseEl = null;
930 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
933 //There is already a collapse button
936 collapseEl = document.createElement('SPAN');
937 collapseEl.innerHTML = 'X';
938 collapseEl.title = this.STR_COLLAPSE;
940 Dom.addClass(collapseEl, 'collapse');
941 this._titlebar.appendChild(collapseEl);
942 Event.addListener(collapseEl, 'click', function() {
943 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
944 this.collapse(false); //Expand Toolbar
946 this.collapse(); //Collapse Toolbar
950 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
952 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) {
953 //We are closed, reopen the titlebar..
954 this.collapse(false); //Expand Toolbar
956 collapseEl[0].parentNode.removeChild(collapseEl[0]);
964 * @attribute draggable
965 * @description Boolean indicating if the toolbar should be draggable.
970 this.setAttributeConfig('draggable', {
971 value: (attr.draggable || false),
972 method: function(draggable) {
973 if (draggable && !this.get('titlebar')) {
974 if (!this._dragHandle) {
975 this._dragHandle = document.createElement('SPAN');
976 this._dragHandle.innerHTML = '|';
977 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
978 this._dragHandle.id = this.get('id') + '_draghandle';
979 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
980 if (this.get('cont').hasChildNodes()) {
981 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
983 this.get('cont').appendChild(this._dragHandle);
985 this.dd = new YAHOO.util.DD(this.get('id'));
986 this.dd.setHandleElId(this._dragHandle.id);
990 if (this._dragHandle) {
991 this._dragHandle.parentNode.removeChild(this._dragHandle);
992 this._dragHandle = null;
996 if (this._titlebar) {
998 this.dd = new YAHOO.util.DD(this.get('id'));
999 this.dd.setHandleElId(this._titlebar);
1000 Dom.addClass(this._titlebar, 'draggable');
1002 Dom.removeClass(this._titlebar, 'draggable');
1010 validator: function(value) {
1012 if (!YAHOO.util.DD) {
1021 * @method addButtonGroup
1022 * @description Add a new button group to the toolbar. (uses addButton)
1023 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1025 addButtonGroup: function(oGroup) {
1026 if (!this.get('element')) {
1027 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1031 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1032 this.addClass(this.CLASS_PREFIX + '-grouped');
1034 var div = document.createElement('DIV');
1035 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1036 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1038 var label = document.createElement('h3');
1039 label.innerHTML = oGroup.label;
1040 div.appendChild(label);
1042 if (!this.get('grouplabels')) {
1043 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1046 this.get('cont').appendChild(div);
1048 //For accessibility, let's put all of the group buttons in an Unordered List
1049 var ul = document.createElement('ul');
1050 div.appendChild(ul);
1052 if (!this._buttonGroupList) {
1053 this._buttonGroupList = {};
1056 this._buttonGroupList[oGroup.group] = ul;
1058 for (var i = 0; i < oGroup.buttons.length; i++) {
1059 var li = document.createElement('li');
1060 li.className = this.CLASS_PREFIX + '-groupitem';
1062 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1063 this.addSeparator(li);
1065 oGroup.buttons[i].container = li;
1066 this.addButton(oGroup.buttons[i]);
1071 * @method addButtonToGroup
1072 * @description Add a new button to a toolbar group. Buttons supported:
1073 * push, split, menu, select, color, spin
1074 * @param {Object} oButton Object literal reference to the Button's Config
1075 * @param {String} group The Group identifier passed into the initial config
1076 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1078 addButtonToGroup: function(oButton, group, after) {
1079 var groupCont = this._buttonGroupList[group];
1080 var li = document.createElement('li');
1081 li.className = this.CLASS_PREFIX + '-groupitem';
1082 oButton.container = li;
1083 this.addButton(oButton, after);
1084 groupCont.appendChild(li);
1088 * @description Add a new button to the toolbar. Buttons supported:
1089 * push, split, menu, select, color, spin
1090 * @param {Object} oButton Object literal reference to the Button's Config
1091 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1093 addButton: function(oButton, after) {
1094 if (!this.get('element')) {
1095 this._queue[this._queue.length] = ['addButton', arguments];
1098 if (!this._buttonList) {
1099 this._buttonList = [];
1101 if (!oButton.container) {
1102 oButton.container = this.get('cont');
1105 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1106 if (Lang.isArray(oButton.menu)) {
1107 for (var i in oButton.menu) {
1108 if (Lang.hasOwnProperty(oButton.menu, i)) {
1110 fn: function(ev, x, oMenu) {
1111 if (!oButton.menucmd) {
1112 oButton.menucmd = oButton.value;
1114 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1118 oButton.menu[i].onclick = funcObject;
1123 var _oButton = {}, skip = false;
1124 for (var o in oButton) {
1125 if (Lang.hasOwnProperty(oButton, o)) {
1126 if (!this._toolbarConfigs[o]) {
1127 _oButton[o] = oButton[o];
1131 if (oButton.type == 'select') {
1132 _oButton.type = 'menu';
1134 if (oButton.type == 'spin') {
1135 _oButton.type = 'push';
1137 if (_oButton.type == 'color') {
1138 if (YAHOO.widget.Overlay) {
1139 _oButton = this._makeColorButton(_oButton);
1144 if (_oButton.menu) {
1145 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1146 oButton.menu.showEvent.subscribe(function() {
1147 this._button = _oButton;
1150 for (var m = 0; m < _oButton.menu.length; m++) {
1151 if (!_oButton.menu[m].value) {
1152 _oButton.menu[m].value = _oButton.menu[m].text;
1155 if (this.browser.webkit) {
1156 _oButton.focusmenu = false;
1163 //Add to .get('buttons') manually
1164 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1166 var tmp = new this.buttonType(_oButton);
1167 tmp.get('element').tabIndex = '-1';
1168 tmp.get('element').setAttribute('role', 'button');
1169 tmp._selected = true;
1171 if (this.get('disabled')) {
1172 //Toolbar is disabled, disable the new button too!
1173 tmp.set('disabled', true);
1176 oButton.id = tmp.get('id');
1180 var el = tmp.get('element');
1183 nextSib = after.get('element').nextSibling;
1184 } else if (after.nextSibling) {
1185 nextSib = after.nextSibling;
1188 nextSib.parentNode.insertBefore(el, nextSib);
1191 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1193 var icon = document.createElement('span');
1194 icon.className = this.CLASS_PREFIX + '-icon';
1195 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1196 if (tmp._button.tagName.toLowerCase() == 'button') {
1197 tmp.get('element').setAttribute('unselectable', 'on');
1198 //Replace the Button HTML Element with an a href if it exists
1199 var a = document.createElement('a');
1200 a.innerHTML = tmp._button.innerHTML;
1203 Event.on(a, 'click', function(ev) {
1204 Event.stopEvent(ev);
1206 tmp._button.parentNode.replaceChild(a, tmp._button);
1210 if (oButton.type == 'select') {
1211 if (tmp._button.tagName.toLowerCase() == 'select') {
1212 icon.parentNode.removeChild(icon);
1213 var iel = tmp._button;
1214 var parEl = tmp.get('element');
1215 parEl.parentNode.replaceChild(iel, parEl);
1217 //Don't put a class on it if it's a real select element
1218 tmp.addClass(this.CLASS_PREFIX + '-select');
1221 if (oButton.type == 'spin') {
1222 if (!Lang.isArray(oButton.range)) {
1223 oButton.range = [ 10, 100 ];
1225 this._makeSpinButton(tmp, oButton);
1227 tmp.get('element').setAttribute('title', tmp.get('label'));
1228 if (oButton.type != 'spin') {
1229 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1230 var showPicker = function(ev) {
1232 if (ev.keyCode && (ev.keyCode == 9)) {
1236 if (this._colorPicker) {
1237 this._colorPicker._button = oButton.value;
1239 var menuEL = tmp.getMenu().element;
1240 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1241 tmp.getMenu().show();
1243 tmp.getMenu().hide();
1246 YAHOO.util.Event.stopEvent(ev);
1248 tmp.on('mousedown', showPicker, oButton, this);
1249 tmp.on('keydown', showPicker, oButton, this);
1251 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1252 tmp.on('keypress', this._buttonClick, oButton, this);
1253 tmp.on('mousedown', function(ev) {
1254 YAHOO.util.Event.stopEvent(ev);
1255 this._buttonClick(ev, oButton);
1257 tmp.on('click', function(ev) {
1258 YAHOO.util.Event.stopEvent(ev);
1261 //Stop the mousedown event so we can trap the selection in the editor!
1262 tmp.on('mousedown', function(ev) {
1263 YAHOO.util.Event.stopEvent(ev);
1265 tmp.on('click', function(ev) {
1266 YAHOO.util.Event.stopEvent(ev);
1268 tmp.on('change', function(ev) {
1269 if (!oButton.menucmd) {
1270 oButton.menucmd = oButton.value;
1272 oButton.value = ev.value;
1273 this._buttonClick(ev, oButton);
1277 //Hijack the mousedown event in the menu and make it fire a button click..
1278 tmp.on('appendTo', function() {
1280 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1281 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1282 var oMenu = args[1];
1283 YAHOO.util.Event.stopEvent(args[0]);
1284 tmp._onMenuClick(args[0], tmp);
1285 if (!oButton.menucmd) {
1286 oButton.menucmd = oButton.value;
1288 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1289 self._buttonClick.call(self, args[1], oButton);
1293 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1294 YAHOO.util.Event.stopEvent(args[0]);
1296 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1297 YAHOO.util.Event.stopEvent(args[0]);
1304 //Stop the mousedown event so we can trap the selection in the editor!
1305 tmp.on('mousedown', function(ev) {
1306 YAHOO.util.Event.stopEvent(ev);
1308 tmp.on('click', function(ev) {
1309 YAHOO.util.Event.stopEvent(ev);
1312 if (this.browser.ie) {
1314 //Add a couple of new events for IE
1315 tmp.DOM_EVENTS.focusin = true;
1316 tmp.DOM_EVENTS.focusout = true;
1318 //Stop them so we don't loose focus in the Editor
1319 tmp.on('focusin', function(ev) {
1320 YAHOO.util.Event.stopEvent(ev);
1323 tmp.on('focusout', function(ev) {
1324 YAHOO.util.Event.stopEvent(ev);
1326 tmp.on('click', function(ev) {
1327 YAHOO.util.Event.stopEvent(ev);
1331 if (this.browser.webkit) {
1332 //This will keep the document from gaining focus and the editor from loosing it..
1333 //Forcefully remove the focus calls in button!
1334 tmp.hasFocus = function() {
1338 this._buttonList[this._buttonList.length] = tmp;
1339 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1340 if (Lang.isArray(oButton.menu)) {
1341 var menu = tmp.getMenu();
1342 if (menu && menu.renderEvent) {
1343 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1344 if (oButton.renderer) {
1345 menu.renderEvent.subscribe(oButton.renderer, tmp);
1354 * @method addSeparator
1355 * @description Add a new button separator to the toolbar.
1356 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1357 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1359 addSeparator: function(cont, after) {
1360 if (!this.get('element')) {
1361 this._queue[this._queue.length] = ['addSeparator', arguments];
1364 var sepCont = ((cont) ? cont : this.get('cont'));
1365 if (!this.get('element')) {
1366 this._queue[this._queue.length] = ['addSeparator', arguments];
1369 if (this._sepCount === null) {
1373 this._sep = document.createElement('SPAN');
1374 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1375 this._sep.innerHTML = '|';
1377 var _sep = this._sep.cloneNode(true);
1379 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1383 nextSib = after.get('element').nextSibling;
1384 } else if (after.nextSibling) {
1385 nextSib = after.nextSibling;
1390 if (nextSib == after) {
1391 nextSib.parentNode.appendChild(_sep);
1393 nextSib.parentNode.insertBefore(_sep, nextSib);
1397 sepCont.appendChild(_sep);
1402 * @method _createColorPicker
1404 * @description Creates the core DOM reference to the color picker menu item.
1405 * @param {String} id the id of the toolbar to prefix this DOM container with.
1407 _createColorPicker: function(id) {
1408 if (Dom.get(id + '_colors')) {
1409 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1411 var picker = document.createElement('div');
1412 picker.className = 'yui-toolbar-colors';
1413 picker.id = id + '_colors';
1414 picker.style.display = 'none';
1415 Event.on(window, 'load', function() {
1416 document.body.appendChild(picker);
1419 this._colorPicker = picker;
1422 for (var i in this._colorData) {
1423 if (Lang.hasOwnProperty(this._colorData, i)) {
1424 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1427 html += '<span><em>X</em><strong></strong></span>';
1428 window.setTimeout(function() {
1429 picker.innerHTML = html;
1432 Event.on(picker, 'mouseover', function(ev) {
1433 var picker = this._colorPicker;
1434 var em = picker.getElementsByTagName('em')[0];
1435 var strong = picker.getElementsByTagName('strong')[0];
1436 var tar = Event.getTarget(ev);
1437 if (tar.tagName.toLowerCase() == 'a') {
1438 em.style.backgroundColor = tar.style.backgroundColor;
1439 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1442 Event.on(picker, 'focus', function(ev) {
1443 Event.stopEvent(ev);
1445 Event.on(picker, 'click', function(ev) {
1446 Event.stopEvent(ev);
1448 Event.on(picker, 'mousedown', function(ev) {
1449 Event.stopEvent(ev);
1450 var tar = Event.getTarget(ev);
1451 if (tar.tagName.toLowerCase() == 'a') {
1452 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1453 if (retVal !== false) {
1455 color: tar.innerHTML,
1456 colorName: this._colorData['#' + tar.innerHTML],
1457 value: this._colorPicker._button
1460 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1462 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1467 * @method _resetColorPicker
1469 * @description Clears the currently selected color or mouseover color in the color picker.
1471 _resetColorPicker: function() {
1472 var em = this._colorPicker.getElementsByTagName('em')[0];
1473 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1474 em.style.backgroundColor = 'transparent';
1475 strong.innerHTML = '';
1478 * @method _makeColorButton
1480 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1481 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1483 _makeColorButton: function(_oButton) {
1484 if (!this._colorPicker) {
1485 this._createColorPicker(this.get('id'));
1487 _oButton.type = 'color';
1488 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1489 _oButton.menu.setBody('');
1490 _oButton.menu.render(this.get('cont'));
1491 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1492 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1493 _oButton.menu.beforeShowEvent.subscribe(function() {
1494 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1495 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1496 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1497 this._resetColorPicker();
1498 var _p = this._colorPicker;
1499 if (_p.parentNode) {
1500 _p.parentNode.removeChild(_p);
1502 _oButton.menu.setBody('');
1503 _oButton.menu.appendToBody(_p);
1504 this._colorPicker.style.display = 'block';
1510 * @method _makeSpinButton
1511 * @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.
1512 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1513 * @param {Object} oButton Object literal containing the buttons initial config
1515 _makeSpinButton: function(_button, oButton) {
1516 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1518 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1519 range = oButton.range,
1520 _b1 = document.createElement('a'),
1521 _b2 = document.createElement('a');
1524 _b1.tabIndex = '-1';
1525 _b2.tabIndex = '-1';
1527 //Setup the up and down arrows
1528 _b1.className = 'up';
1529 _b1.title = this.STR_SPIN_UP;
1530 _b1.innerHTML = this.STR_SPIN_UP;
1531 _b2.className = 'down';
1532 _b2.title = this.STR_SPIN_DOWN;
1533 _b2.innerHTML = this.STR_SPIN_DOWN;
1535 //Append them to the container
1536 _par.appendChild(_b1);
1537 _par.appendChild(_b2);
1539 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1540 _button.set('title', label);
1542 var cleanVal = function(value) {
1543 value = ((value < range[0]) ? range[0] : value);
1544 value = ((value > range[1]) ? range[1] : value);
1548 var br = this.browser;
1550 var strLabel = this.STR_SPIN_LABEL;
1551 if (this._titlebar && this._titlebar.firstChild) {
1552 tbar = this._titlebar.firstChild;
1555 var _intUp = function(ev) {
1556 YAHOO.util.Event.stopEvent(ev);
1557 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1558 var value = parseInt(_button.get('label'), 10);
1560 value = cleanVal(value);
1561 _button.set('label', ''+value);
1562 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1563 _button.set('title', label);
1564 if (!br.webkit && tbar) {
1565 //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
1568 self._buttonClick(ev, oButton);
1572 var _intDown = function(ev) {
1573 YAHOO.util.Event.stopEvent(ev);
1574 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1575 var value = parseInt(_button.get('label'), 10);
1577 value = cleanVal(value);
1579 _button.set('label', ''+value);
1580 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1581 _button.set('title', label);
1582 if (!br.webkit && tbar) {
1583 //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
1586 self._buttonClick(ev, oButton);
1590 var _intKeyUp = function(ev) {
1591 if (ev.keyCode == 38) {
1593 } else if (ev.keyCode == 40) {
1595 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1597 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1602 //Handle arrow keys..
1603 _button.on('keydown', _intKeyUp, this, true);
1605 //Listen for the click on the up button and act on it
1606 //Listen for the click on the down button and act on it
1607 Event.on(_b1, 'mousedown',function(ev) {
1608 Event.stopEvent(ev);
1610 Event.on(_b2, 'mousedown', function(ev) {
1611 Event.stopEvent(ev);
1613 Event.on(_b1, 'click', _intUp, this, true);
1614 Event.on(_b2, 'click', _intDown, this, true);
1618 * @method _buttonClick
1619 * @description Click handler for all buttons in the toolbar.
1620 * @param {String} ev The event that was passed in.
1621 * @param {Object} info Object literal of information about the button that was clicked.
1623 _buttonClick: function(ev, info) {
1626 if (ev && ev.type == 'keypress') {
1627 if (ev.keyCode == 9) {
1629 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1636 var fireNextEvent = true,
1639 info.isSelected = this.isSelected(info.id);
1642 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1643 if (retValue === false) {
1644 fireNextEvent = false;
1648 if (info.menucmd && fireNextEvent) {
1649 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1650 if (retValue === false) {
1651 fireNextEvent = false;
1654 if (fireNextEvent) {
1655 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1658 if (info.type == 'select') {
1659 var button = this.getButtonById(info.id);
1660 if (button.buttonType == 'rich') {
1661 var txt = info.value;
1662 for (var i = 0; i < info.menu.length; i++) {
1663 if (info.menu[i].value == info.value) {
1664 txt = info.menu[i].text;
1668 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1669 var _items = button.getMenu().getItems();
1670 for (var m = 0; m < _items.length; m++) {
1671 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1672 _items[m].cfg.setProperty('checked', true);
1674 _items[m].cfg.setProperty('checked', false);
1680 Event.stopEvent(ev);
1687 * @description Flag to determine if the arrow nav listeners have been attached
1693 * @property _navCounter
1694 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1700 * @method _navigateButtons
1701 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1702 * @param {Event} ev The Key Event
1704 _navigateButtons: function(ev) {
1705 switch (ev.keyCode) {
1708 if (ev.keyCode == 37) {
1713 if (this._navCounter > (this._buttonList.length - 1)) {
1714 this._navCounter = 0;
1716 if (this._navCounter < 0) {
1717 this._navCounter = (this._buttonList.length - 1);
1719 if (this._buttonList[this._navCounter]) {
1720 var el = this._buttonList[this._navCounter].get('element');
1721 if (this.browser.ie) {
1722 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1724 if (this._buttonList[this._navCounter].get('disabled')) {
1725 this._navigateButtons(ev);
1735 * @method _handleFocus
1736 * @description Sets up the listeners for the arrow key navigation
1738 _handleFocus: function() {
1739 if (!this._keyNav) {
1740 var ev = 'keypress';
1741 if (this.browser.ie) {
1744 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1745 this._keyNav = true;
1746 this._navCounter = -1;
1750 * @method getButtonById
1751 * @description Gets a button instance from the toolbar by is Dom id.
1752 * @param {String} id The Dom id to query for.
1753 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1755 getButtonById: function(id) {
1756 var len = this._buttonList.length;
1757 for (var i = 0; i < len; i++) {
1758 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1759 return this._buttonList[i];
1765 * @method getButtonByValue
1766 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1767 * @param {String} value The button value to query for.
1768 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1770 getButtonByValue: function(value) {
1771 var _buttons = this.get('buttons');
1772 var len = _buttons.length;
1773 for (var i = 0; i < len; i++) {
1774 if (_buttons[i].group !== undefined) {
1775 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1776 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1777 return this.getButtonById(_buttons[i].buttons[m].id);
1779 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1780 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1781 if (_buttons[i].buttons[m].menu[s].value == value) {
1782 return this.getButtonById(_buttons[i].buttons[m].id);
1788 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1789 return this.getButtonById(_buttons[i].id);
1791 if (_buttons[i].menu) { //Menu Button, loop through the values
1792 for (var j = 0; j < _buttons[i].menu.length; j++) {
1793 if (_buttons[i].menu[j].value == value) {
1794 return this.getButtonById(_buttons[i].id);
1803 * @method getButtonByIndex
1804 * @description Gets a button instance from the toolbar by is index in _buttonList.
1805 * @param {Number} index The index of the button in _buttonList.
1806 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1808 getButtonByIndex: function(index) {
1809 if (this._buttonList[index]) {
1810 return this._buttonList[index];
1816 * @method getButtons
1817 * @description Returns an array of buttons in the current toolbar
1820 getButtons: function() {
1821 return this._buttonList;
1824 * @method disableButton
1825 * @description Disables a button in the toolbar.
1826 * @param {String/Number} id Disable a button by it's id, index or value.
1829 disableButton: function(id) {
1830 var button = getButton.call(this, id);
1832 button.set('disabled', true);
1838 * @method enableButton
1839 * @description Enables a button in the toolbar.
1840 * @param {String/Number} id Enable a button by it's id, index or value.
1843 enableButton: function(id) {
1844 if (this.get('disabled')) {
1847 var button = getButton.call(this, id);
1849 if (button.get('disabled')) {
1850 button.set('disabled', false);
1857 * @method isSelected
1858 * @description Tells if a button is selected or not.
1859 * @param {String/Number} id A button by it's id, index or value.
1862 isSelected: function(id) {
1863 var button = getButton.call(this, id);
1865 return button._selected;
1870 * @method selectButton
1871 * @description Selects a button in the toolbar.
1872 * @param {String/Number} id Select a button by it's id, index or value.
1873 * @param {String} value If this is a Menu Button, check this item in the menu
1876 selectButton: function(id, value) {
1877 var button = getButton.call(this, id);
1879 button.addClass('yui-button-selected');
1880 button.addClass('yui-button-' + button.get('value') + '-selected');
1881 button._selected = true;
1883 if (button.buttonType == 'rich') {
1884 var _items = button.getMenu().getItems();
1885 for (var m = 0; m < _items.length; m++) {
1886 if (_items[m].value == value) {
1887 _items[m].cfg.setProperty('checked', true);
1888 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1890 _items[m].cfg.setProperty('checked', false);
1900 * @method deselectButton
1901 * @description Deselects a button in the toolbar.
1902 * @param {String/Number} id Deselect a button by it's id, index or value.
1905 deselectButton: function(id) {
1906 var button = getButton.call(this, id);
1908 button.removeClass('yui-button-selected');
1909 button.removeClass('yui-button-' + button.get('value') + '-selected');
1910 button.removeClass('yui-button-hover');
1911 button._selected = false;
1917 * @method deselectAllButtons
1918 * @description Deselects all buttons in the toolbar.
1921 deselectAllButtons: function() {
1922 var len = this._buttonList.length;
1923 for (var i = 0; i < len; i++) {
1924 this.deselectButton(this._buttonList[i]);
1928 * @method disableAllButtons
1929 * @description Disables all buttons in the toolbar.
1932 disableAllButtons: function() {
1933 if (this.get('disabled')) {
1936 var len = this._buttonList.length;
1937 for (var i = 0; i < len; i++) {
1938 this.disableButton(this._buttonList[i]);
1942 * @method enableAllButtons
1943 * @description Enables all buttons in the toolbar.
1946 enableAllButtons: function() {
1947 if (this.get('disabled')) {
1950 var len = this._buttonList.length;
1951 for (var i = 0; i < len; i++) {
1952 this.enableButton(this._buttonList[i]);
1956 * @method resetAllButtons
1957 * @description Resets all buttons to their initial state.
1958 * @param {Object} _ex Except these buttons
1961 resetAllButtons: function(_ex) {
1962 if (!Lang.isObject(_ex)) {
1965 if (this.get('disabled')) {
1968 var len = this._buttonList.length;
1969 for (var i = 0; i < len; i++) {
1970 var _button = this._buttonList[i];
1972 var disabled = _button._configs.disabled._initialConfig.value;
1973 if (_ex[_button.get('id')]) {
1974 this.enableButton(_button);
1975 this.selectButton(_button);
1978 this.disableButton(_button);
1980 this.enableButton(_button);
1982 this.deselectButton(_button);
1988 * @method destroyButton
1989 * @description Destroy a button in the toolbar.
1990 * @param {String/Number} id Destroy a button by it's id or index.
1993 destroyButton: function(id) {
1994 var button = getButton.call(this, id);
1996 var thisID = button.get('id');
1999 var len = this._buttonList.length;
2000 for (var i = 0; i < len; i++) {
2001 if (this._buttonList[i] && this._buttonList[i].get('id') == thisID) {
2002 this._buttonList[i] = null;
2011 * @description Destroys the toolbar, all of it's elements and objects.
2014 destroy: function() {
2015 this.get('element').innerHTML = '';
2016 this.get('element').className = '';
2017 //Brutal Object Destroy
2018 for (var i in this) {
2019 if (Lang.hasOwnProperty(this, i)) {
2027 * @description Programatically collapse the toolbar.
2028 * @param {Boolean} collapse True to collapse, false to expand.
2030 collapse: function(collapse) {
2031 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2032 if (collapse === false) {
2033 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2035 Dom.removeClass(el[0], 'collapsed');
2037 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2040 Dom.addClass(el[0], 'collapsed');
2042 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2043 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2048 * @description Returns a string representing the toolbar.
2051 toString: function() {
2052 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2056 * @event buttonClick
2057 * @param {Object} o The object passed to this handler is the button config used to create the button.
2058 * @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.
2059 * @type YAHOO.util.CustomEvent
2063 * @param {Object} o The object passed to this handler is the button config used to create the button.
2064 * @description This is a special dynamic event that is created and dispatched based on the value property
2065 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2069 * { type: 'button', value: 'test', value: 'testButton' }
2072 * With the valueClick event you could subscribe to this buttons click event with this:
2073 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2074 * @type YAHOO.util.CustomEvent
2077 * @event toolbarExpanded
2078 * @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.
2079 * @type YAHOO.util.CustomEvent
2082 * @event toolbarCollapsed
2083 * @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.
2084 * @type YAHOO.util.CustomEvent
2089 * @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>
2090 * @namespace YAHOO.widget
2091 * @requires yahoo, dom, element, event, toolbar
2092 * @optional animation, container_core, resize, dragdrop
2096 var Dom = YAHOO.util.Dom,
2097 Event = YAHOO.util.Event,
2099 Toolbar = YAHOO.widget.Toolbar;
2102 * 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.
2104 * @class SimpleEditor
2105 * @extends YAHOO.util.Element
2106 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2107 * @param {Object} attrs Object liternal containing configuration parameters.
2110 YAHOO.widget.SimpleEditor = function(el, attrs) {
2113 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2114 Lang.augmentObject(o, el); //Break the config reference
2115 el = document.createElement('textarea');
2116 this.DOMReady = true;
2118 var c = Dom.get(o.container);
2121 document.body.appendChild(el);
2125 Lang.augmentObject(o, attrs); //Break the config reference
2134 if (Lang.isString(el)) {
2137 if (oConfig.attributes.id) {
2138 id = oConfig.attributes.id;
2140 this.DOMReady = true;
2141 id = Dom.generateId(el);
2144 oConfig.element = el;
2146 var element_cont = document.createElement('DIV');
2147 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2148 id: id + '_container'
2150 var div = document.createElement('div');
2151 Dom.addClass(div, 'first-child');
2152 oConfig.attributes.element_cont.appendChild(div);
2154 if (!oConfig.attributes.toolbar_cont) {
2155 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2156 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2157 div.appendChild(oConfig.attributes.toolbar_cont);
2159 var editorWrapper = document.createElement('DIV');
2160 div.appendChild(editorWrapper);
2161 oConfig.attributes.editor_wrapper = editorWrapper;
2163 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2167 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2170 * @property _resizeConfig
2171 * @description The default config for the Resize Utility
2183 * @method _setupResize
2184 * @description Creates the Resize instance and binds its events.
2186 _setupResize: function() {
2187 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2188 if (this.get('resize')) {
2190 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2191 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2192 this.resize.on('resize', function(args) {
2193 var anim = this.get('animate');
2194 this.set('animate', false);
2195 this.set('width', args.width + 'px');
2196 var h = args.height,
2197 th = (this.toolbar.get('element').clientHeight + 2),
2200 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2202 var newH = (h - th - dh);
2203 this.set('height', newH + 'px');
2204 this.get('element_cont').setStyle('height', '');
2205 this.set('animate', anim);
2211 * @description A reference to the Resize object
2212 * @type YAHOO.util.Resize
2218 * @description Sets up the DD instance used from the 'drag' config option.
2220 _setupDD: function() {
2221 if (!YAHOO.util.DD) { return false; }
2222 if (this.get('drag')) {
2223 var d = this.get('drag'),
2225 if (d === 'proxy') {
2226 dd = YAHOO.util.DDProxy;
2229 this.dd = new dd(this.get('element_cont').get('element'));
2230 this.toolbar.addClass('draggable');
2231 this.dd.setHandleElId(this.toolbar._titlebar);
2236 * @description A reference to the DragDrop object.
2237 * @type YAHOO.util.DD/YAHOO.util.DDProxy
2242 * @property _lastCommand
2243 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level)
2247 _undoNodeChange: function() {},
2248 _storeUndo: function() {},
2252 * @description Checks a keyMap entry against a key event
2253 * @param {Object} k The _keyMap object
2254 * @param {Event} e The Mouse Event
2257 _checkKey: function(k, e) {
2259 if ((e.keyCode === k.key)) {
2260 if (k.mods && (k.mods.length > 0)) {
2262 for (var i = 0; i < k.mods.length; i++) {
2263 if (this.browser.mac) {
2264 if (k.mods[i] == 'ctrl') {
2268 if (e[k.mods[i] + 'Key'] === true) {
2272 if (val === k.mods.length) {
2284 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>.
2285 * 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.
2286 * @type {Object/Mixed}
2295 mods: ['shift', 'ctrl']
2306 mods: ['shift', 'ctrl']
2310 mods: ['shift', 'ctrl']
2314 mods: ['shift', 'ctrl']
2318 mods: ['shift', 'ctrl']
2322 mods: ['shift', 'ctrl']
2326 mods: ['shift', 'ctrl']
2334 mods: ['shift', 'ctrl']
2338 mods: ['shift', 'ctrl']
2342 mods: ['shift', 'ctrl']
2346 mods: ['shift', 'ctrl']
2351 * @method _cleanClassName
2352 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2353 * @param {String} str The classname to clean up
2356 _cleanClassName: function(str) {
2357 return str.replace(/ /g, '-').toLowerCase();
2360 * @property _textarea
2361 * @description Flag to determine if we are using a textarea or an HTML Node.
2366 * @property _docType
2367 * @description The DOCTYPE to use in the editable container.
2370 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2372 * @property editorDirty
2373 * @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.
2378 * @property _defaultCSS
2379 * @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' }
2382 _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; }',
2384 * @property _defaultToolbar
2386 * @description Default toolbar config.
2389 _defaultToolbar: null,
2391 * @property _lastButton
2393 * @description The last button pressed, so we don't disable it.
2398 * @property _baseHREF
2400 * @description The base location of the editable page (this page) so that relative paths for image work.
2403 _baseHREF: function() {
2404 var href = document.location.href;
2405 if (href.indexOf('?') !== -1) { //Remove the query string
2406 href = href.substring(0, href.indexOf('?'));
2408 href = href.substring(0, href.lastIndexOf('/')) + '/';
2412 * @property _lastImage
2414 * @description Safari reference for the last image selected (for styling as selected).
2419 * @property _blankImageLoaded
2421 * @description Don't load the blank image more than once..
2424 _blankImageLoaded: null,
2426 * @property _fixNodesTimer
2428 * @description Holder for the fixNodes timer
2431 _fixNodesTimer: null,
2433 * @property _nodeChangeTimer
2435 * @description Holds a reference to the nodeChange setTimeout call
2438 _nodeChangeTimer: null,
2440 * @property _lastNodeChangeEvent
2442 * @description Flag to determine the last event that fired a node change
2445 _lastNodeChangeEvent: null,
2447 * @property _lastNodeChange
2449 * @description Flag to determine when the last node change was fired
2454 * @property _rendered
2456 * @description Flag to determine if editor has been rendered or not
2461 * @property DOMReady
2463 * @description Flag to determine if DOM is ready or not
2468 * @property _selection
2470 * @description Holder for caching iframe selections
2477 * @description DOM Element holder for the editor Mask when disabled
2482 * @property _showingHiddenElements
2484 * @description Status of the hidden elements button
2487 _showingHiddenElements: null,
2489 * @property currentWindow
2490 * @description A reference to the currently open EditorWindow
2493 currentWindow: null,
2495 * @property currentEvent
2496 * @description A reference to the current editor event
2501 * @property operaEvent
2503 * @description setTimeout holder for Opera and Image DoubleClick event..
2508 * @property currentFont
2509 * @description A reference to the last font selected from the Toolbar
2514 * @property currentElement
2515 * @description A reference to the current working element in the editor
2518 currentElement: null,
2521 * @description A reference to the dompath container for writing the current working dom path to.
2526 * @property beforeElement
2527 * @description A reference to the H2 placed before the editor for Accessibilty.
2530 beforeElement: null,
2532 * @property afterElement
2533 * @description A reference to the H2 placed after the editor for Accessibilty.
2538 * @property invalidHTML
2539 * @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.
2557 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2558 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2563 * @property _contentTimer
2564 * @description setTimeout holder for documentReady check
2566 _contentTimer: null,
2569 * @property _contentTimerCounter
2570 * @description Counter to check the number of times the body is polled for before giving up
2573 _contentTimerCounter: 0,
2576 * @property _disabled
2577 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2580 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2583 * @property _alwaysDisabled
2584 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2587 _alwaysDisabled: { undo: true, redo: true },
2590 * @property _alwaysEnabled
2591 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2594 _alwaysEnabled: { },
2597 * @property _semantic
2598 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2601 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2604 * @property _tag2cmd
2605 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2614 'sup': 'superscript',
2616 'img': 'insertimage',
2618 'ul' : 'insertunorderedlist',
2619 'ol' : 'insertorderedlist'
2623 * @private _createIframe
2624 * @description Creates the DOM and YUI Element for the iFrame editor area.
2625 * @param {String} id The string ID to prefix the iframe with
2626 * @return {Object} iFrame object
2628 _createIframe: function() {
2629 var ifrmDom = document.createElement('iframe');
2630 ifrmDom.id = this.get('id') + '_editor';
2638 allowTransparency: 'true',
2641 if (this.get('autoHeight')) {
2642 config.scrolling = 'no';
2644 for (var i in config) {
2645 if (Lang.hasOwnProperty(config, i)) {
2646 ifrmDom.setAttribute(i, config[i]);
2649 var isrc = 'javascript:;';
2650 if (this.browser.ie) {
2651 //isrc = 'about:blank';
2652 //TODO - Check this, I have changed it before..
2653 isrc = 'javascript:false;';
2655 ifrmDom.setAttribute('src', isrc);
2656 var ifrm = new YAHOO.util.Element(ifrmDom);
2657 ifrm.setStyle('visibility', 'hidden');
2661 * @private _isElement
2662 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2663 * @param {HTMLElement} el The element to check
2664 * @param {String} tag The tag that the element needs to be
2667 _isElement: function(el, tag) {
2668 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2671 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2677 * @private _hasParent
2678 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2679 * @param {HTMLElement} el The element to check
2680 * @param {String} tag The tag that the element needs to be
2681 * @return HTMLElement
2683 _hasParent: function(el, tag) {
2684 if (!el || !el.parentNode) {
2688 while (el.parentNode) {
2689 if (this._isElement(el, tag)) {
2692 if (el.parentNode) {
2703 * @description Get the Document of the IFRAME
2706 _getDoc: function() {
2709 if (this.get('iframe')) {
2710 if (this.get('iframe').get) {
2711 if (this.get('iframe').get('element')) {
2713 if (this.get('iframe').get('element').contentWindow) {
2714 if (this.get('iframe').get('element').contentWindow.document) {
2715 value = this.get('iframe').get('element').contentWindow.document;
2728 * @method _getWindow
2729 * @description Get the Window of the IFRAME
2732 _getWindow: function() {
2733 return this.get('iframe').get('element').contentWindow;
2737 * @description Attempt to set the focus of the iframes window.
2740 this._getWindow().focus();
2744 * @depreciated - This should not be used, moved to this.focus();
2745 * @method _focusWindow
2746 * @description Attempt to set the focus of the iframes window.
2748 _focusWindow: function() {
2753 * @method _hasSelection
2754 * @description Determines if there is a selection in the editor document.
2757 _hasSelection: function() {
2758 var sel = this._getSelection();
2759 var range = this._getRange();
2762 if (!sel || !range) {
2767 if (this.browser.ie || this.browser.opera) {
2775 if (this.browser.webkit) {
2776 if (sel+'' !== '') {
2780 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2789 * @method _getSelection
2790 * @description Handles the different selection objects across the A-Grade list.
2791 * @return {Object} Selection Object
2793 _getSelection: function() {
2795 if (this._getDoc() && this._getWindow()) {
2796 if (this._getDoc().selection) {
2797 _sel = this._getDoc().selection;
2799 _sel = this._getWindow().getSelection();
2801 //Handle Safari's lack of Selection Object
2802 if (this.browser.webkit) {
2803 if (_sel.baseNode) {
2804 this._selection = {};
2805 this._selection.baseNode = _sel.baseNode;
2806 this._selection.baseOffset = _sel.baseOffset;
2807 this._selection.extentNode = _sel.extentNode;
2808 this._selection.extentOffset = _sel.extentOffset;
2809 } else if (this._selection !== null) {
2810 _sel = this._getWindow().getSelection();
2811 _sel.setBaseAndExtent(
2812 this._selection.baseNode,
2813 this._selection.baseOffset,
2814 this._selection.extentNode,
2815 this._selection.extentOffset);
2816 this._selection = null;
2824 * @method _selectNode
2825 * @description Places the highlight around a given node
2826 * @param {HTMLElement} node The node to select
2828 _selectNode: function(node, collapse) {
2832 var sel = this._getSelection(),
2835 if (this.browser.ie) {
2836 try { //IE freaks out here sometimes..
2837 range = this._getDoc().body.createTextRange();
2838 range.moveToElementText(node);
2842 } else if (this.browser.webkit) {
2844 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2846 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2848 } else if (this.browser.opera) {
2849 sel = this._getWindow().getSelection();
2850 range = this._getDoc().createRange();
2851 range.selectNode(node);
2852 sel.removeAllRanges();
2853 sel.addRange(range);
2855 range = this._getDoc().createRange();
2856 range.selectNodeContents(node);
2857 sel.removeAllRanges();
2858 sel.addRange(range);
2860 //TODO - Check Performance
2866 * @description Handles the different range objects across the A-Grade list.
2867 * @return {Object} Range Object
2869 _getRange: function() {
2870 var sel = this._getSelection();
2876 if (this.browser.webkit && !sel.getRangeAt) {
2877 var _range = this._getDoc().createRange();
2879 _range.setStart(sel.anchorNode, sel.anchorOffset);
2880 _range.setEnd(sel.focusNode, sel.focusOffset);
2882 _range = this._getWindow().getSelection()+'';
2887 if (this.browser.ie || this.browser.opera) {
2889 return sel.createRange();
2895 if (sel.rangeCount > 0) {
2896 return sel.getRangeAt(0);
2902 * @method _setDesignMode
2903 * @description Sets the designMode of the iFrame document.
2904 * @param {String} state This should be either on or off
2906 _setDesignMode: function(state) {
2909 //SourceForge Bug #1807057
2910 if (this.browser.ie && (state.toLowerCase() == 'off')) {
2914 this._getDoc().designMode = state;
2920 * @method _toggleDesignMode
2921 * @description Toggles the designMode of the iFrame document on and off.
2922 * @return {String} The state that it was set to.
2924 _toggleDesignMode: function() {
2925 var _dMode = this._getDoc().designMode.toLowerCase(),
2927 if (_dMode == 'on') {
2930 this._setDesignMode(_state);
2935 * @property _focused
2936 * @description Holder for trapping focus/blur state and prevent double events
2942 * @method _handleFocus
2943 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event.
2944 * @param {Event} e The DOM Event
2946 _handleFocus: function(e) {
2947 if (!this._focused) {
2948 this._focused = true;
2949 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this });
2954 * @method _handleBlur
2955 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event.
2956 * @param {Event} e The DOM Event
2958 _handleBlur: function(e) {
2959 if (this._focused) {
2960 this._focused = false;
2961 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this });
2966 * @method _initEditorEvents
2967 * @description This method sets up the listeners on the Editors document.
2969 _initEditorEvents: function() {
2970 //Setup Listeners on iFrame
2971 var doc = this._getDoc(),
2972 win = this._getWindow();
2974 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
2975 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
2976 Event.on(doc, 'click', this._handleClick, this, true);
2977 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
2978 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
2979 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
2980 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
2981 /* TODO -- Everyone but Opera works here..
2982 Event.on(doc, 'paste', function() {
2987 Event.on(win, 'focus', this._handleFocus, this, true);
2988 Event.on(win, 'blur', this._handleBlur, this, true);
2992 * @method _removeEditorEvents
2993 * @description This method removes the listeners on the Editors document (for disabling).
2995 _removeEditorEvents: function() {
2996 //Remove Listeners on iFrame
2997 var doc = this._getDoc(),
2998 win = this._getWindow();
3000 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
3001 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
3002 Event.removeListener(doc, 'click', this._handleClick, this, true);
3003 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
3004 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
3005 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
3006 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
3009 Event.removeListener(win, 'focus', this._handleFocus, this, true);
3010 Event.removeListener(win, 'blur', this._handleBlur, this, true);
3012 _fixWebkitDivs: function() {
3013 if (this.browser.webkit) {
3014 var divs = this._getDoc().body.getElementsByTagName('div');
3015 Dom.addClass(divs, 'yui-wk-div');
3020 * @method _initEditor
3021 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
3023 _initEditor: function() {
3024 if (this.browser.ie) {
3025 this._getDoc().body.style.margin = '0';
3027 if (!this.get('disabled')) {
3028 if (this._getDoc().designMode.toLowerCase() != 'on') {
3029 this._setDesignMode('on');
3030 this._contentTimerCounter = 0;
3033 if (!this._getDoc().body) {
3034 this._contentTimerCounter = 0;
3035 this._checkLoaded();
3039 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
3040 if (!this.get('disabled')) {
3041 this._initEditorEvents();
3042 this.toolbar.set('disabled', false);
3045 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3046 this._fixWebkitDivs();
3047 if (this.get('dompath')) {
3049 setTimeout(function() {
3050 self._writeDomPath.call(self);
3051 self._setupResize.call(self);
3055 for (var i in this.browser) {
3056 if (this.browser[i]) {
3060 if (this.get('ptags')) {
3063 Dom.addClass(this._getDoc().body, br.join(' '));
3064 this.nodeChange(true);
3068 * @method _checkLoaded
3069 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3071 _checkLoaded: function() {
3072 this._contentTimerCounter++;
3073 if (this._contentTimer) {
3074 clearTimeout(this._contentTimer);
3076 if (this._contentTimerCounter > 500) {
3081 if (this._getDoc() && this._getDoc().body) {
3082 if (this.browser.ie) {
3083 if (this._getDoc().body.readyState == 'complete') {
3087 if (this._getDoc().body._rteLoaded === true) {
3096 if (init === true) {
3097 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3101 this._contentTimer = setTimeout(function() {
3102 self._checkLoaded.call(self);
3108 * @method _setInitialContent
3109 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3111 _setInitialContent: function() {
3113 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3116 if ((value === '') && this.browser.gecko) {
3117 //It seems that Gecko doesn't like an empty body so we have to give it something..
3121 var html = Lang.substitute(this.get('html'), {
3122 TITLE: this.STR_TITLE,
3123 CONTENT: this._cleanIncomingHTML(value),
3124 CSS: this.get('css'),
3125 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3126 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3129 if (document.compatMode != 'BackCompat') {
3130 html = this._docType + "\n" + html;
3134 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3135 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3138 if (this.browser.air) {
3139 doc = this._getDoc().implementation.createHTMLDocument();
3140 var origDoc = this._getDoc();
3146 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3147 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3148 origDoc.body._rteLoaded = true;
3150 doc = this._getDoc();
3156 //Safari will only be here if we are hidden
3160 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3161 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3163 this.get('iframe').setStyle('visibility', '');
3165 this._checkLoaded();
3170 * @method _setMarkupType
3171 * @param {String} action The action to take. Possible values are: css, default or semantic
3172 * @description This method will turn on/off the useCSS execCommand.
3174 _setMarkupType: function(action) {
3175 switch (this.get('markup')) {
3177 this._setEditorStyle(true);
3180 this._setEditorStyle(false);
3184 if (this._semantic[action]) {
3185 this._setEditorStyle(false);
3187 this._setEditorStyle(true);
3193 * Set the editor to use CSS instead of HTML
3194 * @param {Booleen} stat True/False
3196 _setEditorStyle: function(stat) {
3198 this._getDoc().execCommand('useCSS', false, !stat);
3204 * @method _getSelectedElement
3205 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3206 * @return {HTMLElement} The currently selected element.
3208 _getSelectedElement: function() {
3209 var doc = this._getDoc(),
3215 if (this.browser.ie) {
3216 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3217 range = this._getRange();
3219 elm = range.item ? range.item(0) : range.parentElement();
3220 if (this._hasSelection()) {
3222 //WTF.. Why can't I get an element reference here?!??!
3224 if (elm === doc.body) {
3228 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3229 elm = Event.getTarget(this.currentEvent);
3232 sel = this._getSelection();
3233 range = this._getRange();
3235 if (!sel || !range) {
3239 if (!this._hasSelection() && this.browser.webkit3) {
3242 if (this.browser.gecko) {
3244 if (range.startContainer) {
3245 if (range.startContainer.nodeType === 3) {
3246 elm = range.startContainer.parentNode;
3247 } else if (range.startContainer.nodeType === 1) {
3248 elm = range.startContainer;
3251 if (this.currentEvent) {
3252 var tar = Event.getTarget(this.currentEvent);
3253 if (!this._isElement(tar, 'html')) {
3263 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3264 if (sel.anchorNode.parentNode) { //next check parentNode
3265 elm = sel.anchorNode.parentNode;
3267 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3268 elm = sel.anchorNode.nextSibling;
3271 if (this._isElement(elm, 'br')) {
3275 elm = range.commonAncestorContainer;
3276 if (!range.collapsed) {
3277 if (range.startContainer == range.endContainer) {
3278 if (range.startOffset - range.endOffset < 2) {
3279 if (range.startContainer.hasChildNodes()) {
3280 elm = range.startContainer.childNodes[range.startOffset];
3289 if (this.currentEvent !== null) {
3291 switch (this.currentEvent.type) {
3295 if (this.browser.webkit) {
3296 elm = Event.getTarget(this.currentEvent);
3305 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3306 //TODO is this still needed?
3307 //elm = this.currentElement[0];
3311 if (this.browser.opera || this.browser.webkit) {
3312 if (this.currentEvent && !elm) {
3313 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3316 if (!elm || !elm.tagName) {
3319 if (this._isElement(elm, 'html')) {
3320 //Safari sometimes gives us the HTML node back..
3323 if (this._isElement(elm, 'body')) {
3324 //make sure that body means this body not the parent..
3327 if (elm && !elm.parentNode) { //Not in document
3330 if (elm === undefined) {
3337 * @method _getDomPath
3338 * @description This method will attempt to build the DOM path from the currently selected element.
3339 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3340 * @return {Array} An array of node references that will create the DOM Path.
3342 _getDomPath: function(el) {
3344 el = this._getSelectedElement();
3347 while (el !== null) {
3348 if (el.ownerDocument != this._getDoc()) {
3352 //Check to see if we get el.nodeName and nodeType
3353 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3354 domPath[domPath.length] = el;
3357 if (this._isElement(el, 'body')) {
3363 if (domPath.length === 0) {
3364 if (this._getDoc() && this._getDoc().body) {
3365 domPath[0] = this._getDoc().body;
3368 return domPath.reverse();
3372 * @method _writeDomPath
3373 * @description Write the current DOM path out to the dompath container below the editor.
3375 _writeDomPath: function() {
3376 var path = this._getDomPath(),
3381 for (var i = 0; i < path.length; i++) {
3382 var tag = path[i].tagName.toLowerCase();
3383 if ((tag == 'ol') && (path[i].type)) {
3384 tag += ':' + path[i].type;
3386 if (Dom.hasClass(path[i], 'yui-tag')) {
3387 tag = path[i].getAttribute('tag');
3389 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3391 case 'b': tag = 'strong'; break;
3392 case 'i': tag = 'em'; break;
3395 if (!Dom.hasClass(path[i], 'yui-non')) {
3396 if (Dom.hasClass(path[i], 'yui-tag')) {
3399 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3400 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3403 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3410 if (path[i].getAttribute('href', 2)) {
3411 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3415 var h = path[i].height;
3416 var w = path[i].width;
3417 if (path[i].style.height) {
3418 h = parseInt(path[i].style.height, 10);
3420 if (path[i].style.width) {
3421 w = parseInt(path[i].style.width, 10);
3423 pathStr += '(' + w + 'x' + h + ')';
3427 if (pathStr.length > 10) {
3428 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3430 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3432 pathArr[pathArr.length] = pathStr;
3435 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3436 //Prevent flickering
3437 if (this.dompath.innerHTML != str) {
3438 this.dompath.innerHTML = str;
3444 * @description Fix href and imgs as well as remove invalid HTML.
3446 _fixNodes: function() {
3447 var doc = this._getDoc(),
3450 for (var v in this.invalidHTML) {
3451 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3452 if (v.toLowerCase() != 'span') {
3453 var tags = doc.body.getElementsByTagName(v);
3455 for (var i = 0; i < tags.length; i++) {
3462 for (var h = 0; h < els.length; h++) {
3463 if (els[h].parentNode) {
3464 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3465 this._swapEl(els[h], 'span', function(el) {
3466 el.className = 'yui-non';
3469 els[h].parentNode.removeChild(els[h]);
3473 var imgs = this._getDoc().getElementsByTagName('img');
3474 Dom.addClass(imgs, 'yui-img');
3478 * @method _isNonEditable
3479 * @param Event ev The Dom event being checked
3480 * @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.
3481 * 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
3482 * disable and enable the Editor's toolbar based on the noedit state.
3485 _isNonEditable: function(ev) {
3486 if (this.get('allowNoEdit')) {
3487 var el = Event.getTarget(ev);
3488 if (this._isElement(el, 'html')) {
3491 var path = this._getDomPath(el);
3492 for (var i = (path.length - 1); i > -1; i--) {
3493 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3494 //if (this.toolbar.get('disabled') === false) {
3495 // this.toolbar.set('disabled', true);
3498 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3501 Event.stopEvent(ev);
3505 //if (this.toolbar.get('disabled') === true) {
3506 //Should only happen once..
3507 //this.toolbar.set('disabled', false);
3509 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3517 * @method _setCurrentEvent
3518 * @param {Event} ev The event to cache
3519 * @description Sets the current event property
3521 _setCurrentEvent: function(ev) {
3522 this.currentEvent = ev;
3526 * @method _handleClick
3527 * @param {Event} ev The event we are working on.
3528 * @description Handles all click events inside the iFrame document.
3530 _handleClick: function(ev) {
3531 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3532 if (ret === false) {
3535 if (this._isNonEditable(ev)) {
3538 this._setCurrentEvent(ev);
3539 if (this.currentWindow) {
3542 if (this.currentWindow) {
3545 if (this.browser.webkit) {
3546 var tar =Event.getTarget(ev);
3547 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3548 Event.stopEvent(ev);
3554 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3558 * @method _handleMouseUp
3559 * @param {Event} ev The event we are working on.
3560 * @description Handles all mouseup events inside the iFrame document.
3562 _handleMouseUp: function(ev) {
3563 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3564 if (ret === false) {
3567 if (this._isNonEditable(ev)) {
3570 //Don't set current event for mouseup.
3571 //It get's fired after a menu is closed and gives up a bogus event to work with
3572 //this._setCurrentEvent(ev);
3574 if (this.browser.opera) {
3576 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3578 * @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.
3580 var sel = Event.getTarget(ev);
3581 if (this._isElement(sel, 'img')) {
3583 if (this.operaEvent) {
3584 clearTimeout(this.operaEvent);
3585 this.operaEvent = null;
3586 this._handleDoubleClick(ev);
3588 this.operaEvent = window.setTimeout(function() {
3589 self.operaEvent = false;
3594 //This will stop Safari from selecting the entire document if you select all the text in the editor
3595 if (this.browser.webkit || this.browser.opera) {
3596 if (this.browser.webkit) {
3597 Event.stopEvent(ev);
3601 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3605 * @method _handleMouseDown
3606 * @param {Event} ev The event we are working on.
3607 * @description Handles all mousedown events inside the iFrame document.
3609 _handleMouseDown: function(ev) {
3610 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3611 if (ret === false) {
3614 if (this._isNonEditable(ev)) {
3617 this._setCurrentEvent(ev);
3618 var sel = Event.getTarget(ev);
3619 if (this.browser.webkit && this._hasSelection()) {
3620 var _sel = this._getSelection();
3621 if (!this.browser.webkit3) {
3622 _sel.collapse(true);
3624 _sel.collapseToStart();
3627 if (this.browser.webkit && this._lastImage) {
3628 Dom.removeClass(this._lastImage, 'selected');
3629 this._lastImage = null;
3631 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3632 if (this.browser.webkit) {
3633 Event.stopEvent(ev);
3634 if (this._isElement(sel, 'img')) {
3635 Dom.addClass(sel, 'selected');
3636 this._lastImage = sel;
3639 if (this.currentWindow) {
3644 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3648 * @method _handleDoubleClick
3649 * @param {Event} ev The event we are working on.
3650 * @description Handles all doubleclick events inside the iFrame document.
3652 _handleDoubleClick: function(ev) {
3653 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3654 if (ret === false) {
3657 if (this._isNonEditable(ev)) {
3660 this._setCurrentEvent(ev);
3661 var sel = Event.getTarget(ev);
3662 if (this._isElement(sel, 'img')) {
3663 this.currentElement[0] = sel;
3664 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3665 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3666 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3667 this.currentElement[0] = this._hasParent(sel, 'a');
3668 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3669 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3672 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3676 * @method _handleKeyUp
3677 * @param {Event} ev The event we are working on.
3678 * @description Handles all keyup events inside the iFrame document.
3680 _handleKeyUp: function(ev) {
3681 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3682 if (ret === false) {
3685 if (this._isNonEditable(ev)) {
3688 this._setCurrentEvent(ev);
3689 switch (ev.keyCode) {
3690 case this._keyMap.SELECT_ALL.key:
3691 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3695 case 32: //Space Bar
3698 case 37: //Left Arrow
3700 case 39: //Right Arrow
3701 case 40: //Down Arrow
3702 case 46: //Forward Delete
3704 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3705 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3706 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3710 if (!this.browser.ie) {
3711 if (this._nodeChangeTimer) {
3712 clearTimeout(this._nodeChangeTimer);
3715 this._nodeChangeTimer = setTimeout(function() {
3716 self._nodeChangeTimer = null;
3717 self.nodeChange.call(self);
3722 this.editorDirty = true;
3726 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3731 * @method _handleKeyPress
3732 * @param {Event} ev The event we are working on.
3733 * @description Handles all keypress events inside the iFrame document.
3735 _handleKeyPress: function(ev) {
3736 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3737 if (ret === false) {
3741 if (this.get('allowNoEdit')) {
3742 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3743 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3744 //Forward delete key
3745 Event.stopEvent(ev);
3748 if (this._isNonEditable(ev)) {
3751 this._setCurrentEvent(ev);
3752 if (this.browser.opera) {
3753 if (ev.keyCode === 13) {
3754 var tar = this._getSelectedElement();
3755 if (!this._isElement(tar, 'li')) {
3756 this.execCommand('inserthtml', '<br>');
3757 Event.stopEvent(ev);
3761 if (this.browser.webkit) {
3762 if (!this.browser.webkit3) {
3763 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3764 //This is CMD + z (for undo)
3765 if (this._hasParent(this._getSelectedElement(), 'li')) {
3766 Event.stopEvent(ev);
3772 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3776 * @method _handleKeyDown
3777 * @param {Event} ev The event we are working on.
3778 * @description Handles all keydown events inside the iFrame document.
3780 _handleKeyDown: function(ev) {
3781 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3782 if (ret === false) {
3785 var tar = null, _range = null;
3786 if (this._isNonEditable(ev)) {
3789 this._setCurrentEvent(ev);
3790 if (this.currentWindow) {
3793 if (this.currentWindow) {
3802 switch (ev.keyCode) {
3803 case this._keyMap.FOCUS_TOOLBAR.key:
3804 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3805 var h = this.toolbar.getElementsByTagName('h2')[0];
3806 if (h && h.firstChild) {
3807 h.firstChild.focus();
3809 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3810 //Focus After Element - Esc
3811 this.afterElement.focus();
3813 Event.stopEvent(ev);
3817 case this._keyMap.CREATE_LINK.key: //L
3818 if (this._hasSelection()) {
3819 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3820 var makeLink = true;
3821 if (this.get('limitCommands')) {
3822 if (!this.toolbar.getButtonByValue('createlink')) {
3827 this.execCommand('createlink', '');
3828 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3829 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3836 case this._keyMap.UNDO.key:
3837 case this._keyMap.REDO.key:
3838 if (this._checkKey(this._keyMap.REDO, ev)) {
3841 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3847 case this._keyMap.BOLD.key:
3848 if (this._checkKey(this._keyMap.BOLD, ev)) {
3853 case this._keyMap.FONT_SIZE_UP.key:
3854 case this._keyMap.FONT_SIZE_DOWN.key:
3855 var uk = false, dk = false;
3856 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) {
3859 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) {
3863 var fs_button = this.toolbar.getButtonByValue('fontsize'),
3864 label = parseInt(fs_button.get('label'), 10),
3865 newValue = (label + 1);
3868 newValue = (label - 1);
3871 action = 'fontsize';
3872 value = newValue + 'px';
3877 case this._keyMap.ITALIC.key:
3878 if (this._checkKey(this._keyMap.ITALIC, ev)) {
3884 case this._keyMap.UNDERLINE.key:
3885 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3886 action = 'underline';
3891 if (this.browser.ie) {
3892 //Insert a tab in Internet Explorer
3893 _range = this._getRange();
3894 tar = this._getSelectedElement();
3895 if (!this._isElement(tar, 'li')) {
3897 _range.pasteHTML(' ');
3898 _range.collapse(false);
3901 Event.stopEvent(ev);
3905 if (this.browser.gecko > 1.8) {
3906 tar = this._getSelectedElement();
3907 if (this._isElement(tar, 'li')) {
3909 this._getDoc().execCommand('outdent', null, '');
3911 this._getDoc().execCommand('indent', null, '');
3914 } else if (!this._hasSelection()) {
3915 this.execCommand('inserthtml', ' ');
3917 Event.stopEvent(ev);
3921 var p = null, i = 0;
3922 if (this.get('ptags') && !ev.shiftKey) {
3923 if (this.browser.gecko) {
3924 tar = this._getSelectedElement();
3925 if (!this._hasParent(tar, 'li')) {
3926 if (this._hasParent(tar, 'p')) {
3927 p = this._getDoc().createElement('p');
3928 p.innerHTML = ' ';
3929 Dom.insertAfter(p, tar);
3930 this._selectNode(p.firstChild);
3931 } else if (this._isElement(tar, 'body')) {
3932 this.execCommand('insertparagraph', null);
3933 var ps = this._getDoc().body.getElementsByTagName('p');
3934 for (i = 0; i < ps.length; i++) {
3935 if (ps[i].getAttribute('_moz_dirty') !== null) {
3936 p = this._getDoc().createElement('p');
3937 p.innerHTML = ' ';
3938 Dom.insertAfter(p, ps[i]);
3939 this._selectNode(p.firstChild);
3940 ps[i].removeAttribute('_moz_dirty');
3945 action = 'insertparagraph';
3947 Event.stopEvent(ev);
3950 if (this.browser.webkit) {
3951 tar = this._getSelectedElement();
3952 if (!this._hasParent(tar, 'li')) {
3953 this.execCommand('insertparagraph', null);
3954 var divs = this._getDoc().body.getElementsByTagName('div');
3955 for (i = 0; i < divs.length; i++) {
3956 if (!Dom.hasClass(divs[i], 'yui-wk-div')) {
3957 Dom.addClass(divs[i], 'yui-wk-p');
3960 Event.stopEvent(ev);
3964 if (this.browser.webkit) {
3965 tar = this._getSelectedElement();
3966 if (!this._hasParent(tar, 'li')) {
3967 this.execCommand('inserthtml', '<var id="yui-br"></var>');
3968 var holder = this._getDoc().getElementById('yui-br'),
3969 br = this._getDoc().createElement('br'),
3970 caret = this._getDoc().createElement('span');
3972 holder.parentNode.replaceChild(br, holder);
3973 caret.className = 'yui-non';
3974 caret.innerHTML = ' ';
3975 Dom.insertAfter(caret, br);
3976 this._selectNode(caret);
3977 Event.stopEvent(ev);
3980 if (this.browser.ie) {
3981 //Insert a <br> instead of a <p></p> in Internet Explorer
3982 _range = this._getRange();
3983 tar = this._getSelectedElement();
3984 if (!this._isElement(tar, 'li')) {
3986 _range.pasteHTML('<br>');
3987 _range.collapse(false);
3990 Event.stopEvent(ev);
3996 if (this.browser.ie) {
3999 if (doExec && action) {
4000 this.execCommand(action, value);
4001 Event.stopEvent(ev);
4004 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
4009 * @param {Event} ev The event we are working on.
4010 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
4012 _listFix: function(ev) {
4013 var testLi = null, par = null, preContent = false, range = null;
4015 if (this.browser.webkit) {
4016 if (ev.keyCode && (ev.keyCode == 13)) {
4017 if (this._hasParent(this._getSelectedElement(), 'li')) {
4018 var tar = this._hasParent(this._getSelectedElement(), 'li');
4019 if (tar.previousSibling) {
4020 if (tar.firstChild && (tar.firstChild.length == 1)) {
4021 this._selectNode(tar);
4028 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
4029 testLi = this._getSelectedElement();
4030 if (this._hasParent(testLi, 'li')) {
4031 testLi = this._hasParent(testLi, 'li');
4032 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
4033 par = this._hasParent(testLi, 'ul');
4035 par = this._hasParent(testLi, 'ol');
4037 if (this._isElement(par.previousSibling, 'li')) {
4038 par.removeChild(testLi);
4039 par.parentNode.insertBefore(testLi, par.nextSibling);
4040 if (this.browser.ie) {
4041 range = this._getDoc().body.createTextRange();
4042 range.moveToElementText(testLi);
4043 range.collapse(false);
4046 if (this.browser.webkit) {
4047 this._selectNode(testLi.firstChild);
4049 Event.stopEvent(ev);
4055 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
4056 var preLi = this._getSelectedElement();
4057 if (this._hasParent(preLi, 'li')) {
4058 preContent = this._hasParent(preLi, 'li').innerHTML;
4060 if (this.browser.webkit) {
4061 this._getDoc().execCommand('inserttext', false, '\t');
4063 testLi = this._getSelectedElement();
4064 if (this._hasParent(testLi, 'li')) {
4065 par = this._hasParent(testLi, 'li');
4066 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
4067 if (this.browser.webkit) {
4068 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
4069 //Remove the span element that Safari puts in
4071 par.removeChild(span[0]);
4072 par.innerHTML = Lang.trim(par.innerHTML);
4073 //Put the HTML from the LI into this new LI
4075 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
4077 par.innerHTML = '<span class="yui-non"> </span> ';
4082 par.innerHTML = preContent + ' ';
4084 par.innerHTML = ' ';
4088 par.parentNode.replaceChild(newUl, par);
4089 newUl.appendChild(par);
4090 if (this.browser.webkit) {
4091 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
4092 if (!this.browser.webkit3) {
4093 par.parentNode.parentNode.style.display = 'list-item';
4094 setTimeout(function() {
4095 par.parentNode.parentNode.style.display = 'block';
4098 } else if (this.browser.ie) {
4099 range = this._getDoc().body.createTextRange();
4100 range.moveToElementText(par);
4101 range.collapse(false);
4104 this._selectNode(par);
4106 Event.stopEvent(ev);
4108 if (this.browser.webkit) {
4109 Event.stopEvent(ev);
4115 * @method nodeChange
4116 * @param {Boolean} force Optional paramenter to skip the threshold counter
4117 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4119 nodeChange: function(force) {
4122 if (this.get('nodeChangeDelay')) {
4123 window.setTimeout(function() {
4124 NCself._nodeChange.apply(NCself, arguments);
4132 * @method _nodeChange
4133 * @param {Boolean} force Optional paramenter to skip the threshold counter
4134 * @description Fired from nodeChange in a setTimeout.
4136 _nodeChange: function(force) {
4137 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4138 thisNodeChange = Math.round(new Date().getTime() / 1000),
4141 if (force === true) {
4142 this._lastNodeChange = 0;
4145 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4146 if (this._fixNodesTimer === null) {
4147 this._fixNodesTimer = window.setTimeout(function() {
4148 self._fixNodes.call(self);
4149 self._fixNodesTimer = null;
4153 this._lastNodeChange = thisNodeChange;
4154 if (this.currentEvent) {
4156 this._lastNodeChangeEvent = this.currentEvent.type;
4160 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4161 if (beforeNodeChange === false) {
4164 if (this.get('dompath')) {
4165 window.setTimeout(function() {
4166 self._writeDomPath.call(self);
4169 //Check to see if we are disabled before continuing
4170 if (!this.get('disabled')) {
4171 if (this.STOP_NODE_CHANGE) {
4172 //Reset this var for next action
4173 this.STOP_NODE_CHANGE = false;
4176 var sel = this._getSelection(),
4177 range = this._getRange(),
4178 el = this._getSelectedElement(),
4179 fn_button = this.toolbar.getButtonByValue('fontname'),
4180 fs_button = this.toolbar.getButtonByValue('fontsize'),
4181 undo_button = this.toolbar.getButtonByValue('undo'),
4182 redo_button = this.toolbar.getButtonByValue('redo');
4184 //Handle updating the toolbar with active buttons
4186 if (this._lastButton) {
4187 _ex[this._lastButton.id] = true;
4188 //this._lastButton = null;
4190 if (!this._isElement(el, 'body')) {
4192 _ex[fn_button.get('id')] = true;
4195 _ex[fs_button.get('id')] = true;
4199 delete _ex[redo_button.get('id')];
4201 this.toolbar.resetAllButtons(_ex);
4203 //Handle disabled buttons
4204 for (var d = 0; d < this._disabled.length; d++) {
4205 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4206 if (_button && _button.get) {
4207 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4210 if (!this._hasSelection() && !this.get('insert')) {
4211 switch (this._disabled[d]) {
4216 //No Selection - disable
4217 this.toolbar.disableButton(_button);
4220 if (!this._alwaysDisabled[this._disabled[d]]) {
4221 this.toolbar.enableButton(_button);
4224 if (!this._alwaysEnabled[this._disabled[d]]) {
4225 this.toolbar.deselectButton(_button);
4230 var path = this._getDomPath();
4231 var tag = null, cmd = null;
4232 for (var i = 0; i < path.length; i++) {
4233 tag = path[i].tagName.toLowerCase();
4234 if (path[i].getAttribute('tag')) {
4235 tag = path[i].getAttribute('tag').toLowerCase();
4237 cmd = this._tag2cmd[tag];
4238 if (cmd === undefined) {
4241 if (!Lang.isArray(cmd)) {
4245 //Bold and Italic styles
4246 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4247 cmd[cmd.length] = 'bold';
4249 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4250 cmd[cmd.length] = 'italic';
4252 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4253 cmd[cmd.length] = 'underline';
4255 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4256 cmd[cmd.length] = 'strikethrough';
4258 if (cmd.length > 0) {
4259 for (var j = 0; j < cmd.length; j++) {
4260 this.toolbar.selectButton(cmd[j]);
4261 this.toolbar.enableButton(cmd[j]);
4265 switch (path[i].style.textAlign.toLowerCase()) {
4270 var alignType = path[i].style.textAlign.toLowerCase();
4271 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4274 this.toolbar.selectButton('justify' + alignType);
4275 this.toolbar.enableButton('justify' + alignType);
4281 //Reset Font Family and Size to the inital configs
4283 var family = fn_button._configs.label._initialConfig.value;
4284 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4285 this._updateMenuChecked('fontname', family);
4289 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4292 var hd_button = this.toolbar.getButtonByValue('heading');
4294 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4295 this._updateMenuChecked('heading', 'none');
4297 var img_button = this.toolbar.getButtonByValue('insertimage');
4298 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4299 this.toolbar.disableButton(img_button);
4301 if (this._lastButton && this._lastButton.isSelected) {
4302 this.toolbar.deselectButton(this._lastButton.id);
4304 this._undoNodeChange();
4308 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4312 * @method _updateMenuChecked
4313 * @param {Object} button The command identifier of the button you want to check
4314 * @param {String} value The value of the menu item you want to check
4315 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4316 * @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.
4318 _updateMenuChecked: function(button, value, tbar) {
4320 tbar = this.toolbar;
4322 var _button = tbar.getButtonByValue(button);
4323 _button.checkValue(value);
4327 * @method _handleToolbarClick
4328 * @param {Event} ev The event that triggered the button click
4329 * @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.
4331 _handleToolbarClick: function(ev) {
4334 var cmd = ev.button.value;
4335 if (ev.button.menucmd) {
4337 cmd = ev.button.menucmd;
4339 this._lastButton = ev.button;
4340 if (this.STOP_EXEC_COMMAND) {
4341 this.STOP_EXEC_COMMAND = false;
4344 this.execCommand(cmd, value);
4345 if (!this.browser.webkit) {
4347 setTimeout(function() {
4348 Fself.focus.call(Fself);
4352 Event.stopEvent(ev);
4356 * @method _setupAfterElement
4357 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4359 _setupAfterElement: function() {
4360 if (!this.beforeElement) {
4361 this.beforeElement = document.createElement('h2');
4362 this.beforeElement.className = 'yui-editor-skipheader';
4363 this.beforeElement.tabIndex = '-1';
4364 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4365 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4367 if (!this.afterElement) {
4368 this.afterElement = document.createElement('h2');
4369 this.afterElement.className = 'yui-editor-skipheader';
4370 this.afterElement.tabIndex = '-1';
4371 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4372 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4377 * @method _disableEditor
4378 * @param {Boolean} disabled Pass true to disable, false to enable
4379 * @description Creates a mask to place over the Editor.
4381 _disableEditor: function(disabled) {
4383 this._removeEditorEvents();
4385 if (!!this.browser.ie) {
4386 this._setDesignMode('off');
4389 this.toolbar.set('disabled', true);
4391 this._mask = document.createElement('DIV');
4392 Dom.addClass(this._mask, 'yui-editor-masked');
4393 this.get('iframe').get('parentNode').appendChild(this._mask);
4396 this._initEditorEvents();
4398 this._mask.parentNode.removeChild(this._mask);
4401 this.toolbar.set('disabled', false);
4403 this._setDesignMode('on');
4406 window.setTimeout(function() {
4407 self.nodeChange.call(self);
4413 * @property SEP_DOMPATH
4414 * @description The value to place in between the Dom path items
4419 * @property STR_LEAVE_EDITOR
4420 * @description The accessibility string for the element after the iFrame
4423 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4425 * @property STR_BEFORE_EDITOR
4426 * @description The accessibility string for the element before the iFrame
4429 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>',
4431 * @property STR_TITLE
4432 * @description The Title of the HTML document that is created in the iFrame
4435 STR_TITLE: 'Rich Text Area.',
4437 * @property STR_IMAGE_HERE
4438 * @description The text to place in the URL textbox when using the blankimage.
4441 STR_IMAGE_HERE: 'Image URL Here',
4443 * @property STR_IMAGE_URL
4444 * @description The label string for Image URL
4447 STR_IMAGE_URL: 'Image URL',
4449 * @property STR_LINK_URL
4450 * @description The label string for the Link URL.
4453 STR_LINK_URL: 'Link URL',
4456 * @property STOP_EXEC_COMMAND
4457 * @description Set to true when you want the default execCommand function to not process anything
4460 STOP_EXEC_COMMAND: false,
4463 * @property STOP_NODE_CHANGE
4464 * @description Set to true when you want the default nodeChange function to not process anything
4467 STOP_NODE_CHANGE: false,
4470 * @property CLASS_NOEDIT
4471 * @description CSS class applied to elements that are not editable.
4474 CLASS_NOEDIT: 'yui-noedit',
4477 * @property CLASS_CONTAINER
4478 * @description Default CSS class to apply to the editors container element
4481 CLASS_CONTAINER: 'yui-editor-container',
4484 * @property CLASS_EDITABLE
4485 * @description Default CSS class to apply to the editors iframe element
4488 CLASS_EDITABLE: 'yui-editor-editable',
4491 * @property CLASS_EDITABLE_CONT
4492 * @description Default CSS class to apply to the editors iframe's parent element
4495 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4498 * @property CLASS_PREFIX
4499 * @description Default prefix for dynamically created class names
4502 CLASS_PREFIX: 'yui-editor',
4505 * @description Standard browser detection
4508 browser: function() {
4509 var br = YAHOO.env.ua;
4511 if (br.webkit >= 420) {
4512 br.webkit3 = br.webkit;
4518 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4526 * @description The Editor class' initialization method
4528 init: function(p_oElement, p_oAttributes) {
4530 if (!this._defaultToolbar) {
4531 this._defaultToolbar = {
4533 titlebar: 'Text Editing Tools',
4536 { group: 'fontstyle', label: 'Font Name and Size',
4538 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4540 { text: 'Arial', checked: true },
4541 { text: 'Arial Black' },
4542 { text: 'Comic Sans MS' },
4543 { text: 'Courier New' },
4544 { text: 'Lucida Console' },
4546 { text: 'Times New Roman' },
4547 { text: 'Trebuchet MS' },
4551 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4554 { type: 'separator' },
4555 { group: 'textstyle', label: 'Font Style',
4557 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4558 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4559 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4560 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4561 { type: 'separator' },
4562 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4563 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4567 { type: 'separator' },
4568 { group: 'indentlist', label: 'Lists',
4570 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4571 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4574 { type: 'separator' },
4575 { group: 'insertitem', label: 'Insert Item',
4577 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4578 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4585 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4586 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4589 this.currentElement = [];
4590 this.on('contentReady', function() {
4591 this.DOMReady = true;
4597 * @method initAttributes
4598 * @description Initializes all of the configuration attributes used to create
4600 * @param {Object} attr Object literal specifying a set of
4601 * configuration attributes used to create the editor.
4603 initAttributes: function(attr) {
4604 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4608 * @config nodeChangeDelay
4609 * @description Do we wrap the nodeChange method in a timeout for performance, default: true.
4613 this.setAttributeConfig('nodeChangeDelay', {
4614 value: ((attr.nodeChangeDelay === false) ? false : true)
4618 * @description The max number of undo levels to store.
4622 this.setAttributeConfig('maxUndo', {
4624 value: attr.maxUndo || 30
4629 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4633 this.setAttributeConfig('ptags', {
4635 value: attr.ptags || false
4639 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4643 this.setAttributeConfig('insert', {
4645 value: attr.insert || false,
4646 method: function(insert) {
4654 var tmp = this._defaultToolbar.buttons;
4655 for (var i = 0; i < tmp.length; i++) {
4656 if (tmp[i].buttons) {
4657 for (var a = 0; a < tmp[i].buttons.length; a++) {
4658 if (tmp[i].buttons[a].value) {
4659 if (buttons[tmp[i].buttons[a].value]) {
4660 delete tmp[i].buttons[a].disabled;
4671 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4672 * We will create one and place it in this container. If no container is passed we will append to document.body.
4676 this.setAttributeConfig('container', {
4678 value: attr.container || false
4682 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4686 this.setAttributeConfig('plainText', {
4688 value: attr.plainText || false
4693 * @description Internal config for holding the iframe element.
4697 this.setAttributeConfig('iframe', {
4702 * @depreciated - No longer used, should use this.get('element')
4704 * @description Internal config for holding the textarea element (replaced with element).
4708 this.setAttributeConfig('textarea', {
4713 * @config nodeChangeThreshold
4714 * @description The number of seconds that need to be in between nodeChange processing
4718 this.setAttributeConfig('nodeChangeThreshold', {
4719 value: attr.nodeChangeThreshold || 3,
4720 validator: YAHOO.lang.isNumber
4723 * @config allowNoEdit
4724 * @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.
4725 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4729 this.setAttributeConfig('allowNoEdit', {
4730 value: attr.allowNoEdit || false,
4731 validator: YAHOO.lang.isBoolean
4734 * @config limitCommands
4735 * @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.
4739 this.setAttributeConfig('limitCommands', {
4740 value: attr.limitCommands || false,
4741 validator: YAHOO.lang.isBoolean
4744 * @config element_cont
4745 * @description Internal config for the editors container
4749 this.setAttributeConfig('element_cont', {
4750 value: attr.element_cont
4754 * @config editor_wrapper
4755 * @description The outter wrapper for the entire editor.
4759 this.setAttributeConfig('editor_wrapper', {
4760 value: attr.editor_wrapper || null,
4765 * @description The height of the editor iframe container, not including the toolbar..
4766 * @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
4769 this.setAttributeConfig('height', {
4770 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4771 method: function(height) {
4772 if (this._rendered) {
4773 //We have been rendered, change the height
4774 if (this.get('animate')) {
4775 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4777 to: parseInt(height, 10)
4782 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4788 * @config autoHeight
4789 * @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.
4791 * @type Boolean || Number
4793 this.setAttributeConfig('autoHeight', {
4794 value: attr.autoHeight || false,
4795 method: function(a) {
4797 if (this.get('iframe')) {
4798 this.get('iframe').get('element').setAttribute('scrolling', 'no');
4800 this.on('afterNodeChange', this._handleAutoHeight, this, true);
4801 this.on('editorKeyDown', this._handleAutoHeight, this, true);
4802 this.on('editorKeyPress', this._handleAutoHeight, this, true);
4804 if (this.get('iframe')) {
4805 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4807 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4808 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4809 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4815 * @description The width of the editor container.
4816 * @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
4819 this.setAttributeConfig('width', {
4820 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
4821 method: function(width) {
4822 if (this._rendered) {
4823 //We have been rendered, change the width
4824 if (this.get('animate')) {
4825 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
4827 to: parseInt(width, 10)
4832 this.get('element_cont').setStyle('width', width);
4839 * @attribute blankimage
4840 * @description The URL for the image placeholder to put in when inserting an image.
4841 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
4844 this.setAttributeConfig('blankimage', {
4845 value: attr.blankimage || this._getBlankImage()
4849 * @description The Base CSS used to format the content of the editor
4850 * @default <code><pre>html {
4855 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
4859 text-decoration: underline;
4862 .warning-localfile {
4863 border-bottom: 1px dashed red !important;
4866 cursor: wait !important;
4868 img.selected { //Safari image selection
4869 border: 2px dotted #808080;
4872 cursor: pointer !important;
4878 this.setAttributeConfig('css', {
4879 value: attr.css || this._defaultCSS,
4884 * @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)
4885 * @default This HTML requires a few things if you are to override:
4886 <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>
4887 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
4892 <title>{TITLE}</title>
4893 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4904 <body onload="document.body._rteLoaded = true;">
4912 this.setAttributeConfig('html', {
4913 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>',
4918 * @attribute extracss
4919 * @description Extra user defined css to load after the default SimpleEditor CSS
4923 this.setAttributeConfig('extracss', {
4924 value: attr.extracss || '',
4929 * @attribute handleSubmit
4930 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
4931 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
4932 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
4936 this.setAttributeConfig('handleSubmit', {
4937 value: attr.handleSubmit || false,
4938 method: function(exec) {
4939 if (this.get('element').form) {
4940 if (!this._formButtons) {
4941 this._formButtons = [];
4944 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
4945 var i = this.get('element').form.getElementsByTagName('input');
4946 for (var s = 0; s < i.length; s++) {
4947 var type = i[s].getAttribute('type');
4948 if (type && (type.toLowerCase() == 'submit')) {
4949 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
4950 this._formButtons[this._formButtons.length] = i[s];
4954 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
4955 if (this._formButtons) {
4956 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
4963 * @attribute disabled
4964 * @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.
4965 All Toolbar buttons are also disabled so they cannot be used.
4970 this.setAttributeConfig('disabled', {
4972 method: function(disabled) {
4973 if (this._rendered) {
4974 this._disableEditor(disabled);
4980 * @description When save HTML is called, this element will be updated as well as the source of data.
4984 this.setAttributeConfig('saveEl', {
4985 value: this.get('element')
4988 * @config toolbar_cont
4989 * @description Internal config for the toolbars container
4993 this.setAttributeConfig('toolbar_cont', {
4998 * @attribute toolbar
4999 * @description The default toolbar config.
5002 this.setAttributeConfig('toolbar', {
5003 value: attr.toolbar || this._defaultToolbar,
5005 method: function(toolbar) {
5006 if (!toolbar.buttonType) {
5007 toolbar.buttonType = this._defaultToolbar.buttonType;
5009 this._defaultToolbar = toolbar;
5013 * @attribute animate
5014 * @description Should the editor animate window movements
5015 * @default false unless Animation is found, then true
5018 this.setAttributeConfig('animate', {
5019 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
5020 validator: function(value) {
5022 if (!YAHOO.util.Anim) {
5030 * @description A reference to the panel we are using for windows.
5034 this.setAttributeConfig('panel', {
5037 validator: function(value) {
5039 if (!YAHOO.widget.Overlay) {
5046 * @attribute focusAtStart
5047 * @description Should we focus the window when the content is ready?
5051 this.setAttributeConfig('focusAtStart', {
5052 value: attr.focusAtStart || false,
5054 method: function(fs) {
5056 this.on('editorContentLoaded', function() {
5058 setTimeout(function() {
5059 self.focus.call(self);
5060 self.editorDirty = false;
5067 * @attribute dompath
5068 * @description Toggle the display of the current Dom path below the editor
5072 this.setAttributeConfig('dompath', {
5073 value: attr.dompath || false,
5074 method: function(dompath) {
5075 if (dompath && !this.dompath) {
5076 this.dompath = document.createElement('DIV');
5077 this.dompath.id = this.get('id') + '_dompath';
5078 Dom.addClass(this.dompath, 'dompath');
5079 this.get('element_cont').get('firstChild').appendChild(this.dompath);
5080 if (this.get('iframe')) {
5081 this._writeDomPath();
5083 } else if (!dompath && this.dompath) {
5084 this.dompath.parentNode.removeChild(this.dompath);
5085 this.dompath = null;
5091 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
5092 * @default "semantic"
5095 this.setAttributeConfig('markup', {
5096 value: attr.markup || 'semantic',
5097 validator: function(markup) {
5098 switch (markup.toLowerCase()) {
5109 * @attribute removeLineBreaks
5110 * @description Should we remove linebreaks and extra spaces on cleanup
5114 this.setAttributeConfig('removeLineBreaks', {
5115 value: attr.removeLineBreaks || false,
5116 validator: YAHOO.lang.isBoolean
5121 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5122 * @type {Boolean/String}
5124 this.setAttributeConfig('drag', {
5126 value: attr.drag || false
5131 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5132 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5135 this.setAttributeConfig('resize', {
5137 value: attr.resize || false
5141 * @config filterWord
5142 * @description Attempt to filter out MS Word HTML from the Editor's output.
5145 this.setAttributeConfig('filterWord', {
5146 value: attr.filterWord || false,
5147 validator: YAHOO.lang.isBoolean
5153 * @method _getBlankImage
5154 * @description Retrieves the full url of the image to use as the blank image.
5155 * @return {String} The URL to the blank image
5157 _getBlankImage: function() {
5158 if (!this.DOMReady) {
5159 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5163 if (!this._blankImageLoaded) {
5164 if (YAHOO.widget.EditorInfo.blankImage) {
5165 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5166 this._blankImageLoaded = true;
5168 var div = document.createElement('div');
5169 div.style.position = 'absolute';
5170 div.style.top = '-9999px';
5171 div.style.left = '-9999px';
5172 div.className = this.CLASS_PREFIX + '-blankimage';
5173 document.body.appendChild(div);
5174 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5175 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5177 img = img.replace('app:/', '');
5178 this.set('blankimage', img);
5179 this._blankImageLoaded = true;
5180 div.parentNode.removeChild(div);
5181 YAHOO.widget.EditorInfo.blankImage = img;
5184 img = this.get('blankimage');
5190 * @method _handleAutoHeight
5191 * @description Handles resizing the editor's height based on the content
5193 _handleAutoHeight: function() {
5194 var doc = this._getDoc(),
5196 docEl = doc.documentElement;
5198 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5199 var newHeight = body.scrollHeight;
5200 if (this.browser.webkit) {
5201 newHeight = docEl.scrollHeight;
5203 if (newHeight < parseInt(this.get('height'), 10)) {
5204 newHeight = parseInt(this.get('height'), 10);
5206 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5207 var anim = this.get('animate');
5208 this.set('animate', false);
5209 this.set('height', newHeight + 'px');
5210 this.set('animate', anim);
5211 if (this.browser.ie) {
5212 //Internet Explorer needs this
5213 this.get('iframe').setStyle('height', '99%');
5214 this.get('iframe').setStyle('zoom', '1');
5216 window.setTimeout(function() {
5217 self.get('iframe').setStyle('height', '100%');
5224 * @property _formButtons
5225 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5231 * @property _formButtonClicked
5232 * @description The form button that was clicked to submit the form.
5235 _formButtonClicked: null,
5238 * @method _handleFormButtonClick
5239 * @description The click listener assigned to each submit button in the Editor's parent form.
5240 * @param {Event} ev The click event
5242 _handleFormButtonClick: function(ev) {
5243 var tar = Event.getTarget(ev);
5244 this._formButtonClicked = tar;
5248 * @method _handleFormSubmit
5249 * @description Handles the form submission.
5250 * @param {Object} ev The Form Submit Event
5252 _handleFormSubmit: function(ev) {
5255 var form = this.get('element').form,
5256 tar = this._formButtonClicked || false;
5258 Event.removeListener(form, 'submit', this._handleFormSubmit);
5259 if (YAHOO.env.ua.ie) {
5260 //form.fireEvent("onsubmit");
5261 if (tar && !tar.disabled) {
5264 } else { // Gecko, Opera, and Safari
5265 if (tar && !tar.disabled) {
5268 var oEvent = document.createEvent("HTMLEvents");
5269 oEvent.initEvent("submit", true, true);
5270 form.dispatchEvent(oEvent);
5271 if (YAHOO.env.ua.webkit) {
5272 if (YAHOO.lang.isFunction(form.submit)) {
5278 //Removed this, not need since removing Safari 2.x
5279 //Event.stopEvent(ev);
5283 * @method _handleFontSize
5284 * @description Handles the font size button in the toolbar.
5285 * @param {Object} o Object returned from Toolbar's buttonClick Event
5287 _handleFontSize: function(o) {
5288 var button = this.toolbar.getButtonById(o.button.id);
5289 var value = button.get('label') + 'px';
5290 this.execCommand('fontsize', value);
5295 * @description Handles the colorpicker buttons in the toolbar.
5296 * @param {Object} o Object returned from Toolbar's buttonClick Event
5298 _handleColorPicker: function(o) {
5300 var value = '#' + o.color;
5301 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5302 this.execCommand(cmd, value);
5307 * @method _handleAlign
5308 * @description Handles the alignment buttons in the toolbar.
5309 * @param {Object} o Object returned from Toolbar's buttonClick Event
5311 _handleAlign: function(o) {
5313 for (var i = 0; i < o.button.menu.length; i++) {
5314 if (o.button.menu[i].value == o.button.value) {
5315 cmd = o.button.menu[i].value;
5318 var value = this._getSelection();
5320 this.execCommand(cmd, value);
5325 * @method _handleAfterNodeChange
5326 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5328 _handleAfterNodeChange: function() {
5329 var path = this._getDomPath(),
5334 fn_button = this.toolbar.getButtonByValue('fontname'),
5335 fs_button = this.toolbar.getButtonByValue('fontsize'),
5336 hd_button = this.toolbar.getButtonByValue('heading');
5338 for (var i = 0; i < path.length; i++) {
5341 var tag = elm.tagName.toLowerCase();
5344 if (elm.getAttribute('tag')) {
5345 tag = elm.getAttribute('tag');
5348 family = elm.getAttribute('face');
5349 if (Dom.getStyle(elm, 'font-family')) {
5350 family = Dom.getStyle(elm, 'font-family');
5352 family = family.replace(/'/g, '');
5355 if (tag.substring(0, 1) == 'h') {
5357 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5358 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5359 hd_button.set('label', hd_button._configs.menu.value[h].text);
5362 this._updateMenuChecked('heading', tag);
5368 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5369 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5371 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5375 family = fn_button._configs.label._initialConfig.value;
5377 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5378 if (fn_button.get('label') != familyLabel) {
5379 fn_button.set('label', familyLabel);
5380 this._updateMenuChecked('fontname', family);
5385 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5386 if ((fontsize === null) || isNaN(fontsize)) {
5387 fontsize = fs_button._configs.label._initialConfig.value;
5389 fs_button.set('label', ''+fontsize);
5392 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5393 this.toolbar.enableButton(fn_button);
5394 this.toolbar.enableButton(fs_button);
5395 this.toolbar.enableButton('forecolor');
5396 this.toolbar.enableButton('backcolor');
5398 if (this._isElement(elm, 'img')) {
5399 if (YAHOO.widget.Overlay) {
5400 this.toolbar.enableButton('createlink');
5403 if (this._hasParent(elm, 'blockquote')) {
5404 this.toolbar.selectButton('indent');
5405 this.toolbar.disableButton('indent');
5406 this.toolbar.enableButton('outdent');
5408 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5409 this.toolbar.disableButton('indent');
5411 this._lastButton = null;
5416 * @method _handleInsertImageClick
5417 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5419 _handleInsertImageClick: function() {
5420 if (this.get('limitCommands')) {
5421 if (!this.toolbar.getButtonByValue('insertimage')) {
5426 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5427 var _handleAEC = function() {
5428 var el = this.currentElement[0],
5431 el = this._getSelectedElement();
5434 if (el.getAttribute('src')) {
5435 src = el.getAttribute('src', 2);
5436 if (src.indexOf(this.get('blankimage')) != -1) {
5437 src = this.STR_IMAGE_HERE;
5441 var str = prompt(this.STR_IMAGE_URL + ': ', src);
5442 if ((str !== '') && (str !== null)) {
5443 el.setAttribute('src', str);
5444 } else if (str === '') {
5445 el.parentNode.removeChild(el);
5446 this.currentElement = [];
5448 } else if ((str === null)) {
5449 src = el.getAttribute('src', 2);
5450 if (src.indexOf(this.get('blankimage')) != -1) {
5451 el.parentNode.removeChild(el);
5452 this.currentElement = [];
5457 this.toolbar.set('disabled', false);
5458 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5460 this.on('afterExecCommand', _handleAEC, this, true);
5464 * @method _handleInsertImageWindowClose
5465 * @description Handles the closing of the Image Properties Window.
5467 _handleInsertImageWindowClose: function() {
5472 * @method _isLocalFile
5473 * @param {String} url THe url/string to check
5474 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5476 _isLocalFile: function(url) {
5477 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5484 * @method _handleCreateLinkClick
5485 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5487 _handleCreateLinkClick: function() {
5488 if (this.get('limitCommands')) {
5489 if (!this.toolbar.getButtonByValue('createlink')) {
5494 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5496 var _handleAEC = function() {
5497 var el = this.currentElement[0],
5501 if (el.getAttribute('href', 2) !== null) {
5502 url = el.getAttribute('href', 2);
5505 var str = prompt(this.STR_LINK_URL + ': ', url);
5506 if ((str !== '') && (str !== null)) {
5508 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5509 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5510 //Found an @ sign, prefix with mailto:
5511 urlValue = 'mailto:' + urlValue;
5513 /* :// not found adding */
5514 if (urlValue.substring(0, 1) != '#') {
5515 //urlValue = 'http:/'+'/' + urlValue;
5519 el.setAttribute('href', urlValue);
5520 } else if (str !== null) {
5521 var _span = this._getDoc().createElement('span');
5522 _span.innerHTML = el.innerHTML;
5523 Dom.addClass(_span, 'yui-non');
5524 el.parentNode.replaceChild(_span, el);
5527 this.toolbar.set('disabled', false);
5528 this.unsubscribe('afterExecCommand', _handleAEC, this, true);
5530 this.on('afterExecCommand', _handleAEC, this);
5535 * @method _handleCreateLinkWindowClose
5536 * @description Handles the closing of the Link Properties Window.
5538 _handleCreateLinkWindowClose: function() {
5540 this.currentElement = [];
5544 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5546 render: function() {
5547 if (this._rendered) {
5550 if (!this.DOMReady) {
5551 this._queue[this._queue.length] = ['render', arguments];
5554 if (this.get('element')) {
5555 if (this.get('element').tagName) {
5556 this._textarea = true;
5557 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5558 this._textarea = false;
5566 this._rendered = true;
5568 window.setTimeout(function() {
5569 self._render.call(self);
5575 * @description Causes the toolbar and the editor to render and replace the textarea.
5577 _render: function() {
5579 this.set('textarea', this.get('element'));
5581 this.get('element_cont').setStyle('display', 'none');
5582 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5584 this.set('iframe', this._createIframe());
5586 window.setTimeout(function() {
5587 self._setInitialContent.call(self);
5590 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5592 if (this.get('disabled')) {
5593 this._disableEditor(true);
5596 var tbarConf = this.get('toolbar');
5597 //Create Toolbar instance
5598 if (tbarConf instanceof Toolbar) {
5599 this.toolbar = tbarConf;
5600 //Set the toolbar to disabled until content is loaded
5601 this.toolbar.set('disabled', true);
5603 //Set the toolbar to disabled until content is loaded
5604 tbarConf.disabled = true;
5605 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5608 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5611 this.toolbar.on('toolbarCollapsed', function() {
5612 if (this.currentWindow) {
5616 this.toolbar.on('toolbarExpanded', function() {
5617 if (this.currentWindow) {
5621 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5623 this.toolbar.on('colorPickerClicked', function(o) {
5624 this._handleColorPicker(o);
5625 return false; //Stop the buttonClick event
5628 this.toolbar.on('alignClick', this._handleAlign, this, true);
5629 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5630 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5631 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5632 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5633 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5636 //Replace Textarea with editable area
5637 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5640 this.setStyle('visibility', 'hidden');
5641 this.setStyle('position', 'absolute');
5642 this.setStyle('top', '-9999px');
5643 this.setStyle('left', '-9999px');
5644 this.get('element_cont').appendChild(this.get('element'));
5645 this.get('element_cont').setStyle('display', 'block');
5648 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5649 this.get('iframe').addClass(this.CLASS_EDITABLE);
5651 //Set height and width of editor container
5652 this.get('element_cont').setStyle('width', this.get('width'));
5653 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5655 this.get('iframe').setStyle('width', '100%'); //WIDTH
5656 this.get('iframe').setStyle('height', '100%');
5660 window.setTimeout(function() {
5661 self._setupAfterElement.call(self);
5663 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5666 * @method execCommand
5667 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5668 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5669 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5671 execCommand: function(action, value) {
5672 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5673 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5674 this.STOP_EXEC_COMMAND = false;
5677 this._lastCommand = action;
5678 this._setMarkupType(action);
5679 if (this.browser.ie) {
5680 this._getWindow().focus();
5684 if (this.get('limitCommands')) {
5685 if (!this.toolbar.getButtonByValue(action)) {
5690 this.editorDirty = true;
5692 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5693 var retValue = this['cmd_' + action.toLowerCase()](value);
5696 action = retValue[1];
5699 value = retValue[2];
5704 this._getDoc().execCommand(action, false, value);
5709 this.on('afterExecCommand', function() {
5710 this.unsubscribeAll('afterExecCommand');
5713 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5716 /* {{{ Command Overrides */
5720 * @param value Value passed from the execCommand method
5721 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used.
5723 cmd_bold: function(value) {
5724 if (!this.browser.webkit) {
5725 var el = this._getSelectedElement();
5726 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5727 if (el.style.fontWeight == 'bold') {
5728 el.style.fontWeight = '';
5729 var b = this._getDoc().createElement('b'),
5730 par = el.parentNode;
5731 par.replaceChild(b, el);
5739 * @method cmd_italic
5740 * @param value Value passed from the execCommand method
5741 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used.
5744 cmd_italic: function(value) {
5745 if (!this.browser.webkit) {
5746 var el = this._getSelectedElement();
5747 if (el && this._isElement(el, 'span') && this._hasSelection()) {
5748 if (el.style.fontStyle == 'italic') {
5749 el.style.fontStyle = '';
5750 var i = this._getDoc().createElement('i'),
5751 par = el.parentNode;
5752 par.replaceChild(i, el);
5762 * @method cmd_underline
5763 * @param value Value passed from the execCommand method
5764 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5766 cmd_underline: function(value) {
5767 if (!this.browser.webkit) {
5768 var el = this._getSelectedElement();
5769 if (el && this._isElement(el, 'span')) {
5770 if (el.style.textDecoration == 'underline') {
5771 el.style.textDecoration = 'none';
5773 el.style.textDecoration = 'underline';
5781 * @method cmd_backcolor
5782 * @param value Value passed from the execCommand method
5783 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5785 cmd_backcolor: function(value) {
5787 el = this._getSelectedElement(),
5788 action = 'backcolor';
5790 if (this.browser.gecko || this.browser.opera) {
5791 this._setEditorStyle(true);
5792 action = 'hilitecolor';
5795 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5796 el.style.backgroundColor = value;
5797 this._selectNode(el);
5800 if (this.get('insert')) {
5801 el = this._createInsertElement({ backgroundColor: value });
5803 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily });
5804 this._selectNode(this.currentElement[0]);
5809 return [exec, action];
5812 * @method cmd_forecolor
5813 * @param value Value passed from the execCommand method
5814 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
5816 cmd_forecolor: function(value) {
5818 el = this._getSelectedElement();
5820 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5821 Dom.setStyle(el, 'color', value);
5822 this._selectNode(el);
5825 if (this.get('insert')) {
5826 el = this._createInsertElement({ color: value });
5828 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor });
5829 this._selectNode(this.currentElement[0]);
5836 * @method cmd_unlink
5837 * @param value Value passed from the execCommand method
5838 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
5840 cmd_unlink: function(value) {
5841 this._swapEl(this.currentElement[0], 'span', function(el) {
5842 el.className = 'yui-non';
5847 * @method cmd_createlink
5848 * @param value Value passed from the execCommand method
5849 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
5851 cmd_createlink: function(value) {
5852 var el = this._getSelectedElement(), _a = null;
5853 if (this._hasParent(el, 'a')) {
5854 this.currentElement[0] = this._hasParent(el, 'a');
5855 } else if (this._isElement(el, 'li')) {
5856 _a = this._getDoc().createElement('a');
5857 _a.innerHTML = el.innerHTML;
5860 this.currentElement[0] = _a;
5861 } else if (!this._isElement(el, 'a')) {
5862 this._createCurrentElement('a');
5863 _a = this._swapEl(this.currentElement[0], 'a');
5864 this.currentElement[0] = _a;
5866 this.currentElement[0] = el;
5871 * @method cmd_insertimage
5872 * @param value Value passed from the execCommand method
5873 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
5875 cmd_insertimage: function(value) {
5876 var exec = true, _img = null, action = 'insertimage',
5877 el = this._getSelectedElement();
5880 value = this.get('blankimage');
5884 * @knownissue Safari Cursor Position
5885 * @browser Safari 2.x
5886 * @description The issue here is that we have no way of knowing where the cursor position is
5887 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5890 if (this._isElement(el, 'img')) {
5891 this.currentElement[0] = el;
5894 if (this._getDoc().queryCommandEnabled(action)) {
5895 this._getDoc().execCommand('insertimage', false, value);
5896 var imgs = this._getDoc().getElementsByTagName('img');
5897 for (var i = 0; i < imgs.length; i++) {
5898 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
5899 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
5900 this.currentElement[0] = imgs[i];
5905 if (el == this._getDoc().body) {
5906 _img = this._getDoc().createElement('img');
5907 _img.setAttribute('src', value);
5908 YAHOO.util.Dom.addClass(_img, 'yui-img');
5909 this._getDoc().body.appendChild(_img);
5911 this._createCurrentElement('img');
5912 _img = this._getDoc().createElement('img');
5913 _img.setAttribute('src', value);
5914 YAHOO.util.Dom.addClass(_img, 'yui-img');
5915 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
5917 this.currentElement[0] = _img;
5924 * @method cmd_inserthtml
5925 * @param value Value passed from the execCommand method
5926 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
5928 cmd_inserthtml: function(value) {
5929 var exec = true, action = 'inserthtml', _span = null, _range = null;
5931 * @knownissue Safari cursor position
5932 * @browser Safari 2.x
5933 * @description The issue here is that we have no way of knowing where the cursor position is
5934 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5936 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
5937 this._createCurrentElement('img');
5938 _span = this._getDoc().createElement('span');
5939 _span.innerHTML = value;
5940 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
5942 } else if (this.browser.ie) {
5943 _range = this._getRange();
5945 _range.item(0).outerHTML = value;
5947 _range.pasteHTML(value);
5955 * @param tag The tag of the list you want to create (eg, ul or ol)
5956 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
5958 cmd_list: function(tag) {
5959 var exec = true, list = null, li = 0, el = null, str = '',
5960 selEl = this._getSelectedElement(), action = 'insertorderedlist';
5962 action = 'insertunorderedlist';
5965 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
5966 * @browser Safari 2.x
5967 * The issue with this workaround is that when applied to a set of text
5968 * that has BR's in it, Safari may or may not pick up the individual items as
5969 * list items. This is fixed in WebKit (Safari 3)
5970 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
5972 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
5973 if (this.browser.webkit) {
5974 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
5975 el = selEl.parentNode;
5976 list = this._getDoc().createElement('span');
5977 YAHOO.util.Dom.addClass(list, 'yui-non');
5979 var lis = el.getElementsByTagName('li');
5980 for (li = 0; li < lis.length; li++) {
5981 str += '<div>' + lis[li].innerHTML + '</div>';
5983 list.innerHTML = str;
5984 this.currentElement[0] = el;
5985 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
5987 this._createCurrentElement(tag.toLowerCase());
5988 list = this._getDoc().createElement(tag);
5989 for (li = 0; li < this.currentElement.length; li++) {
5990 var newli = this._getDoc().createElement('li');
5991 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
5992 list.appendChild(newli);
5994 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
5998 var items = list.firstChild.innerHTML.split('<br>');
5999 if (items.length > 0) {
6000 list.innerHTML = '';
6001 for (var i = 0; i < items.length; i++) {
6002 var item = this._getDoc().createElement('li');
6003 item.innerHTML = items[i];
6004 list.appendChild(item);
6008 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
6009 this.currentElement[0] = list;
6010 var _h = this.currentElement[0].firstChild;
6011 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
6012 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
6016 el = this._getSelectedElement();
6017 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..
6018 if (this.browser.ie) {
6019 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
6020 el = el.getElementsByTagName('li')[0];
6023 var lis2 = el.parentNode.getElementsByTagName('li');
6024 for (var j = 0; j < lis2.length; j++) {
6025 str += lis2[j].innerHTML + '<br>';
6027 var newEl = this._getDoc().createElement('span');
6028 newEl.innerHTML = str;
6029 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
6032 this._getDoc().execCommand(action, '', el.parentNode);
6037 if (this.browser.opera) {
6039 window.setTimeout(function() {
6040 var liso = self._getDoc().getElementsByTagName('li');
6041 for (var i = 0; i < liso.length; i++) {
6042 if (liso[i].innerHTML.toLowerCase() == '<br>') {
6043 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
6048 if (this.browser.ie && exec) {
6050 if (this._getRange().html) {
6051 html = '<li>' + this._getRange().html+ '</li>';
6053 var t = this._getRange().text.split('\n');
6056 for (var ie = 0; ie < t.length; ie++) {
6057 html += '<li>' + t[ie] + '</li>';
6060 var txt = this._getRange().text;
6062 html = '<li id="new_list_item">' + txt + '</li>';
6064 html = '<li>' + txt + '</li>';
6068 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
6069 var new_item = this._getDoc().getElementById('new_list_item');
6071 var range = this._getDoc().body.createTextRange();
6072 range.moveToElementText(new_item);
6073 range.collapse(false);
6083 * @method cmd_insertorderedlist
6084 * @param value Value passed from the execCommand method
6085 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
6087 cmd_insertorderedlist: function(value) {
6088 return [this.cmd_list('ol')];
6091 * @method cmd_insertunorderedlist
6092 * @param value Value passed from the execCommand method
6093 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
6095 cmd_insertunorderedlist: function(value) {
6096 return [this.cmd_list('ul')];
6099 * @method cmd_fontname
6100 * @param value Value passed from the execCommand method
6101 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
6103 cmd_fontname: function(value) {
6105 selEl = this._getSelectedElement();
6107 this.currentFont = value;
6108 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
6109 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
6111 } else if (this.get('insert') && !this._hasSelection()) {
6112 var el = this._createInsertElement({ fontFamily: value });
6118 * @method cmd_fontsize
6119 * @param value Value passed from the execCommand method
6120 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
6122 cmd_fontsize: function(value) {
6123 var el = null, go = true;
6124 el = this._getSelectedElement();
6125 if (this.browser.webkit) {
6126 if (this.currentElement[0]) {
6127 if (el == this.currentElement[0]) {
6129 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6130 this._selectNode(el);
6131 this.currentElement[0] = el;
6136 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) {
6137 el = this._getSelectedElement();
6138 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
6139 if (this.get('insert') && this.browser.ie) {
6140 var r = this._getRange();
6144 this._selectNode(el);
6146 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
6147 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
6149 if (this.get('insert') && !this._hasSelection()) {
6150 el = this._createInsertElement({ fontSize: value });
6151 this.currentElement[0] = el;
6152 this._selectNode(this.currentElement[0]);
6154 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor });
6155 this._selectNode(this.currentElement[0]);
6165 * @param {HTMLElement} el The element to swap with
6166 * @param {String} tagName The tagname of the element that you wish to create
6167 * @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.
6168 * @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.
6170 _swapEl: function(el, tagName, callback) {
6171 var _el = this._getDoc().createElement(tagName);
6173 _el.innerHTML = el.innerHTML;
6175 if (typeof callback == 'function') {
6176 callback.call(this, _el);
6179 el.parentNode.replaceChild(_el, el);
6185 * @method _createInsertElement
6186 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
6187 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
6188 * @return {HTMLElement}
6190 _createInsertElement: function(css) {
6191 this._createCurrentElement('span', css);
6192 var el = this.currentElement[0];
6193 if (this.browser.webkit) {
6194 //Little Safari Hackery here..
6195 el.innerHTML = '<span class="yui-non"> </span>';
6197 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6198 } else if (this.browser.ie || this.browser.opera) {
6199 el.innerHTML = ' ';
6202 this._selectNode(el, true);
6207 * @method _createCurrentElement
6208 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6209 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6210 * @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.
6211 * 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
6212 * <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.
6214 _createCurrentElement: function(tagName, tagStyle) {
6215 tagName = ((tagName) ? tagName : 'a');
6218 _doc = this._getDoc();
6220 if (this.currentFont) {
6224 tagStyle.fontFamily = this.currentFont;
6225 this.currentFont = null;
6227 this.currentElement = [];
6229 var _elCreate = function(tagName, tagStyle) {
6231 tagName = ((tagName) ? tagName : 'span');
6232 tagName = tagName.toLowerCase();
6240 el = _doc.createElement(tagName);
6243 el = _doc.createElement(tagName);
6244 if (tagName === 'span') {
6245 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6246 YAHOO.util.Dom.addClass(el, 'yui-tag');
6247 el.setAttribute('tag', tagName);
6250 for (var k in tagStyle) {
6251 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6252 el.style[k] = tagStyle[k];
6260 if (!this._hasSelection()) {
6261 if (this._getDoc().queryCommandEnabled('insertimage')) {
6262 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6263 var imgs = this._getDoc().getElementsByTagName('img');
6264 for (var j = 0; j < imgs.length; j++) {
6265 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6266 el = _elCreate(tagName, tagStyle);
6267 imgs[j].parentNode.replaceChild(el, imgs[j]);
6268 this.currentElement[this.currentElement.length] = el;
6272 if (this.currentEvent) {
6273 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6276 tar = this._getDoc().body;
6281 * @knownissue Safari Cursor Position
6282 * @browser Safari 2.x
6283 * @description The issue here is that we have no way of knowing where the cursor position is
6284 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6286 el = _elCreate(tagName, tagStyle);
6287 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6288 if (this._isElement(tar, 'html')) {
6289 tar = this._getDoc().body;
6291 tar.appendChild(el);
6292 } else if (tar.nextSibling) {
6293 tar.parentNode.insertBefore(el, tar.nextSibling);
6295 tar.parentNode.appendChild(el);
6297 //this.currentElement = el;
6298 this.currentElement[this.currentElement.length] = el;
6299 this.currentEvent = null;
6300 if (this.browser.webkit) {
6301 //Force Safari to focus the new element
6302 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6303 if (this.browser.webkit3) {
6304 this._getSelection().collapseToStart();
6306 this._getSelection().collapse(true);
6311 //Force CSS Styling for this action...
6312 this._setEditorStyle(true);
6313 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6314 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6316 if (!this._isElement(this._getSelectedElement(), 'body')) {
6317 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6318 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6320 for (var _els = 0; _els < __els.length; _els++) {
6321 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6322 for (var e = 0; e < _tmp1.length; e++) {
6323 _tmp[_tmp.length] = _tmp1[e];
6328 for (var i = 0; i < _tmp.length; i++) {
6329 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6330 if (tagName !== 'span') {
6331 el = _elCreate(tagName, tagStyle);
6333 el = _elCreate(_tmp[i].tagName, tagStyle);
6335 el.innerHTML = _tmp[i].innerHTML;
6336 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6337 var fc = _tmp[i].getElementsByTagName('li')[0];
6338 _tmp[i].style.fontFamily = 'inherit';
6339 fc.style.fontFamily = 'inherit';
6340 el.innerHTML = fc.innerHTML;
6343 this.currentElement[this.currentElement.length] = el;
6344 } else if (this._isElement(_tmp[i], 'li')) {
6345 _tmp[i].innerHTML = '';
6346 _tmp[i].appendChild(el);
6347 _tmp[i].style.fontFamily = 'inherit';
6348 this.currentElement[this.currentElement.length] = el;
6350 if (_tmp[i].parentNode) {
6351 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6352 this.currentElement[this.currentElement.length] = el;
6353 this.currentEvent = null;
6354 if (this.browser.webkit) {
6355 //Force Safari to focus the new element
6356 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6357 if (this.browser.webkit3) {
6358 this._getSelection().collapseToStart();
6360 this._getSelection().collapse(true);
6363 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6364 this._getSelection().empty();
6366 if (this.browser.gecko) {
6367 this._getSelection().collapseToStart();
6373 var len = this.currentElement.length;
6374 for (var o = 0; o < len; o++) {
6375 if ((o + 1) != len) { //Skip the last one in the list
6376 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6377 if (this._isElement(this.currentElement[o], 'br')) {
6378 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6387 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6390 saveHTML: function() {
6391 var html = this.cleanHTML();
6392 if (this._textarea) {
6393 this.get('element').value = html;
6395 this.get('element').innerHTML = html;
6397 if (this.get('saveEl') !== this.get('element')) {
6398 var out = this.get('saveEl');
6399 if (Lang.isString(out)) {
6403 if (out.tagName.toLowerCase() === 'textarea') {
6406 out.innerHTML = html;
6413 * @method setEditorHTML
6414 * @param {String} incomingHTML The html content to load into the editor
6415 * @description Loads HTML into the editors body
6417 setEditorHTML: function(incomingHTML) {
6418 var html = this._cleanIncomingHTML(incomingHTML);
6419 this._getDoc().body.innerHTML = html;
6423 * @method getEditorHTML
6424 * @description Gets the unprocessed/unfiltered HTML from the editor
6426 getEditorHTML: function() {
6427 var b = this._getDoc().body;
6431 return this._getDoc().body.innerHTML;
6435 * @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.
6438 if (this.browser.gecko) {
6439 this._setDesignMode('on');
6442 if (this.browser.webkit) {
6444 window.setTimeout(function() {
6445 self._setInitialContent.call(self);
6448 //Adding this will close all other Editor window's when showing this one.
6449 if (this.currentWindow) {
6452 //Put the iframe back in place
6453 this.get('iframe').setStyle('position', 'static');
6454 this.get('iframe').setStyle('left', '');
6458 * @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.
6461 //Adding this will close all other Editor window's.
6462 if (this.currentWindow) {
6465 if (this._fixNodesTimer) {
6466 clearTimeout(this._fixNodesTimer);
6467 this._fixNodesTimer = null;
6469 if (this._nodeChangeTimer) {
6470 clearTimeout(this._nodeChangeTimer);
6471 this._nodeChangeTimer = null;
6473 this._lastNodeChange = 0;
6474 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6475 this.get('iframe').setStyle('position', 'absolute');
6476 this.get('iframe').setStyle('left', '-9999px');
6479 * @method _cleanIncomingHTML
6480 * @param {String} html The unfiltered HTML
6481 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6482 * @return {String} The filtered HTML
6484 _cleanIncomingHTML: function(html) {
6485 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6486 html = html.replace(/<\/strong>/gi, '</b>');
6488 //replace embed before em check
6489 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6490 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6492 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6493 html = html.replace(/<\/em>/gi, '</i>');
6494 html = html.replace(/_moz_dirty=""/gi, '');
6496 //Put embed tags back in..
6497 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6498 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6499 if (this.get('plainText')) {
6500 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6501 html = html.replace(/ /gi, ' '); //Replace all double spaces
6502 html = html.replace(/\t/gi, ' '); //Replace all tabs
6504 //Removing Script Tags from the Editor
6505 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6506 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6507 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6508 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6509 //Replace the line feeds
6510 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6512 //Remove Bad HTML elements (used to be script nodes)
6513 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6514 //Replace the lines feeds
6515 html = html.replace(/<YUI_LF>/g, '\n');
6520 * @param {String} html The unfiltered HTML
6521 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6522 * @return {String} The filtered HTML
6524 cleanHTML: function(html) {
6525 //Start Filtering Output
6528 html = this.getEditorHTML();
6530 var markup = this.get('markup');
6531 //Make some backups...
6532 html = this.pre_filter_linebreaks(html, markup);
6535 html = this.filter_msword(html);
6537 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6538 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6540 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6541 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6543 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6544 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6545 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6546 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6548 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6549 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6551 //Convert b and i tags to strong and em tags
6552 if ((markup == 'semantic') || (markup == 'xhtml')) {
6553 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6554 html = html.replace(/<\/i>/gi, '</em>');
6555 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6556 html = html.replace(/<\/b>/gi, '</strong>');
6559 html = html.replace(/_moz_dirty=""/gi, '');
6561 //normalize strikethrough
6562 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"');
6563 html = html.replace(/\/strike>/gi, '/span>');
6567 if (this.browser.ie) {
6568 html = html.replace(/text-decoration/gi, 'text-decoration');
6569 html = html.replace(/font-weight/gi, 'font-weight');
6570 html = html.replace(/_width="([^>]*)"/gi, '');
6571 html = html.replace(/_height="([^>]*)"/gi, '');
6572 //Cleanup Image URL's
6573 var url = this._baseHREF.replace(/\//gi, '\\/'),
6574 re = new RegExp('src="' + url, 'gi');
6575 html = html.replace(re, 'src="');
6577 html = html.replace(/<font/gi, '<font');
6578 html = html.replace(/<\/font>/gi, '</font>');
6579 html = html.replace(/<span/gi, '<span');
6580 html = html.replace(/<\/span>/gi, '</span>');
6581 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6582 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6583 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6584 if (this.browser.webkit) {
6585 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6586 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6588 html = html.replace(/\/u>/gi, '/span>');
6589 if (markup == 'css') {
6590 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6591 html = html.replace(/<\/em>/gi, '</i>');
6592 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6593 html = html.replace(/<\/strong>/gi, '</b>');
6594 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6595 html = html.replace(/\/b>/gi, '/span>');
6596 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6597 html = html.replace(/\/i>/gi, '/span>');
6599 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6601 html = html.replace(/<u/gi, '<u');
6602 html = html.replace(/\/u>/gi, '/u>');
6604 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6605 html = html.replace(/\/ol>/gi, '/ol>');
6606 html = html.replace(/<li/gi, '<li');
6607 html = html.replace(/\/li>/gi, '/li>');
6608 html = this.filter_safari(html);
6610 html = this.filter_internals(html);
6612 html = this.filter_all_rgb(html);
6614 //Replace our backups with the real thing
6615 html = this.post_filter_linebreaks(html, markup);
6617 if (markup == 'xhtml') {
6618 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6619 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6621 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6622 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6624 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6625 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6627 html = this.filter_invalid_lists(html);
6629 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6630 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6632 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6633 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6635 //This should fix &s in URL's
6636 html = html.replace(/ & /gi, 'YUI_AMP');
6637 html = html.replace(/&/gi, '&');
6638 html = html.replace(/YUI_AMP/gi, ' & ');
6640 //Trim the output, removing whitespace from the beginning and end
6641 html = YAHOO.lang.trim(html);
6643 if (this.get('removeLineBreaks')) {
6644 html = html.replace(/\n/g, '').replace(/\r/g, '');
6645 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6649 if (html.substring(0, 6).toLowerCase() == '<span>') {
6650 html = html.substring(6);
6652 if (html.substring(html.length - 7, html.length).toLowerCase() == '</span>') {
6653 html = html.substring(0, html.length - 7);
6657 for (var v in this.invalidHTML) {
6658 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6659 if (Lang.isObject(v) && v.keepContents) {
6660 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6662 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6667 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6672 * @method filter_msword
6673 * @param String html The HTML string to filter
6674 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config
6676 filter_msword: function(html) {
6677 if (!this.get('filterWord')) {
6680 //Remove the ms o: tags
6681 html = html.replace(/<o:p>\s*<\/o:p>/g, '');
6682 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' ');
6684 //Remove the ms w: tags
6685 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
6687 //Remove mso-? styles.
6688 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '');
6690 //Remove more bogus MS styles.
6691 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '');
6692 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"");
6693 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '');
6694 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"");
6695 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"");
6696 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" );
6697 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '');
6698 html = html.replace( /\s*tab-stops:[^"]*/gi, '');
6700 //Remove XML declarations
6701 html = html.replace(/<\\?\?xml[^>]*>/gi, '');
6704 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");
6706 //Remove language tags
6707 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3");
6709 //Remove onmouseover and onmouseout events (from MS Word comments effect)
6710 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3");
6711 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3");
6716 * @method filter_invalid_lists
6717 * @param String html The HTML string to filter
6718 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6720 filter_invalid_lists: function(html) {
6721 html = html.replace(/<\/li>\n/gi, '</li>');
6723 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6724 html = html.replace(/<\/ol>/gi, '</ol></li>');
6725 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>");
6727 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6728 html = html.replace(/<\/ul>/gi, '</ul></li>');
6729 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>");
6731 html = html.replace(/<\/li>/gi, "</li>");
6732 html = html.replace(/<\/ol>/gi, "</ol>");
6733 html = html.replace(/<ol>/gi, "<ol>");
6734 html = html.replace(/<ul>/gi, "<ul>");
6738 * @method filter_safari
6739 * @param String html The HTML string to filter
6740 * @description Filters strings specific to Safari
6743 filter_safari: function(html) {
6744 if (this.browser.webkit) {
6745 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6746 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
6747 html = html.replace(/Apple-style-span/gi, '');
6748 html = html.replace(/style="line-height: normal;"/gi, '');
6749 html = html.replace(/yui-wk-div/gi, '');
6750 html = html.replace(/yui-wk-p/gi, '');
6754 html = html.replace(/<li><\/li>/gi, '');
6755 html = html.replace(/<li> <\/li>/gi, '');
6756 html = html.replace(/<li> <\/li>/gi, '');
6757 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6758 if (this.get('ptags')) {
6759 html = html.replace(/<div([^>]*)>/g, '<p$1>');
6760 html = html.replace(/<\/div>/gi, '</p>');
6762 html = html.replace(/<div>/gi, '<br>');
6763 html = html.replace(/<\/div>/gi, '');
6769 * @method filter_internals
6770 * @param String html The HTML string to filter
6771 * @description Filters internal RTE strings and bogus attrs we don't want
6774 filter_internals: function(html) {
6775 html = html.replace(/\r/g, '');
6776 //Fix stuff we don't want
6777 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6779 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6781 html = html.replace(/yui-tag-span/gi, '');
6782 html = html.replace(/yui-tag/gi, '');
6783 html = html.replace(/yui-non/gi, '');
6784 html = html.replace(/yui-img/gi, '');
6785 html = html.replace(/ tag="span"/gi, '');
6786 html = html.replace(/ class=""/gi, '');
6787 html = html.replace(/ style=""/gi, '');
6788 html = html.replace(/ class=" "/gi, '');
6789 html = html.replace(/ class=" "/gi, '');
6790 html = html.replace(/ target=""/gi, '');
6791 html = html.replace(/ title=""/gi, '');
6793 if (this.browser.ie) {
6794 html = html.replace(/ class= /gi, '');
6795 html = html.replace(/ class= >/gi, '');
6801 * @method filter_all_rgb
6802 * @param String str The HTML string to filter
6803 * @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"
6806 filter_all_rgb: function(str) {
6807 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
6808 var arr = str.match(exp);
6809 if (Lang.isArray(arr)) {
6810 for (var i = 0; i < arr.length; i++) {
6811 var color = this.filter_rgb(arr[i]);
6812 str = str.replace(arr[i].toString(), color);
6819 * @method filter_rgb
6820 * @param String css The CSS string containing rgb(#,#,#);
6821 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
6824 filter_rgb: function(css) {
6825 if (css.toLowerCase().indexOf('rgb') != -1) {
6826 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
6827 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
6829 if (rgb.length == 5) {
6830 var r = parseInt(rgb[1], 10).toString(16);
6831 var g = parseInt(rgb[2], 10).toString(16);
6832 var b = parseInt(rgb[3], 10).toString(16);
6834 r = r.length == 1 ? '0' + r : r;
6835 g = g.length == 1 ? '0' + g : g;
6836 b = b.length == 1 ? '0' + b : b;
6838 css = "#" + r + g + b;
6844 * @method pre_filter_linebreaks
6845 * @param String html The HTML to filter
6846 * @param String markup The markup type to filter to
6847 * @description HTML Pre Filter
6850 pre_filter_linebreaks: function(html, markup) {
6851 if (this.browser.webkit) {
6852 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
6853 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
6855 html = html.replace(/<br>/gi, '<YUI_BR>');
6856 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
6857 html = html.replace(/<br\/>/gi, '<YUI_BR>');
6858 html = html.replace(/<br \/>/gi, '<YUI_BR>');
6859 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
6860 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
6861 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
6862 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
6864 html = html.replace(/<YUI_BR>$/, '');
6866 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
6867 if (this.browser.ie) {
6868 html = html.replace(/ /g, '\t');
6873 * @method post_filter_linebreaks
6874 * @param String html The HTML to filter
6875 * @param String markup The markup type to filter to
6876 * @description HTML Pre Filter
6879 post_filter_linebreaks: function(html, markup) {
6880 if (markup == 'xhtml') {
6881 html = html.replace(/<YUI_BR>/g, '<br />');
6883 html = html.replace(/<YUI_BR>/g, '<br>');
6888 * @method clearEditorDoc
6889 * @description Clear the doc of the Editor
6891 clearEditorDoc: function() {
6892 this._getDoc().body.innerHTML = ' ';
6895 * @method openWindow
6896 * @description Override Method for Advanced Editor
6898 openWindow: function(win) {
6901 * @method moveWindow
6902 * @description Override Method for Advanced Editor
6904 moveWindow: function() {
6908 * @method _closeWindow
6909 * @description Override Method for Advanced Editor
6911 _closeWindow: function() {
6914 * @method closeWindow
6915 * @description Override Method for Advanced Editor
6917 closeWindow: function() {
6918 //this.unsubscribeAll('afterExecCommand');
6919 this.toolbar.resetAllButtons();
6924 * @description Destroys the editor, all of it's elements and objects.
6927 destroy: function() {
6929 this.resize.destroy();
6934 if (this.get('panel')) {
6935 this.get('panel').destroy();
6938 this.toolbar.destroy();
6939 this.setStyle('visibility', 'visible');
6940 this.setStyle('position', 'static');
6941 this.setStyle('top', '');
6942 this.setStyle('left', '');
6943 var textArea = this.get('element');
6944 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
6945 this.get('element_cont').get('element').innerHTML = '';
6946 this.set('handleSubmit', false); //Remove the submit handler
6951 * @description Returns a string representing the editor.
6954 toString: function() {
6955 var str = 'SimpleEditor';
6956 if (this.get && this.get('element_cont')) {
6957 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
6964 * @event toolbarLoaded
6965 * @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.
6966 * @type YAHOO.util.CustomEvent
6970 * @description Event is fired after the cleanHTML method is called.
6971 * @type YAHOO.util.CustomEvent
6974 * @event afterRender
6975 * @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.
6976 * @type YAHOO.util.CustomEvent
6979 * @event editorContentLoaded
6980 * @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.
6981 * @type YAHOO.util.CustomEvent
6984 * @event beforeNodeChange
6985 * @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.
6986 * @type YAHOO.util.CustomEvent
6989 * @event afterNodeChange
6990 * @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.
6991 * @type YAHOO.util.CustomEvent
6994 * @event beforeExecCommand
6995 * @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.
6996 * @type YAHOO.util.CustomEvent
6999 * @event afterExecCommand
7000 * @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.
7001 * @type YAHOO.util.CustomEvent
7004 * @event editorMouseUp
7005 * @param {Event} ev The DOM Event that occured
7006 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7007 * @type YAHOO.util.CustomEvent
7010 * @event editorMouseDown
7011 * @param {Event} ev The DOM Event that occured
7012 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7013 * @type YAHOO.util.CustomEvent
7016 * @event editorDoubleClick
7017 * @param {Event} ev The DOM Event that occured
7018 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7019 * @type YAHOO.util.CustomEvent
7022 * @event editorClick
7023 * @param {Event} ev The DOM Event that occured
7024 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7025 * @type YAHOO.util.CustomEvent
7028 * @event editorKeyUp
7029 * @param {Event} ev The DOM Event that occured
7030 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7031 * @type YAHOO.util.CustomEvent
7034 * @event editorKeyPress
7035 * @param {Event} ev The DOM Event that occured
7036 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7037 * @type YAHOO.util.CustomEvent
7040 * @event editorKeyDown
7041 * @param {Event} ev The DOM Event that occured
7042 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
7043 * @type YAHOO.util.CustomEvent
7046 * @event beforeEditorMouseUp
7047 * @param {Event} ev The DOM Event that occured
7048 * @description Fires before editor event, returning false will stop the internal processing.
7049 * @type YAHOO.util.CustomEvent
7052 * @event beforeEditorMouseDown
7053 * @param {Event} ev The DOM Event that occured
7054 * @description Fires before editor event, returning false will stop the internal processing.
7055 * @type YAHOO.util.CustomEvent
7058 * @event beforeEditorDoubleClick
7059 * @param {Event} ev The DOM Event that occured
7060 * @description Fires before editor event, returning false will stop the internal processing.
7061 * @type YAHOO.util.CustomEvent
7064 * @event beforeEditorClick
7065 * @param {Event} ev The DOM Event that occured
7066 * @description Fires before editor event, returning false will stop the internal processing.
7067 * @type YAHOO.util.CustomEvent
7070 * @event beforeEditorKeyUp
7071 * @param {Event} ev The DOM Event that occured
7072 * @description Fires before editor event, returning false will stop the internal processing.
7073 * @type YAHOO.util.CustomEvent
7076 * @event beforeEditorKeyPress
7077 * @param {Event} ev The DOM Event that occured
7078 * @description Fires before editor event, returning false will stop the internal processing.
7079 * @type YAHOO.util.CustomEvent
7082 * @event beforeEditorKeyDown
7083 * @param {Event} ev The DOM Event that occured
7084 * @description Fires before editor event, returning false will stop the internal processing.
7085 * @type YAHOO.util.CustomEvent
7089 * @event editorWindowFocus
7090 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event.
7091 * @type YAHOO.util.CustomEvent
7094 * @event editorWindowBlur
7095 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event.
7096 * @type YAHOO.util.CustomEvent
7101 * @description Singleton object used to track the open window objects and panels across the various open editors
7105 YAHOO.widget.EditorInfo = {
7108 * @property _instances
7109 * @description A reference to all editors on the page.
7115 * @property blankImage
7116 * @description A reference to the blankImage url
7123 * @description A reference to the currently open window object in any editor on the page.
7124 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
7130 * @description A reference to the currently open panel in any editor on the page.
7131 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
7135 * @method getEditorById
7136 * @description Returns a reference to the Editor object associated with the given textarea
7137 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
7138 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
7140 getEditorById: function(id) {
7141 if (!YAHOO.lang.isString(id)) {
7142 //Not a string, assume a node Reference
7145 if (this._instances[id]) {
7146 return this._instances[id];
7152 * @description Returns a string representing the EditorInfo.
7155 toString: function() {
7157 for (var i in this._instances) {
7158 if (Lang.hasOwnProperty(this._instances, i)) {
7162 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
7170 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.7.0", build: "1799"});