2 Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
11 var Dom = YAHOO.util.Dom,
12 Event = YAHOO.util.Event,
15 * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
16 * @class ToolbarButtonAdvanced
17 * @namespace YAHOO.widget
18 * @requires yahoo, dom, element, event, container_core, menu, button
21 * Provides a toolbar button based on the button and menu widgets.
23 * @param {String/HTMLElement} el The element to turn into a button.
24 * @param {Object} attrs Object liternal containing configuration parameters.
26 if (YAHOO.widget.Button) {
27 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button;
29 * @property buttonType
31 * @description Tells if the Button is a Rich Button or a Simple Button
33 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich';
36 * @param {String} value The value of the option that we want to mark as selected
37 * @description Select an option by value
39 YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) {
40 var _menuItems = this.getMenu().getItems();
41 if (_menuItems.length === 0) {
42 this.getMenu()._onBeforeShow();
43 _menuItems = this.getMenu().getItems();
45 for (var i = 0; i < _menuItems.length; i++) {
46 _menuItems[i].cfg.setProperty('checked', false);
47 if (_menuItems[i].value == value) {
48 _menuItems[i].cfg.setProperty('checked', true);
53 YAHOO.widget.ToolbarButtonAdvanced = function() {};
58 * @description <p>Creates a basic custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p>
59 * @class ToolbarButton
60 * @namespace YAHOO.widget
61 * @requires yahoo, dom, element, event
62 * @Extends YAHOO.util.Element
65 * Provides a toolbar button based on the button and menu widgets, <select> elements are used in place of menu's.
67 * @param {String/HTMLElement} el The element to turn into a button.
68 * @param {Object} attrs Object liternal containing configuration parameters.
71 YAHOO.widget.ToolbarButton = function(el, attrs) {
73 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
76 var local_attrs = (attrs || {});
80 attributes: local_attrs
83 if (!oConfig.attributes.type) {
84 oConfig.attributes.type = 'push';
87 oConfig.element = document.createElement('span');
88 oConfig.element.setAttribute('unselectable', 'on');
89 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button';
90 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>';
91 oConfig.element.firstChild.firstChild.tabIndex = '-1';
92 oConfig.attributes.id = Dom.generateId();
94 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
97 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, {
99 * @property buttonType
101 * @description Tells if the Button is a Rich Button or a Simple Button
103 buttonType: 'normal',
105 * @method _handleMouseOver
107 * @description Adds classes to the button elements on mouseover (hover)
109 _handleMouseOver: function() {
110 if (!this.get('disabled')) {
111 this.addClass('yui-button-hover');
112 this.addClass('yui-' + this.get('type') + '-button-hover');
116 * @method _handleMouseOut
118 * @description Removes classes from the button elements on mouseout (hover)
120 _handleMouseOut: function() {
121 this.removeClass('yui-button-hover');
122 this.removeClass('yui-' + this.get('type') + '-button-hover');
126 * @param {String} value The value of the option that we want to mark as selected
127 * @description Select an option by value
129 checkValue: function(value) {
130 if (this.get('type') == 'menu') {
131 var opts = this._button.options;
132 for (var i = 0; i < opts.length; i++) {
133 if (opts[i].value == value) {
134 opts.selectedIndex = i;
141 * @description The ToolbarButton class's initialization method
143 init: function(p_oElement, p_oAttributes) {
144 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes);
146 this.on('mouseover', this._handleMouseOver, this, true);
147 this.on('mouseout', this._handleMouseOut, this, true);
150 * @method initAttributes
151 * @description Initializes all of the configuration attributes used to create
153 * @param {Object} attr Object literal specifying a set of
154 * configuration attributes used to create the toolbar.
156 initAttributes: function(attr) {
157 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr);
160 * @description The value of the button
163 this.setAttributeConfig('value', {
168 * @description The menu attribute, see YAHOO.widget.Button
171 this.setAttributeConfig('menu', {
172 value: attr.menu || false
176 * @description The type of button to create: push, menu, color, select, spin
179 this.setAttributeConfig('type', {
182 method: function(type) {
185 this._button = this.get('element').getElementsByTagName('a')[0];
190 el = document.createElement('select');
191 var menu = this.get('menu');
192 for (var i = 0; i < menu.length; i++) {
193 opt = document.createElement('option');
194 opt.innerHTML = menu[i].text;
195 opt.value = menu[i].value;
196 if (menu[i].checked) {
201 this._button.parentNode.replaceChild(el, this._button);
202 Event.on(el, 'change', this._handleSelect, this, true);
210 * @attribute disabled
211 * @description Set the button into a disabled state
214 this.setAttributeConfig('disabled', {
215 value: attr.disabled || false,
216 method: function(disabled) {
218 this.addClass('yui-button-disabled');
219 this.addClass('yui-' + this.get('type') + '-button-disabled');
221 this.removeClass('yui-button-disabled');
222 this.removeClass('yui-' + this.get('type') + '-button-disabled');
224 if (this.get('type') == 'menu') {
225 this._button.disabled = disabled;
232 * @description The text label for the button
235 this.setAttributeConfig('label', {
237 method: function(label) {
239 this._button = this.get('element').getElementsByTagName('a')[0];
241 if (this.get('type') == 'push') {
242 this._button.innerHTML = label;
249 * @description The title of the button
252 this.setAttributeConfig('title', {
258 * @description The container that the button is rendered to, handled by Toolbar
261 this.setAttributeConfig('container', {
264 method: function(cont) {
272 * @method _handleSelect
273 * @description The event fired when a change event gets fired on a select element
274 * @param {Event} ev The change event.
276 _handleSelect: function(ev) {
277 var tar = Event.getTarget(ev);
278 var value = tar.options[tar.selectedIndex].value;
279 this.fireEvent('change', {type: 'change', value: value });
283 * @description A stub function to mimic YAHOO.widget.Button's getMenu method
285 getMenu: function() {
286 return this.get('menu');
290 * @description Destroy the button
292 destroy: function() {
293 Event.purgeElement(this.get('element'), true);
294 this.get('element').parentNode.removeChild(this.get('element'));
295 //Brutal Object Destroy
296 for (var i in this) {
297 if (Lang.hasOwnProperty(this, i)) {
304 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled.
306 fireEvent: function (p_sType , p_aArgs) {
307 // Disabled buttons should not respond to DOM events
308 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) {
312 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs);
316 * @description Returns a string representing the toolbar.
319 toString: function() {
320 return 'ToolbarButton (' + this.get('id') + ')';
326 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
327 * @namespace YAHOO.widget
328 * @requires yahoo, dom, element, event, toolbarbutton
329 * @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);
987 * @description The DragDrop instance associated with the Toolbar
990 this.dd = new YAHOO.util.DD(this.get('id'));
991 this.dd.setHandleElId(this._dragHandle.id);
995 if (this._dragHandle) {
996 this._dragHandle.parentNode.removeChild(this._dragHandle);
997 this._dragHandle = null;
1001 if (this._titlebar) {
1003 this.dd = new YAHOO.util.DD(this.get('id'));
1004 this.dd.setHandleElId(this._titlebar);
1005 Dom.addClass(this._titlebar, 'draggable');
1007 Dom.removeClass(this._titlebar, 'draggable');
1015 validator: function(value) {
1017 if (!YAHOO.util.DD) {
1026 * @method addButtonGroup
1027 * @description Add a new button group to the toolbar. (uses addButton)
1028 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label)
1030 addButtonGroup: function(oGroup) {
1031 if (!this.get('element')) {
1032 this._queue[this._queue.length] = ['addButtonGroup', arguments];
1036 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
1037 this.addClass(this.CLASS_PREFIX + '-grouped');
1039 var div = document.createElement('DIV');
1040 Dom.addClass(div, this.CLASS_PREFIX + '-group');
1041 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
1043 var label = document.createElement('h3');
1044 label.innerHTML = oGroup.label;
1045 div.appendChild(label);
1047 if (!this.get('grouplabels')) {
1048 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels');
1051 this.get('cont').appendChild(div);
1053 //For accessibility, let's put all of the group buttons in an Unordered List
1054 var ul = document.createElement('ul');
1055 div.appendChild(ul);
1057 if (!this._buttonGroupList) {
1058 this._buttonGroupList = {};
1061 this._buttonGroupList[oGroup.group] = ul;
1063 for (var i = 0; i < oGroup.buttons.length; i++) {
1064 var li = document.createElement('li');
1065 li.className = this.CLASS_PREFIX + '-groupitem';
1067 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') {
1068 this.addSeparator(li);
1070 oGroup.buttons[i].container = li;
1071 this.addButton(oGroup.buttons[i]);
1076 * @method addButtonToGroup
1077 * @description Add a new button to a toolbar group. Buttons supported:
1078 * push, split, menu, select, color, spin
1079 * @param {Object} oButton Object literal reference to the Button's Config
1080 * @param {String} group The Group identifier passed into the initial config
1081 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1083 addButtonToGroup: function(oButton, group, after) {
1084 var groupCont = this._buttonGroupList[group];
1085 var li = document.createElement('li');
1086 li.className = this.CLASS_PREFIX + '-groupitem';
1087 oButton.container = li;
1088 this.addButton(oButton, after);
1089 groupCont.appendChild(li);
1093 * @description Add a new button to the toolbar. Buttons supported:
1094 * push, split, menu, select, color, spin
1095 * @param {Object} oButton Object literal reference to the Button's Config
1096 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1098 addButton: function(oButton, after) {
1099 if (!this.get('element')) {
1100 this._queue[this._queue.length] = ['addButton', arguments];
1103 if (!this._buttonList) {
1104 this._buttonList = [];
1106 if (!oButton.container) {
1107 oButton.container = this.get('cont');
1110 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1111 if (Lang.isArray(oButton.menu)) {
1112 for (var i in oButton.menu) {
1113 if (Lang.hasOwnProperty(oButton.menu, i)) {
1115 fn: function(ev, x, oMenu) {
1116 if (!oButton.menucmd) {
1117 oButton.menucmd = oButton.value;
1119 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1123 oButton.menu[i].onclick = funcObject;
1128 var _oButton = {}, skip = false;
1129 for (var o in oButton) {
1130 if (Lang.hasOwnProperty(oButton, o)) {
1131 if (!this._toolbarConfigs[o]) {
1132 _oButton[o] = oButton[o];
1136 if (oButton.type == 'select') {
1137 _oButton.type = 'menu';
1139 if (oButton.type == 'spin') {
1140 _oButton.type = 'push';
1142 if (_oButton.type == 'color') {
1143 if (YAHOO.widget.Overlay) {
1144 _oButton = this._makeColorButton(_oButton);
1149 if (_oButton.menu) {
1150 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) {
1151 oButton.menu.showEvent.subscribe(function() {
1152 this._button = _oButton;
1155 for (var m = 0; m < _oButton.menu.length; m++) {
1156 if (!_oButton.menu[m].value) {
1157 _oButton.menu[m].value = _oButton.menu[m].text;
1160 if (this.browser.webkit) {
1161 _oButton.focusmenu = false;
1168 //Add to .get('buttons') manually
1169 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
1171 var tmp = new this.buttonType(_oButton);
1172 tmp.get('element').tabIndex = '-1';
1173 tmp.get('element').setAttribute('role', 'button');
1174 tmp._selected = true;
1176 if (this.get('disabled')) {
1177 //Toolbar is disabled, disable the new button too!
1178 tmp.set('disabled', true);
1181 oButton.id = tmp.get('id');
1185 var el = tmp.get('element');
1188 nextSib = after.get('element').nextSibling;
1189 } else if (after.nextSibling) {
1190 nextSib = after.nextSibling;
1193 nextSib.parentNode.insertBefore(el, nextSib);
1196 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
1198 var icon = document.createElement('span');
1199 icon.className = this.CLASS_PREFIX + '-icon';
1200 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
1201 if (tmp._button.tagName.toLowerCase() == 'button') {
1202 tmp.get('element').setAttribute('unselectable', 'on');
1203 //Replace the Button HTML Element with an a href if it exists
1204 var a = document.createElement('a');
1205 a.innerHTML = tmp._button.innerHTML;
1208 Event.on(a, 'click', function(ev) {
1209 Event.stopEvent(ev);
1211 tmp._button.parentNode.replaceChild(a, tmp._button);
1215 if (oButton.type == 'select') {
1216 if (tmp._button.tagName.toLowerCase() == 'select') {
1217 icon.parentNode.removeChild(icon);
1218 var iel = tmp._button;
1219 var parEl = tmp.get('element');
1220 parEl.parentNode.replaceChild(iel, parEl);
1222 //Don't put a class on it if it's a real select element
1223 tmp.addClass(this.CLASS_PREFIX + '-select');
1226 if (oButton.type == 'spin') {
1227 if (!Lang.isArray(oButton.range)) {
1228 oButton.range = [ 10, 100 ];
1230 this._makeSpinButton(tmp, oButton);
1232 tmp.get('element').setAttribute('title', tmp.get('label'));
1233 if (oButton.type != 'spin') {
1234 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) {
1235 var showPicker = function(ev) {
1237 if (ev.keyCode && (ev.keyCode == 9)) {
1241 if (this._colorPicker) {
1242 this._colorPicker._button = oButton.value;
1244 var menuEL = tmp.getMenu().element;
1245 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') {
1246 tmp.getMenu().show();
1248 tmp.getMenu().hide();
1251 YAHOO.util.Event.stopEvent(ev);
1253 tmp.on('mousedown', showPicker, oButton, this);
1254 tmp.on('keydown', showPicker, oButton, this);
1256 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
1257 tmp.on('keypress', this._buttonClick, oButton, this);
1258 tmp.on('mousedown', function(ev) {
1259 YAHOO.util.Event.stopEvent(ev);
1260 this._buttonClick(ev, oButton);
1262 tmp.on('click', function(ev) {
1263 YAHOO.util.Event.stopEvent(ev);
1266 //Stop the mousedown event so we can trap the selection in the editor!
1267 tmp.on('mousedown', function(ev) {
1268 YAHOO.util.Event.stopEvent(ev);
1270 tmp.on('click', function(ev) {
1271 YAHOO.util.Event.stopEvent(ev);
1273 tmp.on('change', function(ev) {
1274 if (!oButton.menucmd) {
1275 oButton.menucmd = oButton.value;
1277 oButton.value = ev.value;
1278 this._buttonClick(ev, oButton);
1282 //Hijack the mousedown event in the menu and make it fire a button click..
1283 tmp.on('appendTo', function() {
1285 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) {
1286 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
1287 var oMenu = args[1];
1288 YAHOO.util.Event.stopEvent(args[0]);
1289 tmp._onMenuClick(args[0], tmp);
1290 if (!oButton.menucmd) {
1291 oButton.menucmd = oButton.value;
1293 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
1294 self._buttonClick.call(self, args[1], oButton);
1298 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
1299 YAHOO.util.Event.stopEvent(args[0]);
1301 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) {
1302 YAHOO.util.Event.stopEvent(args[0]);
1309 //Stop the mousedown event so we can trap the selection in the editor!
1310 tmp.on('mousedown', function(ev) {
1311 YAHOO.util.Event.stopEvent(ev);
1313 tmp.on('click', function(ev) {
1314 YAHOO.util.Event.stopEvent(ev);
1317 if (this.browser.ie) {
1319 //Add a couple of new events for IE
1320 tmp.DOM_EVENTS.focusin = true;
1321 tmp.DOM_EVENTS.focusout = true;
1323 //Stop them so we don't loose focus in the Editor
1324 tmp.on('focusin', function(ev) {
1325 YAHOO.util.Event.stopEvent(ev);
1328 tmp.on('focusout', function(ev) {
1329 YAHOO.util.Event.stopEvent(ev);
1331 tmp.on('click', function(ev) {
1332 YAHOO.util.Event.stopEvent(ev);
1336 if (this.browser.webkit) {
1337 //This will keep the document from gaining focus and the editor from loosing it..
1338 //Forcefully remove the focus calls in button!
1339 tmp.hasFocus = function() {
1343 this._buttonList[this._buttonList.length] = tmp;
1344 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
1345 if (Lang.isArray(oButton.menu)) {
1346 var menu = tmp.getMenu();
1347 if (menu && menu.renderEvent) {
1348 menu.renderEvent.subscribe(this._addMenuClasses, tmp);
1349 if (oButton.renderer) {
1350 menu.renderEvent.subscribe(oButton.renderer, tmp);
1359 * @method addSeparator
1360 * @description Add a new button separator to the toolbar.
1361 * @param {HTMLElement} cont Optional HTML element to insert this button into.
1362 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
1364 addSeparator: function(cont, after) {
1365 if (!this.get('element')) {
1366 this._queue[this._queue.length] = ['addSeparator', arguments];
1369 var sepCont = ((cont) ? cont : this.get('cont'));
1370 if (!this.get('element')) {
1371 this._queue[this._queue.length] = ['addSeparator', arguments];
1374 if (this._sepCount === null) {
1378 this._sep = document.createElement('SPAN');
1379 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
1380 this._sep.innerHTML = '|';
1382 var _sep = this._sep.cloneNode(true);
1384 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
1388 nextSib = after.get('element').nextSibling;
1389 } else if (after.nextSibling) {
1390 nextSib = after.nextSibling;
1395 if (nextSib == after) {
1396 nextSib.parentNode.appendChild(_sep);
1398 nextSib.parentNode.insertBefore(_sep, nextSib);
1402 sepCont.appendChild(_sep);
1407 * @method _createColorPicker
1409 * @description Creates the core DOM reference to the color picker menu item.
1410 * @param {String} id the id of the toolbar to prefix this DOM container with.
1412 _createColorPicker: function(id) {
1413 if (Dom.get(id + '_colors')) {
1414 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
1416 var picker = document.createElement('div');
1417 picker.className = 'yui-toolbar-colors';
1418 picker.id = id + '_colors';
1419 picker.style.display = 'none';
1420 Event.on(window, 'load', function() {
1421 document.body.appendChild(picker);
1424 this._colorPicker = picker;
1427 for (var i in this._colorData) {
1428 if (Lang.hasOwnProperty(this._colorData, i)) {
1429 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1432 html += '<span><em>X</em><strong></strong></span>';
1433 window.setTimeout(function() {
1434 picker.innerHTML = html;
1437 Event.on(picker, 'mouseover', function(ev) {
1438 var picker = this._colorPicker;
1439 var em = picker.getElementsByTagName('em')[0];
1440 var strong = picker.getElementsByTagName('strong')[0];
1441 var tar = Event.getTarget(ev);
1442 if (tar.tagName.toLowerCase() == 'a') {
1443 em.style.backgroundColor = tar.style.backgroundColor;
1444 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1447 Event.on(picker, 'focus', function(ev) {
1448 Event.stopEvent(ev);
1450 Event.on(picker, 'click', function(ev) {
1451 Event.stopEvent(ev);
1453 Event.on(picker, 'mousedown', function(ev) {
1454 Event.stopEvent(ev);
1455 var tar = Event.getTarget(ev);
1456 if (tar.tagName.toLowerCase() == 'a') {
1457 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1458 if (retVal !== false) {
1460 color: tar.innerHTML,
1461 colorName: this._colorData['#' + tar.innerHTML],
1462 value: this._colorPicker._button
1465 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1467 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1472 * @method _resetColorPicker
1474 * @description Clears the currently selected color or mouseover color in the color picker.
1476 _resetColorPicker: function() {
1477 var em = this._colorPicker.getElementsByTagName('em')[0];
1478 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1479 em.style.backgroundColor = 'transparent';
1480 strong.innerHTML = '';
1483 * @method _makeColorButton
1485 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1486 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1488 _makeColorButton: function(_oButton) {
1489 if (!this._colorPicker) {
1490 this._createColorPicker(this.get('id'));
1492 _oButton.type = 'color';
1493 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true });
1494 _oButton.menu.setBody('');
1495 _oButton.menu.render(this.get('cont'));
1496 Dom.addClass(_oButton.menu.element, 'yui-button-menu');
1497 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu');
1498 _oButton.menu.beforeShowEvent.subscribe(function() {
1499 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1500 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1501 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1502 this._resetColorPicker();
1503 var _p = this._colorPicker;
1504 if (_p.parentNode) {
1505 _p.parentNode.removeChild(_p);
1507 _oButton.menu.setBody('');
1508 _oButton.menu.appendToBody(_p);
1509 this._colorPicker.style.display = 'block';
1515 * @method _makeSpinButton
1516 * @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.
1517 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference
1518 * @param {Object} oButton Object literal containing the buttons initial config
1520 _makeSpinButton: function(_button, oButton) {
1521 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1523 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1524 range = oButton.range,
1525 _b1 = document.createElement('a'),
1526 _b2 = document.createElement('a');
1529 _b1.tabIndex = '-1';
1530 _b2.tabIndex = '-1';
1532 //Setup the up and down arrows
1533 _b1.className = 'up';
1534 _b1.title = this.STR_SPIN_UP;
1535 _b1.innerHTML = this.STR_SPIN_UP;
1536 _b2.className = 'down';
1537 _b2.title = this.STR_SPIN_DOWN;
1538 _b2.innerHTML = this.STR_SPIN_DOWN;
1540 //Append them to the container
1541 _par.appendChild(_b1);
1542 _par.appendChild(_b2);
1544 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1545 _button.set('title', label);
1547 var cleanVal = function(value) {
1548 value = ((value < range[0]) ? range[0] : value);
1549 value = ((value > range[1]) ? range[1] : value);
1553 var br = this.browser;
1555 var strLabel = this.STR_SPIN_LABEL;
1556 if (this._titlebar && this._titlebar.firstChild) {
1557 tbar = this._titlebar.firstChild;
1560 var _intUp = function(ev) {
1561 YAHOO.util.Event.stopEvent(ev);
1562 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1563 var value = parseInt(_button.get('label'), 10);
1565 value = cleanVal(value);
1566 _button.set('label', ''+value);
1567 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1568 _button.set('title', label);
1569 if (!br.webkit && tbar) {
1570 //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
1573 self._buttonClick(ev, oButton);
1577 var _intDown = function(ev) {
1578 YAHOO.util.Event.stopEvent(ev);
1579 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1580 var value = parseInt(_button.get('label'), 10);
1582 value = cleanVal(value);
1584 _button.set('label', ''+value);
1585 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1586 _button.set('title', label);
1587 if (!br.webkit && tbar) {
1588 //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
1591 self._buttonClick(ev, oButton);
1595 var _intKeyUp = function(ev) {
1596 if (ev.keyCode == 38) {
1598 } else if (ev.keyCode == 40) {
1600 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1602 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1607 //Handle arrow keys..
1608 _button.on('keydown', _intKeyUp, this, true);
1610 //Listen for the click on the up button and act on it
1611 //Listen for the click on the down button and act on it
1612 Event.on(_b1, 'mousedown',function(ev) {
1613 Event.stopEvent(ev);
1615 Event.on(_b2, 'mousedown', function(ev) {
1616 Event.stopEvent(ev);
1618 Event.on(_b1, 'click', _intUp, this, true);
1619 Event.on(_b2, 'click', _intDown, this, true);
1623 * @method _buttonClick
1624 * @description Click handler for all buttons in the toolbar.
1625 * @param {String} ev The event that was passed in.
1626 * @param {Object} info Object literal of information about the button that was clicked.
1628 _buttonClick: function(ev, info) {
1631 if (ev && ev.type == 'keypress') {
1632 if (ev.keyCode == 9) {
1634 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) {
1641 var fireNextEvent = true,
1644 info.isSelected = this.isSelected(info.id);
1647 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1648 if (retValue === false) {
1649 fireNextEvent = false;
1653 if (info.menucmd && fireNextEvent) {
1654 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1655 if (retValue === false) {
1656 fireNextEvent = false;
1659 if (fireNextEvent) {
1660 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1663 if (info.type == 'select') {
1664 var button = this.getButtonById(info.id);
1665 if (button.buttonType == 'rich') {
1666 var txt = info.value;
1667 for (var i = 0; i < info.menu.length; i++) {
1668 if (info.menu[i].value == info.value) {
1669 txt = info.menu[i].text;
1673 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1674 var _items = button.getMenu().getItems();
1675 for (var m = 0; m < _items.length; m++) {
1676 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1677 _items[m].cfg.setProperty('checked', true);
1679 _items[m].cfg.setProperty('checked', false);
1685 Event.stopEvent(ev);
1692 * @description Flag to determine if the arrow nav listeners have been attached
1698 * @property _navCounter
1699 * @description Internal counter for walking the buttons in the toolbar with the arrow keys
1705 * @method _navigateButtons
1706 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys
1707 * @param {Event} ev The Key Event
1709 _navigateButtons: function(ev) {
1710 switch (ev.keyCode) {
1713 if (ev.keyCode == 37) {
1718 if (this._navCounter > (this._buttonList.length - 1)) {
1719 this._navCounter = 0;
1721 if (this._navCounter < 0) {
1722 this._navCounter = (this._buttonList.length - 1);
1724 if (this._buttonList[this._navCounter]) {
1725 var el = this._buttonList[this._navCounter].get('element');
1726 if (this.browser.ie) {
1727 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0];
1729 if (this._buttonList[this._navCounter].get('disabled')) {
1730 this._navigateButtons(ev);
1740 * @method _handleFocus
1741 * @description Sets up the listeners for the arrow key navigation
1743 _handleFocus: function() {
1744 if (!this._keyNav) {
1745 var ev = 'keypress';
1746 if (this.browser.ie) {
1749 Event.on(this.get('element'), ev, this._navigateButtons, this, true);
1750 this._keyNav = true;
1751 this._navCounter = -1;
1755 * @method getButtonById
1756 * @description Gets a button instance from the toolbar by is Dom id.
1757 * @param {String} id The Dom id to query for.
1758 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1760 getButtonById: function(id) {
1761 var len = this._buttonList.length;
1762 for (var i = 0; i < len; i++) {
1763 if (this._buttonList[i] && this._buttonList[i].get('id') == id) {
1764 return this._buttonList[i];
1770 * @method getButtonByValue
1771 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1772 * @param {String} value The button value to query for.
1773 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1775 getButtonByValue: function(value) {
1776 var _buttons = this.get('buttons');
1777 var len = _buttons.length;
1778 for (var i = 0; i < len; i++) {
1779 if (_buttons[i].group !== undefined) {
1780 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1781 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1782 return this.getButtonById(_buttons[i].buttons[m].id);
1784 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1785 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1786 if (_buttons[i].buttons[m].menu[s].value == value) {
1787 return this.getButtonById(_buttons[i].buttons[m].id);
1793 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1794 return this.getButtonById(_buttons[i].id);
1796 if (_buttons[i].menu) { //Menu Button, loop through the values
1797 for (var j = 0; j < _buttons[i].menu.length; j++) {
1798 if (_buttons[i].menu[j].value == value) {
1799 return this.getButtonById(_buttons[i].id);
1808 * @method getButtonByIndex
1809 * @description Gets a button instance from the toolbar by is index in _buttonList.
1810 * @param {Number} index The index of the button in _buttonList.
1811 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>}
1813 getButtonByIndex: function(index) {
1814 if (this._buttonList[index]) {
1815 return this._buttonList[index];
1821 * @method getButtons
1822 * @description Returns an array of buttons in the current toolbar
1825 getButtons: function() {
1826 return this._buttonList;
1829 * @method disableButton
1830 * @description Disables a button in the toolbar.
1831 * @param {String/Number} id Disable a button by it's id, index or value.
1834 disableButton: function(id) {
1835 var button = getButton.call(this, id);
1837 button.set('disabled', true);
1843 * @method enableButton
1844 * @description Enables a button in the toolbar.
1845 * @param {String/Number} id Enable a button by it's id, index or value.
1848 enableButton: function(id) {
1849 if (this.get('disabled')) {
1852 var button = getButton.call(this, id);
1854 if (button.get('disabled')) {
1855 button.set('disabled', false);
1862 * @method isSelected
1863 * @description Tells if a button is selected or not.
1864 * @param {String/Number} id A button by it's id, index or value.
1867 isSelected: function(id) {
1868 var button = getButton.call(this, id);
1870 return button._selected;
1875 * @method selectButton
1876 * @description Selects a button in the toolbar.
1877 * @param {String/Number} id Select a button by it's id, index or value.
1878 * @param {String} value If this is a Menu Button, check this item in the menu
1881 selectButton: function(id, value) {
1882 var button = getButton.call(this, id);
1884 button.addClass('yui-button-selected');
1885 button.addClass('yui-button-' + button.get('value') + '-selected');
1886 button._selected = true;
1888 if (button.buttonType == 'rich') {
1889 var _items = button.getMenu().getItems();
1890 for (var m = 0; m < _items.length; m++) {
1891 if (_items[m].value == value) {
1892 _items[m].cfg.setProperty('checked', true);
1893 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1895 _items[m].cfg.setProperty('checked', false);
1905 * @method deselectButton
1906 * @description Deselects a button in the toolbar.
1907 * @param {String/Number} id Deselect a button by it's id, index or value.
1910 deselectButton: function(id) {
1911 var button = getButton.call(this, id);
1913 button.removeClass('yui-button-selected');
1914 button.removeClass('yui-button-' + button.get('value') + '-selected');
1915 button.removeClass('yui-button-hover');
1916 button._selected = false;
1922 * @method deselectAllButtons
1923 * @description Deselects all buttons in the toolbar.
1926 deselectAllButtons: function() {
1927 var len = this._buttonList.length;
1928 for (var i = 0; i < len; i++) {
1929 this.deselectButton(this._buttonList[i]);
1933 * @method disableAllButtons
1934 * @description Disables all buttons in the toolbar.
1937 disableAllButtons: function() {
1938 if (this.get('disabled')) {
1941 var len = this._buttonList.length;
1942 for (var i = 0; i < len; i++) {
1943 this.disableButton(this._buttonList[i]);
1947 * @method enableAllButtons
1948 * @description Enables all buttons in the toolbar.
1951 enableAllButtons: function() {
1952 if (this.get('disabled')) {
1955 var len = this._buttonList.length;
1956 for (var i = 0; i < len; i++) {
1957 this.enableButton(this._buttonList[i]);
1961 * @method resetAllButtons
1962 * @description Resets all buttons to their initial state.
1963 * @param {Object} _ex Except these buttons
1966 resetAllButtons: function(_ex) {
1967 if (!Lang.isObject(_ex)) {
1970 if (this.get('disabled')) {
1973 var len = this._buttonList.length;
1974 for (var i = 0; i < len; i++) {
1975 var _button = this._buttonList[i];
1977 var disabled = _button._configs.disabled._initialConfig.value;
1978 if (_ex[_button.get('id')]) {
1979 this.enableButton(_button);
1980 this.selectButton(_button);
1983 this.disableButton(_button);
1985 this.enableButton(_button);
1987 this.deselectButton(_button);
1993 * @method destroyButton
1994 * @description Destroy a button in the toolbar.
1995 * @param {String/Number} id Destroy a button by it's id or index.
1998 destroyButton: function(id) {
1999 var button = getButton.call(this, id);
2001 var thisID = button.get('id');
2004 var len = this._buttonList.length;
2005 for (var i = 0; i < len; i++) {
2006 if (this._buttonList[i] && this._buttonList[i].get('id') == thisID) {
2007 this._buttonList[i] = null;
2016 * @description Destroys the toolbar, all of it's elements and objects.
2019 destroy: function() {
2020 this.get('element').innerHTML = '';
2021 this.get('element').className = '';
2022 //Brutal Object Destroy
2023 for (var i in this) {
2024 if (Lang.hasOwnProperty(this, i)) {
2032 * @description Programatically collapse the toolbar.
2033 * @param {Boolean} collapse True to collapse, false to expand.
2035 collapse: function(collapse) {
2036 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar);
2037 if (collapse === false) {
2038 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2040 Dom.removeClass(el[0], 'collapsed');
2042 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
2045 Dom.addClass(el[0], 'collapsed');
2047 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed');
2048 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
2053 * @description Returns a string representing the toolbar.
2056 toString: function() {
2057 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
2061 * @event buttonClick
2062 * @param {Object} o The object passed to this handler is the button config used to create the button.
2063 * @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.
2064 * @type YAHOO.util.CustomEvent
2068 * @param {Object} o The object passed to this handler is the button config used to create the button.
2069 * @description This is a special dynamic event that is created and dispatched based on the value property
2070 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
2074 * { type: 'button', value: 'test', value: 'testButton' }
2077 * With the valueClick event you could subscribe to this buttons click event with this:
2078 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
2079 * @type YAHOO.util.CustomEvent
2082 * @event toolbarExpanded
2083 * @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.
2084 * @type YAHOO.util.CustomEvent
2087 * @event toolbarCollapsed
2088 * @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.
2089 * @type YAHOO.util.CustomEvent
2093 * @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>
2094 * @namespace YAHOO.widget
2095 * @requires yahoo, dom, element, event, toolbar
2096 * @optional animation, container_core, resize, dragdrop
2101 var Dom = YAHOO.util.Dom,
2102 Event = YAHOO.util.Event,
2104 Toolbar = YAHOO.widget.Toolbar;
2107 * 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.
2109 * @class SimpleEditor
2110 * @extends YAHOO.util.Element
2111 * @param {String/HTMLElement} el The textarea element to turn into an editor.
2112 * @param {Object} attrs Object liternal containing configuration parameters.
2115 YAHOO.widget.SimpleEditor = function(el, attrs) {
2118 if (Lang.isObject(el) && (!el.tagName) && !attrs) {
2119 Lang.augmentObject(o, el); //Break the config reference
2120 el = document.createElement('textarea');
2121 this.DOMReady = true;
2123 var c = Dom.get(o.container);
2126 document.body.appendChild(el);
2130 Lang.augmentObject(o, attrs); //Break the config reference
2139 if (Lang.isString(el)) {
2142 if (oConfig.attributes.id) {
2143 id = oConfig.attributes.id;
2145 this.DOMReady = true;
2146 id = Dom.generateId(el);
2149 oConfig.element = el;
2151 var element_cont = document.createElement('DIV');
2152 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
2153 id: id + '_container'
2155 var div = document.createElement('div');
2156 Dom.addClass(div, 'first-child');
2157 oConfig.attributes.element_cont.appendChild(div);
2159 if (!oConfig.attributes.toolbar_cont) {
2160 oConfig.attributes.toolbar_cont = document.createElement('DIV');
2161 oConfig.attributes.toolbar_cont.id = id + '_toolbar';
2162 div.appendChild(oConfig.attributes.toolbar_cont);
2164 var editorWrapper = document.createElement('DIV');
2165 div.appendChild(editorWrapper);
2166 oConfig.attributes.editor_wrapper = editorWrapper;
2168 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
2172 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
2175 * @property _resizeConfig
2176 * @description The default config for the Resize Utility
2188 * @method _setupResize
2189 * @description Creates the Resize instance and binds its events.
2191 _setupResize: function() {
2192 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; }
2193 if (this.get('resize')) {
2195 Lang.augmentObject(config, this._resizeConfig); //Break the config reference
2196 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config);
2197 this.resize.on('resize', function(args) {
2198 var anim = this.get('animate');
2199 this.set('animate', false);
2200 this.set('width', args.width + 'px');
2201 var h = args.height,
2202 th = (this.toolbar.get('element').clientHeight + 2),
2205 dh = (this.dompath.clientHeight + 1); //It has a 1px top border..
2207 var newH = (h - th - dh);
2208 this.set('height', newH + 'px');
2209 this.get('element_cont').setStyle('height', '');
2210 this.set('animate', anim);
2216 * @description A reference to the Resize object
2217 * @type YAHOO.util.Resize
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']
2326 mods: ['shift', 'ctrl']
2330 mods: ['shift', 'ctrl']
2334 mods: ['shift', 'ctrl']
2338 mods: ['shift', 'ctrl']
2343 * @method _cleanClassName
2344 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
2345 * @param {String} str The classname to clean up
2348 _cleanClassName: function(str) {
2349 return str.replace(/ /g, '-').toLowerCase();
2352 * @property _textarea
2353 * @description Flag to determine if we are using a textarea or an HTML Node.
2358 * @property _docType
2359 * @description The DOCTYPE to use in the editable container.
2362 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
2364 * @property editorDirty
2365 * @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.
2370 * @property _defaultCSS
2371 * @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' }
2374 _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 { margin: 11px 0; }',
2376 * @property _defaultToolbar
2378 * @description Default toolbar config.
2381 _defaultToolbar: null,
2383 * @property _lastButton
2385 * @description The last button pressed, so we don't disable it.
2390 * @property _baseHREF
2392 * @description The base location of the editable page (this page) so that relative paths for image work.
2395 _baseHREF: function() {
2396 var href = document.location.href;
2397 if (href.indexOf('?') !== -1) { //Remove the query string
2398 href = href.substring(0, href.indexOf('?'));
2400 href = href.substring(0, href.lastIndexOf('/')) + '/';
2404 * @property _lastImage
2406 * @description Safari reference for the last image selected (for styling as selected).
2411 * @property _blankImageLoaded
2413 * @description Don't load the blank image more than once..
2416 _blankImageLoaded: null,
2418 * @property _fixNodesTimer
2420 * @description Holder for the fixNodes timer
2423 _fixNodesTimer: null,
2425 * @property _nodeChangeTimer
2427 * @description Holds a reference to the nodeChange setTimeout call
2430 _nodeChangeTimer: null,
2432 * @property _lastNodeChangeEvent
2434 * @description Flag to determine the last event that fired a node change
2437 _lastNodeChangeEvent: null,
2439 * @property _lastNodeChange
2441 * @description Flag to determine when the last node change was fired
2446 * @property _rendered
2448 * @description Flag to determine if editor has been rendered or not
2453 * @property DOMReady
2455 * @description Flag to determine if DOM is ready or not
2460 * @property _selection
2462 * @description Holder for caching iframe selections
2469 * @description DOM Element holder for the editor Mask when disabled
2474 * @property _showingHiddenElements
2476 * @description Status of the hidden elements button
2479 _showingHiddenElements: null,
2481 * @property currentWindow
2482 * @description A reference to the currently open EditorWindow
2485 currentWindow: null,
2487 * @property currentEvent
2488 * @description A reference to the current editor event
2493 * @property operaEvent
2495 * @description setTimeout holder for Opera and Image DoubleClick event..
2500 * @property currentFont
2501 * @description A reference to the last font selected from the Toolbar
2506 * @property currentElement
2507 * @description A reference to the current working element in the editor
2510 currentElement: null,
2513 * @description A reference to the dompath container for writing the current working dom path to.
2518 * @property beforeElement
2519 * @description A reference to the H2 placed before the editor for Accessibilty.
2522 beforeElement: null,
2524 * @property afterElement
2525 * @description A reference to the H2 placed after the editor for Accessibilty.
2530 * @property invalidHTML
2531 * @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.
2549 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
2550 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
2555 * @property _contentTimer
2556 * @description setTimeout holder for documentReady check
2558 _contentTimer: null,
2561 * @property _contentTimerCounter
2562 * @description Counter to check the number of times the body is polled for before giving up
2565 _contentTimerCounter: 0,
2568 * @property _disabled
2569 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
2572 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
2575 * @property _alwaysDisabled
2576 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
2579 _alwaysDisabled: { undo: true, redo: true },
2582 * @property _alwaysEnabled
2583 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
2586 _alwaysEnabled: { },
2589 * @property _semantic
2590 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
2593 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
2596 * @property _tag2cmd
2597 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
2606 'sup': 'superscript',
2608 'img': 'insertimage',
2610 'ul' : 'insertunorderedlist',
2611 'ol' : 'insertorderedlist'
2615 * @private _createIframe
2616 * @description Creates the DOM and YUI Element for the iFrame editor area.
2617 * @param {String} id The string ID to prefix the iframe with
2618 * @return {Object} iFrame object
2620 _createIframe: function() {
2621 var ifrmDom = document.createElement('iframe');
2622 ifrmDom.id = this.get('id') + '_editor';
2630 allowTransparency: 'true',
2633 if (this.get('autoHeight')) {
2634 config.scrolling = 'no';
2636 for (var i in config) {
2637 if (Lang.hasOwnProperty(config, i)) {
2638 ifrmDom.setAttribute(i, config[i]);
2641 var isrc = 'javascript:;';
2642 if (this.browser.ie) {
2643 //isrc = 'about:blank';
2644 //TODO - Check this, I have changed it before..
2645 isrc = 'javascript:false;';
2647 ifrmDom.setAttribute('src', isrc);
2648 var ifrm = new YAHOO.util.Element(ifrmDom);
2649 ifrm.setStyle('visibility', 'hidden');
2653 * @private _isElement
2654 * @description Checks to see if an Element reference is a valid one and has a certain tag type
2655 * @param {HTMLElement} el The element to check
2656 * @param {String} tag The tag that the element needs to be
2659 _isElement: function(el, tag) {
2660 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
2663 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
2669 * @private _hasParent
2670 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
2671 * @param {HTMLElement} el The element to check
2672 * @param {String} tag The tag that the element needs to be
2673 * @return HTMLElement
2675 _hasParent: function(el, tag) {
2676 if (!el || !el.parentNode) {
2680 while (el.parentNode) {
2681 if (this._isElement(el, tag)) {
2684 if (el.parentNode) {
2695 * @description Get the Document of the IFRAME
2698 _getDoc: function() {
2701 if (this.get('iframe')) {
2702 if (this.get('iframe').get) {
2703 if (this.get('iframe').get('element')) {
2705 if (this.get('iframe').get('element').contentWindow) {
2706 if (this.get('iframe').get('element').contentWindow.document) {
2707 value = this.get('iframe').get('element').contentWindow.document;
2720 * @method _getWindow
2721 * @description Get the Window of the IFRAME
2724 _getWindow: function() {
2725 return this.get('iframe').get('element').contentWindow;
2729 * @method _focusWindow
2730 * @description Attempt to set the focus of the iframes window.
2731 * @param {Boolean} onLoad Safari needs some special care to set the cursor in the iframe
2733 _focusWindow: function(onLoad) {
2734 if (this.browser.webkit) {
2737 * @knownissue Safari Cursor Position
2738 * @browser Safari 2.x
2739 * @description Can't get Safari to place the cursor at the beginning of the text..
2740 * This workaround at least set's the toolbar into the proper state.
2742 this._getSelection().setBaseAndExtent(this._getDoc().body.firstChild, 0, this._getDoc().body.firstChild, 1);
2743 if (this.browser.webkit3) {
2744 this._getSelection().collapseToStart();
2746 this._getSelection().collapse(false);
2749 this._getSelection().setBaseAndExtent(this._getDoc().body, 1, this._getDoc().body, 1);
2750 if (this.browser.webkit3) {
2751 this._getSelection().collapseToStart();
2753 this._getSelection().collapse(false);
2756 this._getWindow().focus();
2758 this._getWindow().focus();
2763 * @method _hasSelection
2764 * @description Determines if there is a selection in the editor document.
2767 _hasSelection: function() {
2768 var sel = this._getSelection();
2769 var range = this._getRange();
2772 if (!sel || !range) {
2777 if (this.browser.ie || this.browser.opera) {
2785 if (this.browser.webkit) {
2786 if (sel+'' !== '') {
2790 if (sel && (sel.toString() !== '') && (sel !== undefined)) {
2799 * @method _getSelection
2800 * @description Handles the different selection objects across the A-Grade list.
2801 * @return {Object} Selection Object
2803 _getSelection: function() {
2805 if (this._getDoc() && this._getWindow()) {
2806 if (this._getDoc().selection) {
2807 _sel = this._getDoc().selection;
2809 _sel = this._getWindow().getSelection();
2811 //Handle Safari's lack of Selection Object
2812 if (this.browser.webkit) {
2813 if (_sel.baseNode) {
2814 this._selection = {};
2815 this._selection.baseNode = _sel.baseNode;
2816 this._selection.baseOffset = _sel.baseOffset;
2817 this._selection.extentNode = _sel.extentNode;
2818 this._selection.extentOffset = _sel.extentOffset;
2819 } else if (this._selection !== null) {
2820 _sel = this._getWindow().getSelection();
2821 _sel.setBaseAndExtent(
2822 this._selection.baseNode,
2823 this._selection.baseOffset,
2824 this._selection.extentNode,
2825 this._selection.extentOffset);
2826 this._selection = null;
2834 * @method _selectNode
2835 * @description Places the highlight around a given node
2836 * @param {HTMLElement} node The node to select
2838 _selectNode: function(node, collapse) {
2842 var sel = this._getSelection(),
2845 if (this.browser.ie) {
2846 try { //IE freaks out here sometimes..
2847 range = this._getDoc().body.createTextRange();
2848 range.moveToElementText(node);
2852 } else if (this.browser.webkit) {
2854 sel.setBaseAndExtent(node, 1, node, node.innerText.length);
2856 sel.setBaseAndExtent(node, 0, node, node.innerText.length);
2858 } else if (this.browser.opera) {
2859 sel = this._getWindow().getSelection();
2860 range = this._getDoc().createRange();
2861 range.selectNode(node);
2862 sel.removeAllRanges();
2863 sel.addRange(range);
2865 range = this._getDoc().createRange();
2866 range.selectNodeContents(node);
2867 sel.removeAllRanges();
2868 sel.addRange(range);
2870 //TODO - Check Performance
2876 * @description Handles the different range objects across the A-Grade list.
2877 * @return {Object} Range Object
2879 _getRange: function() {
2880 var sel = this._getSelection();
2886 if (this.browser.webkit && !sel.getRangeAt) {
2887 var _range = this._getDoc().createRange();
2889 _range.setStart(sel.anchorNode, sel.anchorOffset);
2890 _range.setEnd(sel.focusNode, sel.focusOffset);
2892 _range = this._getWindow().getSelection()+'';
2897 if (this.browser.ie || this.browser.opera) {
2899 return sel.createRange();
2905 if (sel.rangeCount > 0) {
2906 return sel.getRangeAt(0);
2912 * @method _setDesignMode
2913 * @description Sets the designMode of the iFrame document.
2914 * @param {String} state This should be either on or off
2916 _setDesignMode: function(state) {
2919 //SourceForge Bug #1807057
2920 if (this.browser.ie && (state.toLowerCase() == 'off')) {
2924 this._getDoc().designMode = state;
2930 * @method _toggleDesignMode
2931 * @description Toggles the designMode of the iFrame document on and off.
2932 * @return {String} The state that it was set to.
2934 _toggleDesignMode: function() {
2935 var _dMode = this._getDoc().designMode.toLowerCase(),
2937 if (_dMode == 'on') {
2940 this._setDesignMode(_state);
2945 * @method _initEditorEvents
2946 * @description This method sets up the listeners on the Editors document.
2948 _initEditorEvents: function() {
2949 //Setup Listeners on iFrame
2950 var doc = this._getDoc();
2951 Event.on(doc, 'mouseup', this._handleMouseUp, this, true);
2952 Event.on(doc, 'mousedown', this._handleMouseDown, this, true);
2953 Event.on(doc, 'click', this._handleClick, this, true);
2954 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true);
2955 Event.on(doc, 'keypress', this._handleKeyPress, this, true);
2956 Event.on(doc, 'keyup', this._handleKeyUp, this, true);
2957 Event.on(doc, 'keydown', this._handleKeyDown, this, true);
2961 * @method _removeEditorEvents
2962 * @description This method removes the listeners on the Editors document (for disabling).
2964 _removeEditorEvents: function() {
2965 //Remove Listeners on iFrame
2966 var doc = this._getDoc();
2967 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true);
2968 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true);
2969 Event.removeListener(doc, 'click', this._handleClick, this, true);
2970 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true);
2971 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true);
2972 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true);
2973 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true);
2977 * @method _initEditor
2978 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
2980 _initEditor: function() {
2981 if (this.browser.ie) {
2982 this._getDoc().body.style.margin = '0';
2984 if (!this.get('disabled')) {
2985 if (this._getDoc().designMode.toLowerCase() != 'on') {
2986 this._setDesignMode('on');
2987 this._contentTimerCounter = 0;
2990 if (!this._getDoc().body) {
2991 this._contentTimerCounter = 0;
2992 this._checkLoaded();
2996 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
2997 if (!this.get('disabled')) {
2998 this._initEditorEvents();
2999 this.toolbar.set('disabled', false);
3002 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
3003 if (this.get('dompath')) {
3005 setTimeout(function() {
3006 self._writeDomPath.call(self);
3007 self._setupResize.call(self);
3011 for (var i in this.browser) {
3012 if (this.browser[i]) {
3016 if (this.get('ptags')) {
3019 Dom.addClass(this._getDoc().body, br.join(' '));
3020 this.nodeChange(true);
3024 * @method _checkLoaded
3025 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
3027 _checkLoaded: function() {
3028 this._contentTimerCounter++;
3029 if (this._contentTimer) {
3030 clearTimeout(this._contentTimer);
3032 if (this._contentTimerCounter > 500) {
3037 if (this._getDoc() && this._getDoc().body) {
3038 if (this.browser.ie) {
3039 if (this._getDoc().body.readyState == 'complete') {
3043 if (this._getDoc().body._rteLoaded === true) {
3052 if (init === true) {
3053 //The onload event has fired, clean up after ourselves and fire the _initEditor method
3057 this._contentTimer = setTimeout(function() {
3058 self._checkLoaded.call(self);
3064 * @method _setInitialContent
3065 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
3067 _setInitialContent: function() {
3069 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML),
3072 var html = Lang.substitute(this.get('html'), {
3073 TITLE: this.STR_TITLE,
3074 CONTENT: this._cleanIncomingHTML(value),
3075 CSS: this.get('css'),
3076 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
3077 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
3080 if (document.compatMode != 'BackCompat') {
3081 html = this._docType + "\n" + html;
3085 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
3086 //Firefox 1.5 doesn't like setting designMode on an document created with a data url
3089 if (this.browser.air) {
3090 doc = this._getDoc().implementation.createHTMLDocument();
3091 var origDoc = this._getDoc();
3097 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
3098 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
3099 origDoc.body._rteLoaded = true;
3101 doc = this._getDoc();
3107 //Safari will only be here if we are hidden
3111 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
3112 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
3114 this.get('iframe').setStyle('visibility', '');
3116 this._checkLoaded();
3121 * @method _setMarkupType
3122 * @param {String} action The action to take. Possible values are: css, default or semantic
3123 * @description This method will turn on/off the useCSS execCommand.
3125 _setMarkupType: function(action) {
3126 switch (this.get('markup')) {
3128 this._setEditorStyle(true);
3131 this._setEditorStyle(false);
3135 if (this._semantic[action]) {
3136 this._setEditorStyle(false);
3138 this._setEditorStyle(true);
3144 * Set the editor to use CSS instead of HTML
3145 * @param {Booleen} stat True/False
3147 _setEditorStyle: function(stat) {
3149 this._getDoc().execCommand('useCSS', false, !stat);
3155 * @method _getSelectedElement
3156 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
3157 * @return {HTMLElement} The currently selected element.
3159 _getSelectedElement: function() {
3160 var doc = this._getDoc(),
3166 if (this.browser.ie) {
3167 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
3168 range = this._getRange();
3170 elm = range.item ? range.item(0) : range.parentElement();
3171 if (this._hasSelection()) {
3173 //WTF.. Why can't I get an element reference here?!??!
3175 if (elm === doc.body) {
3179 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
3180 elm = Event.getTarget(this.currentEvent);
3183 sel = this._getSelection();
3184 range = this._getRange();
3186 if (!sel || !range) {
3190 if (!this._hasSelection() && this.browser.webkit3) {
3193 if (this.browser.gecko) {
3195 if (range.startContainer) {
3197 if (range.startContainer.nodeType === 3) {
3198 elm = range.startContainer.parentNode;
3199 } else if (range.startContainer.nodeType === 1) {
3200 elm = range.startContainer;
3205 this.currentEvent = null;
3210 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
3211 if (sel.anchorNode.parentNode) { //next check parentNode
3212 elm = sel.anchorNode.parentNode;
3214 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
3215 elm = sel.anchorNode.nextSibling;
3218 if (this._isElement(elm, 'br')) {
3222 elm = range.commonAncestorContainer;
3223 if (!range.collapsed) {
3224 if (range.startContainer == range.endContainer) {
3225 if (range.startOffset - range.endOffset < 2) {
3226 if (range.startContainer.hasChildNodes()) {
3227 elm = range.startContainer.childNodes[range.startOffset];
3236 if (this.currentEvent !== null) {
3238 switch (this.currentEvent.type) {
3242 if (this.browser.webkit) {
3243 elm = Event.getTarget(this.currentEvent);
3252 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
3253 //TODO is this still needed?
3254 //elm = this.currentElement[0];
3258 if (this.browser.opera || this.browser.webkit) {
3259 if (this.currentEvent && !elm) {
3260 elm = YAHOO.util.Event.getTarget(this.currentEvent);
3263 if (!elm || !elm.tagName) {
3266 if (this._isElement(elm, 'html')) {
3267 //Safari sometimes gives us the HTML node back..
3270 if (this._isElement(elm, 'body')) {
3271 //make sure that body means this body not the parent..
3274 if (elm && !elm.parentNode) { //Not in document
3277 if (elm === undefined) {
3284 * @method _getDomPath
3285 * @description This method will attempt to build the DOM path from the currently selected element.
3286 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
3287 * @return {Array} An array of node references that will create the DOM Path.
3289 _getDomPath: function(el) {
3291 el = this._getSelectedElement();
3294 while (el !== null) {
3295 if (el.ownerDocument != this._getDoc()) {
3299 //Check to see if we get el.nodeName and nodeType
3300 if (el.nodeName && el.nodeType && (el.nodeType == 1)) {
3301 domPath[domPath.length] = el;
3304 if (this._isElement(el, 'body')) {
3310 if (domPath.length === 0) {
3311 if (this._getDoc() && this._getDoc().body) {
3312 domPath[0] = this._getDoc().body;
3315 return domPath.reverse();
3319 * @method _writeDomPath
3320 * @description Write the current DOM path out to the dompath container below the editor.
3322 _writeDomPath: function() {
3323 var path = this._getDomPath(),
3327 for (var i = 0; i < path.length; i++) {
3328 var tag = path[i].tagName.toLowerCase();
3329 if ((tag == 'ol') && (path[i].type)) {
3330 tag += ':' + path[i].type;
3332 if (Dom.hasClass(path[i], 'yui-tag')) {
3333 tag = path[i].getAttribute('tag');
3335 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) {
3337 case 'b': tag = 'strong'; break;
3338 case 'i': tag = 'em'; break;
3341 if (!Dom.hasClass(path[i], 'yui-non')) {
3342 if (Dom.hasClass(path[i], 'yui-tag')) {
3345 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : '');
3346 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
3349 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
3356 if (path[i].getAttribute('href', 2)) {
3357 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
3361 var h = path[i].height;
3362 var w = path[i].width;
3363 if (path[i].style.height) {
3364 h = parseInt(path[i].style.height, 10);
3366 if (path[i].style.width) {
3367 w = parseInt(path[i].style.width, 10);
3369 pathStr += '(' + w + 'x' + h + ')';
3373 if (pathStr.length > 10) {
3374 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>';
3376 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>';
3378 pathArr[pathArr.length] = pathStr;
3381 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
3382 //Prevent flickering
3383 if (this.dompath.innerHTML != str) {
3384 this.dompath.innerHTML = str;
3390 * @description Fix href and imgs as well as remove invalid HTML.
3392 _fixNodes: function() {
3393 var doc = this._getDoc(),
3396 for (var v in this.invalidHTML) {
3397 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
3398 if (v.toLowerCase() != 'span') {
3399 var tags = doc.body.getElementsByTagName(v);
3401 for (var i = 0; i < tags.length; i++) {
3408 for (var h = 0; h < els.length; h++) {
3409 if (els[h].parentNode) {
3410 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) {
3411 this._swapEl(els[h], 'span', function(el) {
3412 el.className = 'yui-non';
3415 els[h].parentNode.removeChild(els[h]);
3419 var imgs = this._getDoc().getElementsByTagName('img');
3420 Dom.addClass(imgs, 'yui-img');
3424 * @method _isNonEditable
3425 * @param Event ev The Dom event being checked
3426 * @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.
3427 * 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
3428 * disable and enable the Editor's toolbar based on the noedit state.
3431 _isNonEditable: function(ev) {
3432 if (this.get('allowNoEdit')) {
3433 var el = Event.getTarget(ev);
3434 if (this._isElement(el, 'html')) {
3437 var path = this._getDomPath(el);
3438 for (var i = (path.length - 1); i > -1; i--) {
3439 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) {
3440 //if (this.toolbar.get('disabled') === false) {
3441 // this.toolbar.set('disabled', true);
3444 this._getDoc().execCommand('enableObjectResizing', false, 'false');
3447 Event.stopEvent(ev);
3451 //if (this.toolbar.get('disabled') === true) {
3452 //Should only happen once..
3453 //this.toolbar.set('disabled', false);
3455 this._getDoc().execCommand('enableObjectResizing', false, 'true');
3463 * @method _setCurrentEvent
3464 * @param {Event} ev The event to cache
3465 * @description Sets the current event property
3467 _setCurrentEvent: function(ev) {
3468 this.currentEvent = ev;
3472 * @method _handleClick
3473 * @param {Event} ev The event we are working on.
3474 * @description Handles all click events inside the iFrame document.
3476 _handleClick: function(ev) {
3477 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev });
3478 if (ret === false) {
3481 if (this._isNonEditable(ev)) {
3484 this._setCurrentEvent(ev);
3485 if (this.currentWindow) {
3488 if (this.currentWindow) {
3491 if (this.browser.webkit) {
3492 var tar =Event.getTarget(ev);
3493 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) {
3494 Event.stopEvent(ev);
3500 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev });
3504 * @method _handleMouseUp
3505 * @param {Event} ev The event we are working on.
3506 * @description Handles all mouseup events inside the iFrame document.
3508 _handleMouseUp: function(ev) {
3509 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev });
3510 if (ret === false) {
3513 if (this._isNonEditable(ev)) {
3516 //Don't set current event for mouseup.
3517 //It get's fired after a menu is closed and gives up a bogus event to work with
3518 //this._setCurrentEvent(ev);
3520 if (this.browser.opera) {
3522 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
3524 * @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.
3526 var sel = Event.getTarget(ev);
3527 if (this._isElement(sel, 'img')) {
3529 if (this.operaEvent) {
3530 clearTimeout(this.operaEvent);
3531 this.operaEvent = null;
3532 this._handleDoubleClick(ev);
3534 this.operaEvent = window.setTimeout(function() {
3535 self.operaEvent = false;
3540 //This will stop Safari from selecting the entire document if you select all the text in the editor
3541 if (this.browser.webkit || this.browser.opera) {
3542 if (this.browser.webkit) {
3543 Event.stopEvent(ev);
3547 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
3551 * @method _handleMouseDown
3552 * @param {Event} ev The event we are working on.
3553 * @description Handles all mousedown events inside the iFrame document.
3555 _handleMouseDown: function(ev) {
3556 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev });
3557 if (ret === false) {
3560 if (this._isNonEditable(ev)) {
3563 this._setCurrentEvent(ev);
3564 var sel = Event.getTarget(ev);
3565 if (this.browser.webkit && this._hasSelection()) {
3566 var _sel = this._getSelection();
3567 if (!this.browser.webkit3) {
3568 _sel.collapse(true);
3570 _sel.collapseToStart();
3573 if (this.browser.webkit && this._lastImage) {
3574 Dom.removeClass(this._lastImage, 'selected');
3575 this._lastImage = null;
3577 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) {
3578 if (this.browser.webkit) {
3579 Event.stopEvent(ev);
3580 if (this._isElement(sel, 'img')) {
3581 Dom.addClass(sel, 'selected');
3582 this._lastImage = sel;
3585 if (this.currentWindow) {
3590 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
3594 * @method _handleDoubleClick
3595 * @param {Event} ev The event we are working on.
3596 * @description Handles all doubleclick events inside the iFrame document.
3598 _handleDoubleClick: function(ev) {
3599 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev });
3600 if (ret === false) {
3603 if (this._isNonEditable(ev)) {
3606 this._setCurrentEvent(ev);
3607 var sel = Event.getTarget(ev);
3608 if (this._isElement(sel, 'img')) {
3609 this.currentElement[0] = sel;
3610 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
3611 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3612 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a
3613 this.currentElement[0] = this._hasParent(sel, 'a');
3614 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3615 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3618 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
3622 * @method _handleKeyUp
3623 * @param {Event} ev The event we are working on.
3624 * @description Handles all keyup events inside the iFrame document.
3626 _handleKeyUp: function(ev) {
3627 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev });
3628 if (ret === false) {
3631 if (this._isNonEditable(ev)) {
3634 this._setCurrentEvent(ev);
3635 switch (ev.keyCode) {
3636 case this._keyMap.SELECT_ALL.key:
3637 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) {
3641 case 32: //Space Bar
3644 case 37: //Left Arrow
3646 case 39: //Right Arrow
3647 case 40: //Down Arrow
3648 case 46: //Forward Delete
3650 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open
3651 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) {
3652 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) {
3656 if (!this.browser.ie) {
3657 if (this._nodeChangeTimer) {
3658 clearTimeout(this._nodeChangeTimer);
3661 this._nodeChangeTimer = setTimeout(function() {
3662 self._nodeChangeTimer = null;
3663 self.nodeChange.call(self);
3668 this.editorDirty = true;
3672 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
3677 * @method _handleKeyPress
3678 * @param {Event} ev The event we are working on.
3679 * @description Handles all keypress events inside the iFrame document.
3681 _handleKeyPress: function(ev) {
3682 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev });
3683 if (ret === false) {
3687 if (this.get('allowNoEdit')) {
3688 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) {
3689 if (ev && ev.keyCode && (ev.keyCode == 63272)) {
3690 //Forward delete key
3691 Event.stopEvent(ev);
3694 if (this._isNonEditable(ev)) {
3697 this._setCurrentEvent(ev);
3698 if (this.browser.opera) {
3699 if (ev.keyCode === 13) {
3700 var tar = this._getSelectedElement();
3701 if (!this._isElement(tar, 'li')) {
3702 this.execCommand('inserthtml', '<br>');
3703 Event.stopEvent(ev);
3707 if (this.browser.webkit) {
3708 if (!this.browser.webkit3) {
3709 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) {
3710 //This is CMD + z (for undo)
3711 if (this._hasParent(this._getSelectedElement(), 'li')) {
3712 Event.stopEvent(ev);
3718 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
3722 * @method _handleKeyDown
3723 * @param {Event} ev The event we are working on.
3724 * @description Handles all keydown events inside the iFrame document.
3726 _handleKeyDown: function(ev) {
3727 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev });
3728 if (ret === false) {
3731 var tar = null, _range = null;
3732 if (this._isNonEditable(ev)) {
3735 this._setCurrentEvent(ev);
3736 if (this.currentWindow) {
3739 if (this.currentWindow) {
3747 switch (ev.keyCode) {
3748 case this._keyMap.FOCUS_TOOLBAR.key:
3749 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) {
3750 var h = this.toolbar.getElementsByTagName('h2')[0];
3751 if (h && h.firstChild) {
3752 h.firstChild.focus();
3754 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) {
3755 //Focus After Element - Esc
3756 this.afterElement.focus();
3758 Event.stopEvent(ev);
3762 case this._keyMap.CREATE_LINK.key: //L
3763 if (this._hasSelection()) {
3764 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) {
3765 var makeLink = true;
3766 if (this.get('limitCommands')) {
3767 if (!this.toolbar.getButtonByValue('createlink')) {
3772 this.execCommand('createlink', '');
3773 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
3774 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
3781 case this._keyMap.UNDO.key:
3782 case this._keyMap.REDO.key:
3783 if (this._checkKey(this._keyMap.REDO, ev)) {
3786 } else if (this._checkKey(this._keyMap.UNDO, ev)) {
3792 case this._keyMap.BOLD.key:
3793 if (this._checkKey(this._keyMap.BOLD, ev)) {
3799 case this._keyMap.ITALIC.key:
3800 if (this._checkKey(this._keyMap.ITALIC, ev)) {
3806 case this._keyMap.UNDERLINE.key:
3807 if (this._checkKey(this._keyMap.UNDERLINE, ev)) {
3808 action = 'underline';
3813 if (this.browser.ie) {
3814 //Insert a tab in Internet Explorer
3815 _range = this._getRange();
3816 tar = this._getSelectedElement();
3817 if (!this._isElement(tar, 'li')) {
3819 _range.pasteHTML(' ');
3820 _range.collapse(false);
3823 Event.stopEvent(ev);
3827 if (this.browser.gecko > 1.8) {
3828 tar = this._getSelectedElement();
3829 if (this._isElement(tar, 'li')) {
3831 this._getDoc().execCommand('outdent', null, '');
3833 this._getDoc().execCommand('indent', null, '');
3836 } else if (!this._hasSelection()) {
3837 this.execCommand('inserthtml', ' ');
3839 Event.stopEvent(ev);
3843 if (this.get('ptags') && !ev.shiftKey) {
3844 if (this.browser.gecko) {
3845 tar = this._getSelectedElement();
3846 if (!this._isElement(tar, 'li')) {
3848 action = 'insertparagraph';
3849 Event.stopEvent(ev);
3852 if (this.browser.webkit) {
3853 tar = this._getSelectedElement();
3854 if (!this._hasParent(tar, 'li')) {
3856 action = 'insertparagraph';
3857 Event.stopEvent(ev);
3861 if (this.browser.ie) {
3862 //Insert a <br> instead of a <p></p> in Internet Explorer
3863 _range = this._getRange();
3864 tar = this._getSelectedElement();
3865 if (!this._isElement(tar, 'li')) {
3867 _range.pasteHTML('<br>');
3868 _range.collapse(false);
3871 Event.stopEvent(ev);
3877 if (this.browser.ie) {
3880 if (doExec && action) {
3881 this.execCommand(action, null);
3882 Event.stopEvent(ev);
3885 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
3890 * @param {Event} ev The event we are working on.
3891 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items.
3893 _listFix: function(ev) {
3894 var testLi = null, par = null, preContent = false, range = null;
3896 if (this.browser.webkit) {
3897 if (ev.keyCode && (ev.keyCode == 13)) {
3898 if (this._hasParent(this._getSelectedElement(), 'li')) {
3899 var tar = this._hasParent(this._getSelectedElement(), 'li');
3900 if (tar.previousSibling) {
3901 if (tar.firstChild && (tar.firstChild.length == 1)) {
3902 this._selectNode(tar);
3909 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) {
3910 testLi = this._getSelectedElement();
3911 if (this._hasParent(testLi, 'li')) {
3912 testLi = this._hasParent(testLi, 'li');
3913 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) {
3914 par = this._hasParent(testLi, 'ul');
3916 par = this._hasParent(testLi, 'ol');
3918 if (this._isElement(par.previousSibling, 'li')) {
3919 par.removeChild(testLi);
3920 par.parentNode.insertBefore(testLi, par.nextSibling);
3921 if (this.browser.ie) {
3922 range = this._getDoc().body.createTextRange();
3923 range.moveToElementText(testLi);
3924 range.collapse(false);
3927 if (this.browser.webkit) {
3928 this._selectNode(testLi.firstChild);
3930 Event.stopEvent(ev);
3936 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) {
3937 var preLi = this._getSelectedElement();
3938 if (this._hasParent(preLi, 'li')) {
3939 preContent = this._hasParent(preLi, 'li').innerHTML;
3941 if (this.browser.webkit) {
3942 this._getDoc().execCommand('inserttext', false, '\t');
3944 testLi = this._getSelectedElement();
3945 if (this._hasParent(testLi, 'li')) {
3946 par = this._hasParent(testLi, 'li');
3947 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase());
3948 if (this.browser.webkit) {
3949 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par);
3950 //Remove the span element that Safari puts in
3952 par.removeChild(span[0]);
3953 par.innerHTML = Lang.trim(par.innerHTML);
3954 //Put the HTML from the LI into this new LI
3956 par.innerHTML = '<span class="yui-non">' + preContent + '</span> ';
3958 par.innerHTML = '<span class="yui-non"> </span> ';
3963 par.innerHTML = preContent + ' ';
3965 par.innerHTML = ' ';
3969 par.parentNode.replaceChild(newUl, par);
3970 newUl.appendChild(par);
3971 if (this.browser.webkit) {
3972 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length);
3973 if (!this.browser.webkit3) {
3974 par.parentNode.parentNode.style.display = 'list-item';
3975 setTimeout(function() {
3976 par.parentNode.parentNode.style.display = 'block';
3979 } else if (this.browser.ie) {
3980 range = this._getDoc().body.createTextRange();
3981 range.moveToElementText(par);
3982 range.collapse(false);
3985 this._selectNode(par);
3987 Event.stopEvent(ev);
3989 if (this.browser.webkit) {
3990 Event.stopEvent(ev);
3996 * @method nodeChange
3997 * @param {Boolean} force Optional paramenter to skip the threshold counter
3998 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
4000 nodeChange: function(force) {
4003 if (this.get('nodeChangeDelay')) {
4004 window.setTimeout(function() {
4005 NCself._nodeChange.apply(NCself, arguments);
4013 * @method _nodeChange
4014 * @param {Boolean} force Optional paramenter to skip the threshold counter
4015 * @description Fired from nodeChange in a setTimeout.
4017 _nodeChange: function(force) {
4018 var threshold = parseInt(this.get('nodeChangeThreshold'), 10),
4019 thisNodeChange = Math.round(new Date().getTime() / 1000),
4022 if (force === true) {
4023 this._lastNodeChange = 0;
4026 if ((this._lastNodeChange + threshold) < thisNodeChange) {
4027 if (this._fixNodesTimer === null) {
4028 this._fixNodesTimer = window.setTimeout(function() {
4029 self._fixNodes.call(self);
4030 self._fixNodesTimer = null;
4034 this._lastNodeChange = thisNodeChange;
4035 if (this.currentEvent) {
4037 this._lastNodeChangeEvent = this.currentEvent.type;
4041 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
4042 if (beforeNodeChange === false) {
4045 if (this.get('dompath')) {
4046 window.setTimeout(function() {
4047 self._writeDomPath.call(self);
4050 //Check to see if we are disabled before continuing
4051 if (!this.get('disabled')) {
4052 if (this.STOP_NODE_CHANGE) {
4053 //Reset this var for next action
4054 this.STOP_NODE_CHANGE = false;
4057 var sel = this._getSelection(),
4058 range = this._getRange(),
4059 el = this._getSelectedElement(),
4060 fn_button = this.toolbar.getButtonByValue('fontname'),
4061 fs_button = this.toolbar.getButtonByValue('fontsize'),
4062 undo_button = this.toolbar.getButtonByValue('undo'),
4063 redo_button = this.toolbar.getButtonByValue('redo');
4065 //Handle updating the toolbar with active buttons
4067 if (this._lastButton) {
4068 _ex[this._lastButton.id] = true;
4069 //this._lastButton = null;
4071 if (!this._isElement(el, 'body')) {
4073 _ex[fn_button.get('id')] = true;
4076 _ex[fs_button.get('id')] = true;
4080 delete _ex[redo_button.get('id')];
4082 this.toolbar.resetAllButtons(_ex);
4084 //Handle disabled buttons
4085 for (var d = 0; d < this._disabled.length; d++) {
4086 var _button = this.toolbar.getButtonByValue(this._disabled[d]);
4087 if (_button && _button.get) {
4088 if (this._lastButton && (_button.get('id') === this._lastButton.id)) {
4091 if (!this._hasSelection() && !this.get('insert')) {
4092 switch (this._disabled[d]) {
4097 //No Selection - disable
4098 this.toolbar.disableButton(_button);
4101 if (!this._alwaysDisabled[this._disabled[d]]) {
4102 this.toolbar.enableButton(_button);
4105 if (!this._alwaysEnabled[this._disabled[d]]) {
4106 this.toolbar.deselectButton(_button);
4111 var path = this._getDomPath();
4112 var tag = null, cmd = null;
4113 for (var i = 0; i < path.length; i++) {
4114 tag = path[i].tagName.toLowerCase();
4115 if (path[i].getAttribute('tag')) {
4116 tag = path[i].getAttribute('tag').toLowerCase();
4118 cmd = this._tag2cmd[tag];
4119 if (cmd === undefined) {
4122 if (!Lang.isArray(cmd)) {
4126 //Bold and Italic styles
4127 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
4128 cmd[cmd.length] = 'bold';
4130 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
4131 cmd[cmd.length] = 'italic';
4133 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
4134 cmd[cmd.length] = 'underline';
4136 if (path[i].style.textDecoration.toLowerCase() == 'line-through') {
4137 cmd[cmd.length] = 'strikethrough';
4139 if (cmd.length > 0) {
4140 for (var j = 0; j < cmd.length; j++) {
4141 this.toolbar.selectButton(cmd[j]);
4142 this.toolbar.enableButton(cmd[j]);
4146 switch (path[i].style.textAlign.toLowerCase()) {
4151 var alignType = path[i].style.textAlign.toLowerCase();
4152 if (path[i].style.textAlign.toLowerCase() == 'justify') {
4155 this.toolbar.selectButton('justify' + alignType);
4156 this.toolbar.enableButton('justify' + alignType);
4162 //Reset Font Family and Size to the inital configs
4164 var family = fn_button._configs.label._initialConfig.value;
4165 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>');
4166 this._updateMenuChecked('fontname', family);
4170 fs_button.set('label', fs_button._configs.label._initialConfig.value);
4173 var hd_button = this.toolbar.getButtonByValue('heading');
4175 hd_button.set('label', hd_button._configs.label._initialConfig.value);
4176 this._updateMenuChecked('heading', 'none');
4178 var img_button = this.toolbar.getButtonByValue('insertimage');
4179 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) {
4180 this.toolbar.disableButton(img_button);
4182 if (this._lastButton && this._lastButton.isSelected) {
4183 this.toolbar.deselectButton(this._lastButton.id);
4185 this._undoNodeChange();
4189 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
4193 * @method _updateMenuChecked
4194 * @param {Object} button The command identifier of the button you want to check
4195 * @param {String} value The value of the menu item you want to check
4196 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
4197 * @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.
4199 _updateMenuChecked: function(button, value, tbar) {
4201 tbar = this.toolbar;
4203 var _button = tbar.getButtonByValue(button);
4204 _button.checkValue(value);
4208 * @method _handleToolbarClick
4209 * @param {Event} ev The event that triggered the button click
4210 * @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.
4212 _handleToolbarClick: function(ev) {
4215 var cmd = ev.button.value;
4216 if (ev.button.menucmd) {
4218 cmd = ev.button.menucmd;
4220 this._lastButton = ev.button;
4221 if (this.STOP_EXEC_COMMAND) {
4222 this.STOP_EXEC_COMMAND = false;
4225 this.execCommand(cmd, value);
4226 if (!this.browser.webkit) {
4228 setTimeout(function() {
4229 Fself._focusWindow.call(Fself);
4233 Event.stopEvent(ev);
4237 * @method _setupAfterElement
4238 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
4240 _setupAfterElement: function() {
4241 if (!this.beforeElement) {
4242 this.beforeElement = document.createElement('h2');
4243 this.beforeElement.className = 'yui-editor-skipheader';
4244 this.beforeElement.tabIndex = '-1';
4245 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4246 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4248 if (!this.afterElement) {
4249 this.afterElement = document.createElement('h2');
4250 this.afterElement.className = 'yui-editor-skipheader';
4251 this.afterElement.tabIndex = '-1';
4252 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
4253 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
4258 * @method _disableEditor
4259 * @param {Boolean} disabled Pass true to disable, false to enable
4260 * @description Creates a mask to place over the Editor.
4262 _disableEditor: function(disabled) {
4264 this._removeEditorEvents();
4266 if (!!this.browser.ie) {
4267 this._setDesignMode('off');
4270 this.toolbar.set('disabled', true);
4272 this._mask = document.createElement('DIV');
4273 Dom.setStyle(this._mask, 'height', '100%');
4274 Dom.setStyle(this._mask, 'width', '100%');
4275 Dom.setStyle(this._mask, 'position', 'absolute');
4276 Dom.setStyle(this._mask, 'top', '0');
4277 Dom.setStyle(this._mask, 'left', '0');
4278 Dom.setStyle(this._mask, 'opacity', '.5');
4279 Dom.addClass(this._mask, 'yui-editor-masked');
4280 this.get('iframe').get('parentNode').appendChild(this._mask);
4283 this._initEditorEvents();
4285 this._mask.parentNode.removeChild(this._mask);
4288 this.toolbar.set('disabled', false);
4290 this._setDesignMode('on');
4291 this._focusWindow();
4293 window.setTimeout(function() {
4294 self.nodeChange.call(self);
4300 * @property SEP_DOMPATH
4301 * @description The value to place in between the Dom path items
4306 * @property STR_LEAVE_EDITOR
4307 * @description The accessibility string for the element after the iFrame
4310 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
4312 * @property STR_BEFORE_EDITOR
4313 * @description The accessibility string for the element before the iFrame
4316 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>',
4318 * @property STR_TITLE
4319 * @description The Title of the HTML document that is created in the iFrame
4322 STR_TITLE: 'Rich Text Area.',
4324 * @property STR_IMAGE_HERE
4325 * @description The text to place in the URL textbox when using the blankimage.
4328 STR_IMAGE_HERE: 'Image URL Here',
4330 * @property STR_LINK_URL
4331 * @description The label string for the Link URL.
4334 STR_LINK_URL: 'Link URL',
4337 * @property STOP_EXEC_COMMAND
4338 * @description Set to true when you want the default execCommand function to not process anything
4341 STOP_EXEC_COMMAND: false,
4344 * @property STOP_NODE_CHANGE
4345 * @description Set to true when you want the default nodeChange function to not process anything
4348 STOP_NODE_CHANGE: false,
4351 * @property CLASS_NOEDIT
4352 * @description CSS class applied to elements that are not editable.
4355 CLASS_NOEDIT: 'yui-noedit',
4358 * @property CLASS_CONTAINER
4359 * @description Default CSS class to apply to the editors container element
4362 CLASS_CONTAINER: 'yui-editor-container',
4365 * @property CLASS_EDITABLE
4366 * @description Default CSS class to apply to the editors iframe element
4369 CLASS_EDITABLE: 'yui-editor-editable',
4372 * @property CLASS_EDITABLE_CONT
4373 * @description Default CSS class to apply to the editors iframe's parent element
4376 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
4379 * @property CLASS_PREFIX
4380 * @description Default prefix for dynamically created class names
4383 CLASS_PREFIX: 'yui-editor',
4386 * @description Standard browser detection
4389 browser: function() {
4390 var br = YAHOO.env.ua;
4392 if (br.webkit >= 420) {
4393 br.webkit3 = br.webkit;
4399 if (navigator.userAgent.indexOf('Macintosh') !== -1) {
4407 * @description The Editor class' initialization method
4409 init: function(p_oElement, p_oAttributes) {
4411 if (!this._defaultToolbar) {
4412 this._defaultToolbar = {
4414 titlebar: 'Text Editing Tools',
4417 { group: 'fontstyle', label: 'Font Name and Size',
4419 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
4421 { text: 'Arial', checked: true },
4422 { text: 'Arial Black' },
4423 { text: 'Comic Sans MS' },
4424 { text: 'Courier New' },
4425 { text: 'Lucida Console' },
4427 { text: 'Times New Roman' },
4428 { text: 'Trebuchet MS' },
4432 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
4435 { type: 'separator' },
4436 { group: 'textstyle', label: 'Font Style',
4438 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
4439 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
4440 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
4441 { type: 'push', label: 'Strike Through', value: 'strikethrough' },
4442 { type: 'separator' },
4443 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
4444 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }
4448 { type: 'separator' },
4449 { group: 'indentlist', label: 'Lists',
4451 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
4452 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
4455 { type: 'separator' },
4456 { group: 'insertitem', label: 'Insert Item',
4458 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
4459 { type: 'push', label: 'Insert Image', value: 'insertimage' }
4466 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes);
4467 YAHOO.widget.EditorInfo._instances[this.get('id')] = this;
4470 this.currentElement = [];
4471 this.on('contentReady', function() {
4472 this.DOMReady = true;
4478 * @method initAttributes
4479 * @description Initializes all of the configuration attributes used to create
4481 * @param {Object} attr Object literal specifying a set of
4482 * configuration attributes used to create the editor.
4484 initAttributes: function(attr) {
4485 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr);
4489 * @config nodeChangeDelay
4490 * @description Do we wrap the nodeChange method in a timeout for performance, default: true.
4494 this.setAttributeConfig('nodeChangeDelay', {
4495 value: ((attr.nodeChangeDelay === false) ? false : true)
4499 * @description The max number of undo levels to store.
4503 this.setAttributeConfig('maxUndo', {
4505 value: attr.maxUndo || 30
4510 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>)
4514 this.setAttributeConfig('ptags', {
4516 value: attr.ptags || false
4520 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor.
4524 this.setAttributeConfig('insert', {
4526 value: attr.insert || false,
4527 method: function(insert) {
4535 var tmp = this._defaultToolbar.buttons;
4536 for (var i = 0; i < tmp.length; i++) {
4537 if (tmp[i].buttons) {
4538 for (var a = 0; a < tmp[i].buttons.length; a++) {
4539 if (tmp[i].buttons[a].value) {
4540 if (buttons[tmp[i].buttons[a].value]) {
4541 delete tmp[i].buttons[a].disabled;
4552 * @description Used when dynamically creating the Editor from Javascript with no default textarea.
4553 * We will create one and place it in this container. If no container is passed we will append to document.body.
4557 this.setAttributeConfig('container', {
4559 value: attr.container || false
4563 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds.
4567 this.setAttributeConfig('plainText', {
4569 value: attr.plainText || false
4574 * @description Internal config for holding the iframe element.
4578 this.setAttributeConfig('iframe', {
4585 * @description Internal config for holding the textarea element (replaced with element).
4589 this.setAttributeConfig('textarea', {
4596 * @description Internal config for holding a reference to the container to append a dynamic editor to.
4600 this.setAttributeConfig('container', {
4605 * @config nodeChangeThreshold
4606 * @description The number of seconds that need to be in between nodeChange processing
4610 this.setAttributeConfig('nodeChangeThreshold', {
4611 value: attr.nodeChangeThreshold || 3,
4612 validator: YAHOO.lang.isNumber
4615 * @config allowNoEdit
4616 * @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.
4617 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key.
4621 this.setAttributeConfig('allowNoEdit', {
4622 value: attr.allowNoEdit || false,
4623 validator: YAHOO.lang.isBoolean
4626 * @config limitCommands
4627 * @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.
4631 this.setAttributeConfig('limitCommands', {
4632 value: attr.limitCommands || false,
4633 validator: YAHOO.lang.isBoolean
4636 * @config element_cont
4637 * @description Internal config for the editors container
4641 this.setAttributeConfig('element_cont', {
4642 value: attr.element_cont
4646 * @config editor_wrapper
4647 * @description The outter wrapper for the entire editor.
4651 this.setAttributeConfig('editor_wrapper', {
4652 value: attr.editor_wrapper || null,
4657 * @description The height of the editor iframe container, not including the toolbar..
4658 * @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
4661 this.setAttributeConfig('height', {
4662 value: attr.height || Dom.getStyle(self.get('element'), 'height'),
4663 method: function(height) {
4664 if (this._rendered) {
4665 //We have been rendered, change the height
4666 if (this.get('animate')) {
4667 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), {
4669 to: parseInt(height, 10)
4674 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height);
4680 * @config autoHeight
4681 * @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.
4683 * @type Boolean || Number
4685 this.setAttributeConfig('autoHeight', {
4686 value: attr.autoHeight || false,
4687 method: function(a) {
4689 if (this.get('iframe')) {
4690 this.get('iframe').get('element').setAttribute('scrolling', 'no');
4692 this.on('afterNodeChange', this._handleAutoHeight, this, true);
4693 this.on('editorKeyDown', this._handleAutoHeight, this, true);
4694 this.on('editorKeyPress', this._handleAutoHeight, this, true);
4696 if (this.get('iframe')) {
4697 this.get('iframe').get('element').setAttribute('scrolling', 'auto');
4699 this.unsubscribe('afterNodeChange', this._handleAutoHeight);
4700 this.unsubscribe('editorKeyDown', this._handleAutoHeight);
4701 this.unsubscribe('editorKeyPress', this._handleAutoHeight);
4707 * @description The width of the editor container.
4708 * @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
4711 this.setAttributeConfig('width', {
4712 value: attr.width || Dom.getStyle(this.get('element'), 'width'),
4713 method: function(width) {
4714 if (this._rendered) {
4715 //We have been rendered, change the width
4716 if (this.get('animate')) {
4717 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), {
4719 to: parseInt(width, 10)
4724 this.get('element_cont').setStyle('width', width);
4731 * @attribute blankimage
4732 * @description The URL for the image placeholder to put in when inserting an image.
4733 * @default The yahooapis.com address for the current release + 'assets/blankimage.png'
4736 this.setAttributeConfig('blankimage', {
4737 value: attr.blankimage || this._getBlankImage()
4741 * @description The Base CSS used to format the content of the editor
4742 * @default <code><pre>html {
4747 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
4751 text-decoration: underline;
4754 .warning-localfile {
4755 border-bottom: 1px dashed red !important;
4758 cursor: wait !important;
4760 img.selected { //Safari image selection
4761 border: 2px dotted #808080;
4764 cursor: pointer !important;
4770 this.setAttributeConfig('css', {
4771 value: attr.css || this._defaultCSS,
4776 * @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)
4777 * @default This HTML requires a few things if you are to override:
4778 <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>
4779 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
4784 <title>{TITLE}</title>
4785 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4796 <body onload="document.body._rteLoaded = true;">
4804 this.setAttributeConfig('html', {
4805 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>',
4810 * @attribute extracss
4811 * @description Extra user defined css to load after the default SimpleEditor CSS
4815 this.setAttributeConfig('extracss', {
4816 value: attr.extracss || '',
4821 * @attribute handleSubmit
4822 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
4823 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
4824 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
4828 this.setAttributeConfig('handleSubmit', {
4829 value: attr.handleSubmit || false,
4830 method: function(exec) {
4831 if (this.get('element').form) {
4832 if (!this._formButtons) {
4833 this._formButtons = [];
4836 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true);
4837 var i = this.get('element').form.getElementsByTagName('input');
4838 for (var s = 0; s < i.length; s++) {
4839 var type = i[s].getAttribute('type');
4840 if (type && (type.toLowerCase() == 'submit')) {
4841 Event.on(i[s], 'click', this._handleFormButtonClick, this, true);
4842 this._formButtons[this._formButtons.length] = i[s];
4846 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit);
4847 if (this._formButtons) {
4848 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick);
4855 * @attribute disabled
4856 * @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.
4857 All Toolbar buttons are also disabled so they cannot be used.
4862 this.setAttributeConfig('disabled', {
4864 method: function(disabled) {
4865 if (this._rendered) {
4866 this._disableEditor(disabled);
4872 * @description When save HTML is called, this element will be updated as well as the source of data.
4876 this.setAttributeConfig('saveEl', {
4877 value: this.get('element')
4880 * @config toolbar_cont
4881 * @description Internal config for the toolbars container
4885 this.setAttributeConfig('toolbar_cont', {
4890 * @attribute toolbar
4891 * @description The default toolbar config.
4894 this.setAttributeConfig('toolbar', {
4895 value: attr.toolbar || this._defaultToolbar,
4897 method: function(toolbar) {
4898 if (!toolbar.buttonType) {
4899 toolbar.buttonType = this._defaultToolbar.buttonType;
4901 this._defaultToolbar = toolbar;
4905 * @attribute animate
4906 * @description Should the editor animate window movements
4907 * @default false unless Animation is found, then true
4910 this.setAttributeConfig('animate', {
4911 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false),
4912 validator: function(value) {
4914 if (!YAHOO.util.Anim) {
4922 * @description A reference to the panel we are using for windows.
4926 this.setAttributeConfig('panel', {
4929 validator: function(value) {
4931 if (!YAHOO.widget.Overlay) {
4938 * @attribute focusAtStart
4939 * @description Should we focus the window when the content is ready?
4943 this.setAttributeConfig('focusAtStart', {
4944 value: attr.focusAtStart || false,
4946 method: function(fs) {
4948 this.on('editorContentLoaded', function() {
4950 setTimeout(function() {
4951 self._focusWindow.call(self, true);
4952 self.editorDirty = false;
4959 * @attribute dompath
4960 * @description Toggle the display of the current Dom path below the editor
4964 this.setAttributeConfig('dompath', {
4965 value: attr.dompath || false,
4966 method: function(dompath) {
4967 if (dompath && !this.dompath) {
4968 this.dompath = document.createElement('DIV');
4969 this.dompath.id = this.get('id') + '_dompath';
4970 Dom.addClass(this.dompath, 'dompath');
4971 this.get('element_cont').get('firstChild').appendChild(this.dompath);
4972 if (this.get('iframe')) {
4973 this._writeDomPath();
4975 } else if (!dompath && this.dompath) {
4976 this.dompath.parentNode.removeChild(this.dompath);
4977 this.dompath = null;
4983 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml
4984 * @default "semantic"
4987 this.setAttributeConfig('markup', {
4988 value: attr.markup || 'semantic',
4989 validator: function(markup) {
4990 switch (markup.toLowerCase()) {
5001 * @attribute removeLineBreaks
5002 * @description Should we remove linebreaks and extra spaces on cleanup
5006 this.setAttributeConfig('removeLineBreaks', {
5007 value: attr.removeLineBreaks || false,
5008 validator: YAHOO.lang.isBoolean
5013 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy.
5014 * @type {Boolean/String}
5016 this.setAttributeConfig('drag', {
5018 value: attr.drag || false
5023 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig
5024 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar.
5027 this.setAttributeConfig('resize', {
5029 value: attr.resize || false
5034 * @method _getBlankImage
5035 * @description Retrieves the full url of the image to use as the blank image.
5036 * @return {String} The URL to the blank image
5038 _getBlankImage: function() {
5039 if (!this.DOMReady) {
5040 this._queue[this._queue.length] = ['_getBlankImage', arguments];
5044 if (!this._blankImageLoaded) {
5045 if (YAHOO.widget.EditorInfo.blankImage) {
5046 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage);
5047 this._blankImageLoaded = true;
5049 var div = document.createElement('div');
5050 div.style.position = 'absolute';
5051 div.style.top = '-9999px';
5052 div.style.left = '-9999px';
5053 div.className = this.CLASS_PREFIX + '-blankimage';
5054 document.body.appendChild(div);
5055 img = YAHOO.util.Dom.getStyle(div, 'background-image');
5056 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
5058 img = img.replace('app:/', '');
5059 this.set('blankimage', img);
5060 this._blankImageLoaded = true;
5061 div.parentNode.removeChild(div);
5062 YAHOO.widget.EditorInfo.blankImage = img;
5065 img = this.get('blankimage');
5071 * @method _handleAutoHeight
5072 * @description Handles resizing the editor's height based on the content
5074 _handleAutoHeight: function() {
5075 var doc = this._getDoc(),
5077 docEl = doc.documentElement;
5079 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10);
5080 var newHeight = body.scrollHeight;
5081 if (this.browser.webkit) {
5082 newHeight = docEl.scrollHeight;
5084 if (newHeight < parseInt(this.get('height'), 10)) {
5085 newHeight = parseInt(this.get('height'), 10);
5087 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) {
5088 Dom.setStyle(this.get('editor_wrapper'), 'height', newHeight + 'px');
5089 if (this.browser.ie) {
5090 //Internet Explorer needs this
5091 this.get('iframe').setStyle('height', '99%');
5092 this.get('iframe').setStyle('zoom', '1');
5094 window.setTimeout(function() {
5095 self.get('iframe').setStyle('height', '100%');
5102 * @property _formButtons
5103 * @description Array of buttons that are in the Editor's parent form (for handleSubmit)
5109 * @property _formButtonClicked
5110 * @description The form button that was clicked to submit the form.
5113 _formButtonClicked: null,
5116 * @method _handleFormButtonClick
5117 * @description The click listener assigned to each submit button in the Editor's parent form.
5118 * @param {Event} ev The click event
5120 _handleFormButtonClick: function(ev) {
5121 var tar = Event.getTarget(ev);
5122 this._formButtonClicked = tar;
5126 * @method _handleFormSubmit
5127 * @description Handles the form submission.
5128 * @param {Object} ev The Form Submit Event
5130 _handleFormSubmit: function(ev) {
5133 var form = this.get('element').form,
5134 tar = this._formButtonClicked || false;
5136 Event.removeListener(form, 'submit', this._handleFormSubmit);
5137 if (YAHOO.env.ua.ie) {
5138 //form.fireEvent("onsubmit");
5139 if (tar && !tar.disabled) {
5142 } else { // Gecko, Opera, and Safari
5143 if (tar && !tar.disabled) {
5146 var oEvent = document.createEvent("HTMLEvents");
5147 oEvent.initEvent("submit", true, true);
5148 form.dispatchEvent(oEvent);
5149 if (YAHOO.env.ua.webkit) {
5150 if (YAHOO.lang.isFunction(form.submit)) {
5156 //Removed this, not need since removing Safari 2.x
5157 //Event.stopEvent(ev);
5161 * @method _handleFontSize
5162 * @description Handles the font size button in the toolbar.
5163 * @param {Object} o Object returned from Toolbar's buttonClick Event
5165 _handleFontSize: function(o) {
5166 var button = this.toolbar.getButtonById(o.button.id);
5167 var value = button.get('label') + 'px';
5168 this.execCommand('fontsize', value);
5169 this.STOP_EXEC_COMMAND = true;
5173 * @description Handles the colorpicker buttons in the toolbar.
5174 * @param {Object} o Object returned from Toolbar's buttonClick Event
5176 _handleColorPicker: function(o) {
5178 var value = '#' + o.color;
5179 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
5180 this.execCommand(cmd, value);
5185 * @method _handleAlign
5186 * @description Handles the alignment buttons in the toolbar.
5187 * @param {Object} o Object returned from Toolbar's buttonClick Event
5189 _handleAlign: function(o) {
5191 for (var i = 0; i < o.button.menu.length; i++) {
5192 if (o.button.menu[i].value == o.button.value) {
5193 cmd = o.button.menu[i].value;
5196 var value = this._getSelection();
5198 this.execCommand(cmd, value);
5199 this.STOP_EXEC_COMMAND = true;
5203 * @method _handleAfterNodeChange
5204 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
5206 _handleAfterNodeChange: function() {
5207 var path = this._getDomPath(),
5212 fn_button = this.toolbar.getButtonByValue('fontname'),
5213 fs_button = this.toolbar.getButtonByValue('fontsize'),
5214 hd_button = this.toolbar.getButtonByValue('heading');
5216 for (var i = 0; i < path.length; i++) {
5219 var tag = elm.tagName.toLowerCase();
5222 if (elm.getAttribute('tag')) {
5223 tag = elm.getAttribute('tag');
5226 family = elm.getAttribute('face');
5227 if (Dom.getStyle(elm, 'font-family')) {
5228 family = Dom.getStyle(elm, 'font-family');
5230 family = family.replace(/'/g, '');
5233 if (tag.substring(0, 1) == 'h') {
5235 for (var h = 0; h < hd_button._configs.menu.value.length; h++) {
5236 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) {
5237 hd_button.set('label', hd_button._configs.menu.value[h].text);
5240 this._updateMenuChecked('heading', tag);
5246 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
5247 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
5249 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
5253 family = fn_button._configs.label._initialConfig.value;
5255 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>';
5256 if (fn_button.get('label') != familyLabel) {
5257 fn_button.set('label', familyLabel);
5258 this._updateMenuChecked('fontname', family);
5263 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10);
5264 if ((fontsize === null) || isNaN(fontsize)) {
5265 fontsize = fs_button._configs.label._initialConfig.value;
5267 fs_button.set('label', ''+fontsize);
5270 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) {
5271 this.toolbar.enableButton(fn_button);
5272 this.toolbar.enableButton(fs_button);
5273 this.toolbar.enableButton('forecolor');
5274 this.toolbar.enableButton('backcolor');
5276 if (this._isElement(elm, 'img')) {
5277 if (YAHOO.widget.Overlay) {
5278 this.toolbar.enableButton('createlink');
5281 if (this._hasParent(elm, 'blockquote')) {
5282 this.toolbar.selectButton('indent');
5283 this.toolbar.disableButton('indent');
5284 this.toolbar.enableButton('outdent');
5286 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) {
5287 this.toolbar.disableButton('indent');
5289 this._lastButton = null;
5294 * @method _handleInsertImageClick
5295 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
5297 _handleInsertImageClick: function() {
5298 if (this.get('limitCommands')) {
5299 if (!this.toolbar.getButtonByValue('insertimage')) {
5304 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5305 this.on('afterExecCommand', function() {
5306 var el = this.currentElement[0],
5309 el = this._getSelectedElement();
5312 if (el.getAttribute('src')) {
5313 src = el.getAttribute('src', 2);
5314 if (src.indexOf(this.get('blankimage')) != -1) {
5315 src = this.STR_IMAGE_HERE;
5319 var str = prompt(this.STR_LINK_URL + ': ', src);
5320 if ((str !== '') && (str !== null)) {
5321 el.setAttribute('src', str);
5322 } else if (str === null) {
5323 el.parentNode.removeChild(el);
5324 this.currentElement = [];
5328 this.toolbar.set('disabled', false);
5333 * @method _handleInsertImageWindowClose
5334 * @description Handles the closing of the Image Properties Window.
5336 _handleInsertImageWindowClose: function() {
5341 * @method _isLocalFile
5342 * @param {String} url THe url/string to check
5343 * @description Checks to see if a string (href or img src) is possibly a local file reference..
5345 _isLocalFile: function(url) {
5346 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
5353 * @method _handleCreateLinkClick
5354 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
5356 _handleCreateLinkClick: function() {
5357 if (this.get('limitCommands')) {
5358 if (!this.toolbar.getButtonByValue('createlink')) {
5363 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing
5365 this.on('afterExecCommand', function() {
5366 var el = this.currentElement[0],
5370 if (el.getAttribute('href', 2) !== null) {
5371 url = el.getAttribute('href', 2);
5374 var str = prompt(this.STR_LINK_URL + ': ', url);
5375 if ((str !== '') && (str !== null)) {
5377 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5378 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
5379 //Found an @ sign, prefix with mailto:
5380 urlValue = 'mailto:' + urlValue;
5382 /* :// not found adding */
5383 if (urlValue.substring(0, 1) != '#') {
5384 //urlValue = 'http:/'+'/' + urlValue;
5388 el.setAttribute('href', urlValue);
5389 } else if (str !== null) {
5390 var _span = this._getDoc().createElement('span');
5391 _span.innerHTML = el.innerHTML;
5392 Dom.addClass(_span, 'yui-non');
5393 el.parentNode.replaceChild(_span, el);
5396 this.toolbar.set('disabled', false);
5402 * @method _handleCreateLinkWindowClose
5403 * @description Handles the closing of the Link Properties Window.
5405 _handleCreateLinkWindowClose: function() {
5407 this.currentElement = [];
5411 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load.
5413 render: function() {
5414 if (this._rendered) {
5417 if (!this.DOMReady) {
5418 this._queue[this._queue.length] = ['render', arguments];
5421 if (this.get('element')) {
5422 if (this.get('element').tagName) {
5423 this._textarea = true;
5424 if (this.get('element').tagName.toLowerCase() !== 'textarea') {
5425 this._textarea = false;
5433 this._rendered = true;
5435 window.setTimeout(function() {
5436 self._render.call(self);
5442 * @description Causes the toolbar and the editor to render and replace the textarea.
5444 _render: function() {
5446 this.set('textarea', this.get('element'));
5448 this.get('element_cont').setStyle('display', 'none');
5449 this.get('element_cont').addClass(this.CLASS_CONTAINER);
5451 this.set('iframe', this._createIframe());
5452 window.setTimeout(function() {
5453 self._setInitialContent.call(self);
5456 this.get('editor_wrapper').appendChild(this.get('iframe').get('element'));
5458 if (this.get('disabled')) {
5459 this._disableEditor(true);
5462 var tbarConf = this.get('toolbar');
5463 //Create Toolbar instance
5464 if (tbarConf instanceof Toolbar) {
5465 this.toolbar = tbarConf;
5466 //Set the toolbar to disabled until content is loaded
5467 this.toolbar.set('disabled', true);
5469 //Set the toolbar to disabled until content is loaded
5470 tbarConf.disabled = true;
5471 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
5474 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
5477 this.toolbar.on('toolbarCollapsed', function() {
5478 if (this.currentWindow) {
5482 this.toolbar.on('toolbarExpanded', function() {
5483 if (this.currentWindow) {
5487 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true);
5489 this.toolbar.on('colorPickerClicked', function(o) {
5490 this._handleColorPicker(o);
5491 return false; //Stop the buttonClick event
5494 this.toolbar.on('alignClick', this._handleAlign, this, true);
5495 this.on('afterNodeChange', this._handleAfterNodeChange, this, true);
5496 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true);
5497 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true);
5498 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true);
5499 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true);
5502 //Replace Textarea with editable area
5503 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
5506 this.setStyle('visibility', 'hidden');
5507 this.setStyle('position', 'absolute');
5508 this.setStyle('top', '-9999px');
5509 this.setStyle('left', '-9999px');
5510 this.get('element_cont').appendChild(this.get('element'));
5511 this.get('element_cont').setStyle('display', 'block');
5514 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
5515 this.get('iframe').addClass(this.CLASS_EDITABLE);
5517 //Set height and width of editor container
5518 this.get('element_cont').setStyle('width', this.get('width'));
5519 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
5521 this.get('iframe').setStyle('width', '100%'); //WIDTH
5522 this.get('iframe').setStyle('height', '100%');
5526 window.setTimeout(function() {
5527 self._setupAfterElement.call(self);
5529 this.fireEvent('afterRender', { type: 'afterRender', target: this });
5532 * @method execCommand
5533 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
5534 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
5535 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
5537 execCommand: function(action, value) {
5538 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
5539 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) {
5540 this.STOP_EXEC_COMMAND = false;
5543 this._lastCommand = action;
5544 this._setMarkupType(action);
5545 if (this.browser.ie) {
5546 this._getWindow().focus();
5550 if (this.get('limitCommands')) {
5551 if (!this.toolbar.getButtonByValue(action)) {
5556 this.editorDirty = true;
5558 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) {
5559 var retValue = this['cmd_' + action.toLowerCase()](value);
5562 action = retValue[1];
5565 value = retValue[2];
5570 this._getDoc().execCommand(action, false, value);
5575 this.on('afterExecCommand', function() {
5576 this.unsubscribeAll('afterExecCommand');
5579 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
5582 /* {{{ Command Overrides */
5585 * @method cmd_underline
5586 * @param value Value passed from the execCommand method
5587 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used.
5589 cmd_underline: function(value) {
5590 if (!this.browser.webkit) {
5591 var el = this._getSelectedElement();
5592 if (el && this._isElement(el, 'span')) {
5593 if (el.style.textDecoration == 'underline') {
5594 el.style.textDecoration = 'none';
5596 el.style.textDecoration = 'underline';
5604 * @method cmd_backcolor
5605 * @param value Value passed from the execCommand method
5606 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used.
5608 cmd_backcolor: function(value) {
5610 el = this._getSelectedElement(),
5611 action = 'backcolor';
5613 if (this.browser.gecko || this.browser.opera) {
5614 this._setEditorStyle(true);
5615 action = 'hilitecolor';
5618 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5619 Dom.setStyle(el, 'background-color', value);
5620 this._selectNode(el);
5622 } else if (!this._isElement(el, 'body') && this._hasSelection()) {
5623 Dom.setStyle(el, 'background-color', value);
5624 this._selectNode(el);
5627 if (this.get('insert')) {
5628 el = this._createInsertElement({ backgroundColor: value });
5630 this._createCurrentElement('span', { backgroundColor: value });
5631 this._selectNode(this.currentElement[0]);
5636 return [exec, action];
5639 * @method cmd_forecolor
5640 * @param value Value passed from the execCommand method
5641 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used.
5643 cmd_forecolor: function(value) {
5645 el = this._getSelectedElement();
5648 if (!this._isElement(el, 'body') && !this._hasSelection()) {
5649 Dom.setStyle(el, 'color', value);
5650 this._selectNode(el);
5652 } else if (!this._isElement(el, 'body') && this._hasSelection()) {
5653 Dom.setStyle(el, 'color', value);
5654 this._selectNode(el);
5657 if (this.get('insert')) {
5658 el = this._createInsertElement({ color: value });
5660 this._createCurrentElement('span', { color: value });
5661 this._selectNode(this.currentElement[0]);
5668 * @method cmd_unlink
5669 * @param value Value passed from the execCommand method
5670 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used.
5672 cmd_unlink: function(value) {
5673 this._swapEl(this.currentElement[0], 'span', function(el) {
5674 el.className = 'yui-non';
5679 * @method cmd_createlink
5680 * @param value Value passed from the execCommand method
5681 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used.
5683 cmd_createlink: function(value) {
5684 var el = this._getSelectedElement(), _a = null;
5685 if (this._hasParent(el, 'a')) {
5686 this.currentElement[0] = this._hasParent(el, 'a');
5687 } else if (!this._isElement(el, 'a')) {
5688 this._createCurrentElement('a');
5689 _a = this._swapEl(this.currentElement[0], 'a');
5690 this.currentElement[0] = _a;
5692 this.currentElement[0] = el;
5697 * @method cmd_insertimage
5698 * @param value Value passed from the execCommand method
5699 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used.
5701 cmd_insertimage: function(value) {
5702 var exec = true, _img = null, action = 'insertimage',
5703 el = this._getSelectedElement();
5706 value = this.get('blankimage');
5711 * @browser Safari 2.x
5712 * @description The issue here is that we have no way of knowing where the cursor position is
5713 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5716 if (this._isElement(el, 'img')) {
5717 this.currentElement[0] = el;
5720 if (this._getDoc().queryCommandEnabled(action)) {
5721 this._getDoc().execCommand('insertimage', false, value);
5722 var imgs = this._getDoc().getElementsByTagName('img');
5723 for (var i = 0; i < imgs.length; i++) {
5724 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
5725 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
5726 this.currentElement[0] = imgs[i];
5731 if (el == this._getDoc().body) {
5732 _img = this._getDoc().createElement('img');
5733 _img.setAttribute('src', value);
5734 YAHOO.util.Dom.addClass(_img, 'yui-img');
5735 this._getDoc().body.appendChild(_img);
5737 this._createCurrentElement('img');
5738 _img = this._getDoc().createElement('img');
5739 _img.setAttribute('src', value);
5740 YAHOO.util.Dom.addClass(_img, 'yui-img');
5741 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
5743 this.currentElement[0] = _img;
5750 * @method cmd_inserthtml
5751 * @param value Value passed from the execCommand method
5752 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used.
5754 cmd_inserthtml: function(value) {
5755 var exec = true, action = 'inserthtml', _span = null, _range = null;
5758 * @browser Safari 2.x
5759 * @description The issue here is that we have no way of knowing where the cursor position is
5760 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
5762 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
5763 this._createCurrentElement('img');
5764 _span = this._getDoc().createElement('span');
5765 _span.innerHTML = value;
5766 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
5768 } else if (this.browser.ie) {
5769 _range = this._getRange();
5771 _range.item(0).outerHTML = value;
5773 _range.pasteHTML(value);
5781 * @param tag The tag of the list you want to create (eg, ul or ol)
5782 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods.
5784 cmd_list: function(tag) {
5785 var exec = true, list = null, li = 0, el = null, str = '',
5786 selEl = this._getSelectedElement(), action = 'insertorderedlist';
5788 action = 'insertunorderedlist';
5791 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
5792 * @browser Safari 2.x
5793 * The issue with this workaround is that when applied to a set of text
5794 * that has BR's in it, Safari may or may not pick up the individual items as
5795 * list items. This is fixed in WebKit (Safari 3)
5796 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code
5798 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
5799 if (this.browser.webkit) {
5800 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) {
5801 el = selEl.parentNode;
5802 list = this._getDoc().createElement('span');
5803 YAHOO.util.Dom.addClass(list, 'yui-non');
5805 var lis = el.getElementsByTagName('li');
5806 for (li = 0; li < lis.length; li++) {
5807 str += '<div>' + lis[li].innerHTML + '</div>';
5809 list.innerHTML = str;
5810 this.currentElement[0] = el;
5811 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
5813 this._createCurrentElement(tag.toLowerCase());
5814 list = this._getDoc().createElement(tag);
5815 for (li = 0; li < this.currentElement.length; li++) {
5816 var newli = this._getDoc().createElement('li');
5817 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> ';
5818 list.appendChild(newli);
5820 this.currentElement[li].parentNode.removeChild(this.currentElement[li]);
5823 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]);
5824 this.currentElement[0] = list;
5825 var _h = this.currentElement[0].firstChild;
5826 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0];
5827 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length);
5831 el = this._getSelectedElement();
5832 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..
5833 if (this.browser.ie) {
5834 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) {
5835 el = el.getElementsByTagName('li')[0];
5838 var lis2 = el.parentNode.getElementsByTagName('li');
5839 for (var j = 0; j < lis2.length; j++) {
5840 str += lis2[j].innerHTML + '<br>';
5842 var newEl = this._getDoc().createElement('span');
5843 newEl.innerHTML = str;
5844 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
5847 this._getDoc().execCommand(action, '', el.parentNode);
5852 if (this.browser.opera) {
5854 window.setTimeout(function() {
5855 var liso = self._getDoc().getElementsByTagName('li');
5856 for (var i = 0; i < liso.length; i++) {
5857 if (liso[i].innerHTML.toLowerCase() == '<br>') {
5858 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode);
5863 if (this.browser.ie && exec) {
5865 if (this._getRange().html) {
5866 html = '<li>' + this._getRange().html+ '</li>';
5868 var t = this._getRange().text.split('\n');
5871 for (var ie = 0; ie < t.length; ie++) {
5872 html += '<li>' + t[ie] + '</li>';
5875 var txt = this._getRange().text;
5877 html = '<li id="new_list_item">' + txt + '</li>';
5879 html = '<li>' + txt + '</li>';
5883 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
5884 var new_item = this._getDoc().getElementById('new_list_item');
5886 var range = this._getDoc().body.createTextRange();
5887 range.moveToElementText(new_item);
5888 range.collapse(false);
5898 * @method cmd_insertorderedlist
5899 * @param value Value passed from the execCommand method
5900 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used.
5902 cmd_insertorderedlist: function(value) {
5903 return [this.cmd_list('ol')];
5906 * @method cmd_insertunorderedlist
5907 * @param value Value passed from the execCommand method
5908 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used.
5910 cmd_insertunorderedlist: function(value) {
5911 return [this.cmd_list('ul')];
5914 * @method cmd_fontname
5915 * @param value Value passed from the execCommand method
5916 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used.
5918 cmd_fontname: function(value) {
5920 selEl = this._getSelectedElement();
5922 this.currentFont = value;
5923 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) {
5924 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
5926 } else if (this.get('insert') && !this._hasSelection()) {
5927 var el = this._createInsertElement({ fontFamily: value });
5933 * @method cmd_fontsize
5934 * @param value Value passed from the execCommand method
5935 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used.
5937 cmd_fontsize: function(value) {
5939 if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) {
5940 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
5941 } else if (!this._isElement(this._getSelectedElement(), 'body')) {
5942 el = this._getSelectedElement();
5943 YAHOO.util.Dom.setStyle(el, 'fontSize', value);
5944 if (this.get('insert') && this.browser.ie) {
5945 var r = this._getRange();
5949 this._selectNode(el);
5952 if (this.get('insert') && !this._hasSelection()) {
5953 el = this._createInsertElement({ fontSize: value });
5954 this.currentElement[0] = el;
5955 this._selectNode(this.currentElement[0]);
5957 this._createCurrentElement('span', {'fontSize': value });
5958 this._selectNode(this.currentElement[0]);
5967 * @param {HTMLElement} el The element to swap with
5968 * @param {String} tagName The tagname of the element that you wish to create
5969 * @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.
5970 * @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.
5972 _swapEl: function(el, tagName, callback) {
5973 var _el = this._getDoc().createElement(tagName);
5975 _el.innerHTML = el.innerHTML;
5977 if (typeof callback == 'function') {
5978 callback.call(this, _el);
5981 el.parentNode.replaceChild(_el, el);
5987 * @method _createInsertElement
5988 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing.
5989 * @param {Object} css (optional) Object literal containing styles to apply to the new element.
5992 _createInsertElement: function(css) {
5993 this._createCurrentElement('span', css);
5994 var el = this.currentElement[0];
5995 if (this.browser.webkit) {
5996 //Little Safari Hackery here..
5997 el.innerHTML = '<span class="yui-non"> </span>';
5999 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length);
6000 } else if (this.browser.ie || this.browser.opera) {
6001 el.innerHTML = ' ';
6003 this._focusWindow();
6004 this._selectNode(el, true);
6009 * @method _createCurrentElement
6010 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
6011 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
6012 * @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.
6013 * 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
6014 * <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.
6016 _createCurrentElement: function(tagName, tagStyle) {
6017 tagName = ((tagName) ? tagName : 'a');
6020 _doc = this._getDoc();
6022 if (this.currentFont) {
6026 tagStyle.fontFamily = this.currentFont;
6027 this.currentFont = null;
6029 this.currentElement = [];
6031 var _elCreate = function(tagName, tagStyle) {
6033 tagName = ((tagName) ? tagName : 'span');
6034 tagName = tagName.toLowerCase();
6042 el = _doc.createElement(tagName);
6045 el = _doc.createElement(tagName);
6046 if (tagName === 'span') {
6047 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
6048 YAHOO.util.Dom.addClass(el, 'yui-tag');
6049 el.setAttribute('tag', tagName);
6052 for (var k in tagStyle) {
6053 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) {
6054 el.style[k] = tagStyle[k];
6062 if (!this._hasSelection()) {
6063 if (this._getDoc().queryCommandEnabled('insertimage')) {
6064 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
6065 var imgs = this._getDoc().getElementsByTagName('img');
6066 for (var j = 0; j < imgs.length; j++) {
6067 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') {
6068 el = _elCreate(tagName, tagStyle);
6069 imgs[j].parentNode.replaceChild(el, imgs[j]);
6070 this.currentElement[this.currentElement.length] = el;
6074 if (this.currentEvent) {
6075 tar = YAHOO.util.Event.getTarget(this.currentEvent);
6078 tar = this._getDoc().body;
6084 * @browser Safari 2.x
6085 * @description The issue here is that we have no way of knowing where the cursor position is
6086 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
6088 el = _elCreate(tagName, tagStyle);
6089 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) {
6090 if (this._isElement(tar, 'html')) {
6091 tar = this._getDoc().body;
6093 tar.appendChild(el);
6094 } else if (tar.nextSibling) {
6095 tar.parentNode.insertBefore(el, tar.nextSibling);
6097 tar.parentNode.appendChild(el);
6099 //this.currentElement = el;
6100 this.currentElement[this.currentElement.length] = el;
6101 this.currentEvent = null;
6102 if (this.browser.webkit) {
6103 //Force Safari to focus the new element
6104 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6105 if (this.browser.webkit3) {
6106 this._getSelection().collapseToStart();
6108 this._getSelection().collapse(true);
6113 //Force CSS Styling for this action...
6114 this._setEditorStyle(true);
6115 this._getDoc().execCommand('fontname', false, 'yui-tmp');
6116 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u'];
6118 if (!this._isElement(this._getSelectedElement(), 'body')) {
6119 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
6120 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName);
6122 for (var _els = 0; _els < __els.length; _els++) {
6123 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]);
6124 for (var e = 0; e < _tmp1.length; e++) {
6125 _tmp[_tmp.length] = _tmp1[e];
6129 for (var i = 0; i < _tmp.length; i++) {
6130 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
6131 //TODO Why is this here?!?
6132 //el = _elCreate(_tmp[i].tagName, tagStyle);
6133 el = _elCreate(tagName, tagStyle);
6134 el.innerHTML = _tmp[i].innerHTML;
6135 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) {
6136 var fc = _tmp[i].getElementsByTagName('li')[0];
6137 _tmp[i].style.fontFamily = 'inherit';
6138 fc.style.fontFamily = 'inherit';
6139 el.innerHTML = fc.innerHTML;
6142 this.currentElement[this.currentElement.length] = el;
6143 } else if (this._isElement(_tmp[i], 'li')) {
6144 _tmp[i].innerHTML = '';
6145 _tmp[i].appendChild(el);
6146 _tmp[i].style.fontFamily = 'inherit';
6147 this.currentElement[this.currentElement.length] = el;
6149 if (_tmp[i].parentNode) {
6150 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
6151 this.currentElement[this.currentElement.length] = el;
6152 this.currentEvent = null;
6153 if (this.browser.webkit) {
6154 //Force Safari to focus the new element
6155 this._getSelection().setBaseAndExtent(el, 0, el, 0);
6156 if (this.browser.webkit3) {
6157 this._getSelection().collapseToStart();
6159 this._getSelection().collapse(true);
6162 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
6163 this._getSelection().empty();
6165 if (this.browser.gecko) {
6166 this._getSelection().collapseToStart();
6172 var len = this.currentElement.length;
6173 for (var o = 0; o < len; o++) {
6174 if ((o + 1) != len) { //Skip the last one in the list
6175 if (this.currentElement[o] && this.currentElement[o].nextSibling) {
6176 if (this._isElement(this.currentElement[o], 'br')) {
6177 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling;
6186 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
6189 saveHTML: function() {
6190 var html = this.cleanHTML();
6191 if (this._textarea) {
6192 this.get('element').value = html;
6194 this.get('element').innerHTML = html;
6196 if (this.get('saveEl') !== this.get('element')) {
6197 var out = this.get('saveEl');
6198 if (Lang.isString(out)) {
6202 if (out.tagName.toLowerCase() === 'textarea') {
6205 out.innerHTML = html;
6212 * @method setEditorHTML
6213 * @param {String} incomingHTML The html content to load into the editor
6214 * @description Loads HTML into the editors body
6216 setEditorHTML: function(incomingHTML) {
6217 var html = this._cleanIncomingHTML(incomingHTML);
6218 this._getDoc().body.innerHTML = html;
6222 * @method getEditorHTML
6223 * @description Gets the unprocessed/unfiltered HTML from the editor
6225 getEditorHTML: function() {
6226 var b = this._getDoc().body;
6230 return this._getDoc().body.innerHTML;
6234 * @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.
6237 if (this.browser.gecko) {
6238 this._setDesignMode('on');
6239 this._focusWindow();
6241 if (this.browser.webkit) {
6243 window.setTimeout(function() {
6244 self._setInitialContent.call(self);
6247 //Adding this will close all other Editor window's when showing this one.
6248 if (this.currentWindow) {
6251 //Put the iframe back in place
6252 this.get('iframe').setStyle('position', 'static');
6253 this.get('iframe').setStyle('left', '');
6257 * @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.
6260 //Adding this will close all other Editor window's.
6261 if (this.currentWindow) {
6264 if (this._fixNodesTimer) {
6265 clearTimeout(this._fixNodesTimer);
6266 this._fixNodesTimer = null;
6268 if (this._nodeChangeTimer) {
6269 clearTimeout(this._nodeChangeTimer);
6270 this._nodeChangeTimer = null;
6272 this._lastNodeChange = 0;
6273 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements.
6274 this.get('iframe').setStyle('position', 'absolute');
6275 this.get('iframe').setStyle('left', '-9999px');
6278 * @method _cleanIncomingHTML
6279 * @param {String} html The unfiltered HTML
6280 * @description Process the HTML with a few regexes to clean it up and stabilize the input
6281 * @return {String} The filtered HTML
6283 _cleanIncomingHTML: function(html) {
6284 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6285 html = html.replace(/<\/strong>/gi, '</b>');
6287 //replace embed before em check
6288 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6289 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>');
6291 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6292 html = html.replace(/<\/em>/gi, '</i>');
6294 //Put embed tags back in..
6295 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>');
6296 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>');
6297 if (this.get('plainText')) {
6298 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>');
6299 html = html.replace(/ /gi, ' '); //Replace all double spaces
6300 html = html.replace(/\t/gi, ' '); //Replace all tabs
6302 //Removing Script Tags from the Editor
6303 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6304 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6305 html = html.replace(/<script([^>]*)>/gi, '<bad>');
6306 html = html.replace(/<\/script([^>]*)>/gi, '</bad>');
6307 //Replace the line feeds
6308 html = html.replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>');
6309 //Remove Bad HTML elements (used to be script nodes)
6310 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), '');
6311 //Replace the lines feeds
6312 html = html.replace(/<YUI_LF>/g, '\n');
6317 * @param {String} html The unfiltered HTML
6318 * @description Process the HTML with a few regexes to clean it up and stabilize the output
6319 * @return {String} The filtered HTML
6321 cleanHTML: function(html) {
6322 //Start Filtering Output
6325 html = this.getEditorHTML();
6327 var markup = this.get('markup');
6328 //Make some backups...
6329 html = this.pre_filter_linebreaks(html, markup);
6331 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>');
6332 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
6334 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>');
6335 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>');
6337 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
6338 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
6339 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>');
6340 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>');
6342 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>');
6343 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>');
6345 //Convert b and i tags to strong and em tags
6346 if ((markup == 'semantic') || (markup == 'xhtml')) {
6347 html = html.replace(/<i(\s+[^>]*)?>/gi, '<em$1>');
6348 html = html.replace(/<\/i>/gi, '</em>');
6349 html = html.replace(/<b(\s+[^>]*)?>/gi, '<strong$1>');
6350 html = html.replace(/<\/b>/gi, '</strong>');
6354 html = html.replace(/<font/gi, '<font');
6355 html = html.replace(/<\/font>/gi, '</font>');
6356 html = html.replace(/<span/gi, '<span');
6357 html = html.replace(/<\/span>/gi, '</span>');
6358 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) {
6359 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>');
6360 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"');
6361 if (this.browser.webkit) {
6362 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>');
6363 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>');
6365 html = html.replace(/\/u>/gi, '/span>');
6366 if (markup == 'css') {
6367 html = html.replace(/<em([^>]*)>/gi, '<i$1>');
6368 html = html.replace(/<\/em>/gi, '</i>');
6369 html = html.replace(/<strong([^>]*)>/gi, '<b$1>');
6370 html = html.replace(/<\/strong>/gi, '</b>');
6371 html = html.replace(/<b/gi, '<span style="font-weight: bold;"');
6372 html = html.replace(/\/b>/gi, '/span>');
6373 html = html.replace(/<i/gi, '<span style="font-style: italic;"');
6374 html = html.replace(/\/i>/gi, '/span>');
6376 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6378 html = html.replace(/<u/gi, '<u');
6379 html = html.replace(/\/u>/gi, '/u>');
6381 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
6382 html = html.replace(/\/ol>/gi, '/ol>');
6383 html = html.replace(/<li/gi, '<li');
6384 html = html.replace(/\/li>/gi, '/li>');
6385 html = this.filter_safari(html);
6387 html = this.filter_internals(html);
6389 html = this.filter_all_rgb(html);
6391 //Replace our backups with the real thing
6392 html = this.post_filter_linebreaks(html, markup);
6394 if (markup == 'xhtml') {
6395 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />');
6396 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />');
6398 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>');
6399 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>');
6401 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
6402 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
6404 html = this.filter_invalid_lists(html);
6406 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>');
6407 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>');
6409 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>');
6410 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>');
6412 //This should fix &s in URL's
6413 html = html.replace(' & ', 'YUI_AMP');
6414 html = html.replace('&', '&');
6415 html = html.replace('YUI_AMP', '&');
6417 //Trim the output, removing whitespace from the beginning and end
6418 html = YAHOO.lang.trim(html);
6420 if (this.get('removeLineBreaks')) {
6421 html = html.replace(/\n/g, '').replace(/\r/g, '');
6422 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single
6426 if (html.substring(0, 6).toLowerCase() == '<span>') {
6427 html = html.substring(6);
6429 if (html.substring(html.length - 7, html.length).toLowerCase() == '</span>') {
6430 html = html.substring(0, html.length - 7);
6434 for (var v in this.invalidHTML) {
6435 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
6436 if (Lang.isObject(v) && v.keepContents) {
6437 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1');
6439 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '');
6444 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html });
6449 * @method filter_invalid_lists
6450 * @param String html The HTML string to filter
6451 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li>
6453 filter_invalid_lists: function(html) {
6454 html = html.replace(/<\/li>\n/gi, '</li>');
6456 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>');
6457 html = html.replace(/<\/ol>/gi, '</ol></li>');
6458 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>\n");
6460 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>');
6461 html = html.replace(/<\/ul>/gi, '</ul></li>');
6462 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>\n");
6464 html = html.replace(/<\/li>/gi, "</li>\n");
6465 html = html.replace(/<\/ol>/gi, "</ol>\n");
6466 html = html.replace(/<ol>/gi, "<ol>\n");
6467 html = html.replace(/<ul>/gi, "<ul>\n");
6471 * @method filter_safari
6472 * @param String html The HTML string to filter
6473 * @description Filters strings specific to Safari
6476 filter_safari: function(html) {
6477 if (this.browser.webkit) {
6478 //<span class="Apple-tab-span" style="white-space:pre"> </span>
6479 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' ');
6480 html = html.replace(/Apple-style-span/gi, '');
6481 html = html.replace(/style="line-height: normal;"/gi, '');
6483 html = html.replace(/<li><\/li>/gi, '');
6484 html = html.replace(/<li> <\/li>/gi, '');
6485 html = html.replace(/<li> <\/li>/gi, '');
6486 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break
6487 if (this.get('ptags')) {
6488 html = html.replace(/<div([^>]*)>/g, '<p$1>');
6489 html = html.replace(/<\/div>/gi, '</p>');
6491 html = html.replace(/<div>/gi, '');
6492 html = html.replace(/<\/div>/gi, '<br>');
6498 * @method filter_internals
6499 * @param String html The HTML string to filter
6500 * @description Filters internal RTE strings and bogus attrs we don't want
6503 filter_internals: function(html) {
6504 html = html.replace(/\r/g, '');
6505 //Fix stuff we don't want
6506 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, '');
6508 html = html.replace(/<YUI_BR><\/li>/gi, '</li>');
6510 html = html.replace(/yui-tag-span/gi, '');
6511 html = html.replace(/yui-tag/gi, '');
6512 html = html.replace(/yui-non/gi, '');
6513 html = html.replace(/yui-img/gi, '');
6514 html = html.replace(/ tag="span"/gi, '');
6515 html = html.replace(/ class=""/gi, '');
6516 html = html.replace(/ style=""/gi, '');
6517 html = html.replace(/ class=" "/gi, '');
6518 html = html.replace(/ class=" "/gi, '');
6519 html = html.replace(/ target=""/gi, '');
6520 html = html.replace(/ title=""/gi, '');
6522 if (this.browser.ie) {
6523 html = html.replace(/ class= /gi, '');
6524 html = html.replace(/ class= >/gi, '');
6525 html = html.replace(/_height="([^>])"/gi, '');
6526 html = html.replace(/_width="([^>])"/gi, '');
6532 * @method filter_all_rgb
6533 * @param String str The HTML string to filter
6534 * @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"
6537 filter_all_rgb: function(str) {
6538 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi");
6539 var arr = str.match(exp);
6540 if (Lang.isArray(arr)) {
6541 for (var i = 0; i < arr.length; i++) {
6542 var color = this.filter_rgb(arr[i]);
6543 str = str.replace(arr[i].toString(), color);
6550 * @method filter_rgb
6551 * @param String css The CSS string containing rgb(#,#,#);
6552 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00
6555 filter_rgb: function(css) {
6556 if (css.toLowerCase().indexOf('rgb') != -1) {
6557 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi");
6558 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(',');
6560 if (rgb.length == 5) {
6561 var r = parseInt(rgb[1], 10).toString(16);
6562 var g = parseInt(rgb[2], 10).toString(16);
6563 var b = parseInt(rgb[3], 10).toString(16);
6565 r = r.length == 1 ? '0' + r : r;
6566 g = g.length == 1 ? '0' + g : g;
6567 b = b.length == 1 ? '0' + b : b;
6569 css = "#" + r + g + b;
6575 * @method pre_filter_linebreaks
6576 * @param String html The HTML to filter
6577 * @param String markup The markup type to filter to
6578 * @description HTML Pre Filter
6581 pre_filter_linebreaks: function(html, markup) {
6582 if (this.browser.webkit) {
6583 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
6584 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>');
6586 html = html.replace(/<br>/gi, '<YUI_BR>');
6587 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>');
6588 html = html.replace(/<br\/>/gi, '<YUI_BR>');
6589 html = html.replace(/<br \/>/gi, '<YUI_BR>');
6590 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>');
6591 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
6592 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
6593 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
6595 html = html.replace(/<YUI_BR>$/, '');
6597 html = html.replace(/<YUI_BR><\/p>/g, '</p>');
6598 if (this.browser.ie) {
6599 html = html.replace(/ /g, '\t');
6604 * @method post_filter_linebreaks
6605 * @param String html The HTML to filter
6606 * @param String markup The markup type to filter to
6607 * @description HTML Pre Filter
6610 post_filter_linebreaks: function(html, markup) {
6611 if (markup == 'xhtml') {
6612 html = html.replace(/<YUI_BR>/g, '<br />');
6614 html = html.replace(/<YUI_BR>/g, '<br>');
6619 * @method clearEditorDoc
6620 * @description Clear the doc of the Editor
6622 clearEditorDoc: function() {
6623 this._getDoc().body.innerHTML = ' ';
6626 * @method openWindow
6627 * @description Override Method for Advanced Editor
6629 openWindow: function(win) {
6632 * @method moveWindow
6633 * @description Override Method for Advanced Editor
6635 moveWindow: function() {
6639 * @method _closeWindow
6640 * @description Override Method for Advanced Editor
6642 _closeWindow: function() {
6645 * @method closeWindow
6646 * @description Override Method for Advanced Editor
6648 closeWindow: function() {
6649 //this.unsubscribeAll('afterExecCommand');
6650 this.toolbar.resetAllButtons();
6651 this._focusWindow();
6655 * @description Destroys the editor, all of it's elements and objects.
6658 destroy: function() {
6660 this.resize.destroy();
6665 if (this.get('panel')) {
6666 this.get('panel').destroy();
6669 this.toolbar.destroy();
6670 this.setStyle('visibility', 'visible');
6671 this.setStyle('position', 'static');
6672 this.setStyle('top', '');
6673 this.setStyle('left', '');
6674 var textArea = this.get('element');
6675 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
6676 this.get('element_cont').get('element').innerHTML = '';
6677 this.set('handleSubmit', false); //Remove the submit handler
6682 * @description Returns a string representing the editor.
6685 toString: function() {
6686 var str = 'SimpleEditor';
6687 if (this.get && this.get('element_cont')) {
6688 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
6695 * @event toolbarLoaded
6696 * @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.
6697 * @type YAHOO.util.CustomEvent
6701 * @description Event is fired after the cleanHTML method is called.
6702 * @type YAHOO.util.CustomEvent
6705 * @event afterRender
6706 * @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.
6707 * @type YAHOO.util.CustomEvent
6710 * @event editorContentLoaded
6711 * @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.
6712 * @type YAHOO.util.CustomEvent
6715 * @event beforeNodeChange
6716 * @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.
6717 * @type YAHOO.util.CustomEvent
6720 * @event afterNodeChange
6721 * @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.
6722 * @type YAHOO.util.CustomEvent
6725 * @event beforeExecCommand
6726 * @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.
6727 * @type YAHOO.util.CustomEvent
6730 * @event afterExecCommand
6731 * @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.
6732 * @type YAHOO.util.CustomEvent
6735 * @event editorMouseUp
6736 * @param {Event} ev The DOM Event that occured
6737 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6738 * @type YAHOO.util.CustomEvent
6741 * @event editorMouseDown
6742 * @param {Event} ev The DOM Event that occured
6743 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6744 * @type YAHOO.util.CustomEvent
6747 * @event editorDoubleClick
6748 * @param {Event} ev The DOM Event that occured
6749 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6750 * @type YAHOO.util.CustomEvent
6753 * @event editorClick
6754 * @param {Event} ev The DOM Event that occured
6755 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6756 * @type YAHOO.util.CustomEvent
6759 * @event editorKeyUp
6760 * @param {Event} ev The DOM Event that occured
6761 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6762 * @type YAHOO.util.CustomEvent
6765 * @event editorKeyPress
6766 * @param {Event} ev The DOM Event that occured
6767 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6768 * @type YAHOO.util.CustomEvent
6771 * @event editorKeyDown
6772 * @param {Event} ev The DOM Event that occured
6773 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
6774 * @type YAHOO.util.CustomEvent
6777 * @event beforeEditorMouseUp
6778 * @param {Event} ev The DOM Event that occured
6779 * @description Fires before editor event, returning false will stop the internal processing.
6780 * @type YAHOO.util.CustomEvent
6783 * @event beforeEditorMouseDown
6784 * @param {Event} ev The DOM Event that occured
6785 * @description Fires before editor event, returning false will stop the internal processing.
6786 * @type YAHOO.util.CustomEvent
6789 * @event beforeEditorDoubleClick
6790 * @param {Event} ev The DOM Event that occured
6791 * @description Fires before editor event, returning false will stop the internal processing.
6792 * @type YAHOO.util.CustomEvent
6795 * @event beforeEditorClick
6796 * @param {Event} ev The DOM Event that occured
6797 * @description Fires before editor event, returning false will stop the internal processing.
6798 * @type YAHOO.util.CustomEvent
6801 * @event beforeEditorKeyUp
6802 * @param {Event} ev The DOM Event that occured
6803 * @description Fires before editor event, returning false will stop the internal processing.
6804 * @type YAHOO.util.CustomEvent
6807 * @event beforeEditorKeyPress
6808 * @param {Event} ev The DOM Event that occured
6809 * @description Fires before editor event, returning false will stop the internal processing.
6810 * @type YAHOO.util.CustomEvent
6813 * @event beforeEditorKeyDown
6814 * @param {Event} ev The DOM Event that occured
6815 * @description Fires before editor event, returning false will stop the internal processing.
6816 * @type YAHOO.util.CustomEvent
6821 * @description Singleton object used to track the open window objects and panels across the various open editors
6825 YAHOO.widget.EditorInfo = {
6828 * @property _instances
6829 * @description A reference to all editors on the page.
6835 * @property blankImage
6836 * @description A reference to the blankImage url
6843 * @description A reference to the currently open window object in any editor on the page.
6844 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
6850 * @description A reference to the currently open panel in any editor on the page.
6851 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>
6855 * @method getEditorById
6856 * @description Returns a reference to the Editor object associated with the given textarea
6857 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of
6858 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a>
6860 getEditorById: function(id) {
6861 if (!YAHOO.lang.isString(id)) {
6862 //Not a string, assume a node Reference
6865 if (this._instances[id]) {
6866 return this._instances[id];
6872 * @description Returns a string representing the EditorInfo.
6875 toString: function() {
6877 for (var i in this._instances) {
6878 if (Lang.hasOwnProperty(this._instances, i)) {
6882 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')';
6890 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.6.0", build: "1321"});