2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
9 Code licensed under the BSD License:
10 http://developer.yahoo.net/yui/license.txt
13 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
15 * @namespace YAHOO.widget
16 * @requires yahoo, dom, element, event
17 * @optional container, menu, button, dragdrop
24 var Dom = YAHOO.util.Dom,
25 Event = YAHOO.util.Event,
29 * Provides a rich toolbar widget based on the button and menu widgets
31 * @param {String/HTMLElement} el The element to turn into a toolbar.
32 * @param {Object} attrs Object liternal containing configuration parameters.
34 YAHOO.widget.Toolbar = function(el, attrs) {
36 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) {
39 var local_attrs = (attrs || {});
43 attributes: local_attrs
47 if (Lang.isString(el) && Dom.get(el)) {
48 oConfig.element = Dom.get(el);
49 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) {
50 oConfig.element = Dom.get(el);
54 if (!oConfig.element) {
55 oConfig.element = document.createElement('DIV');
56 oConfig.element.id = Dom.generateId();
58 if (local_attrs.container && Dom.get(local_attrs.container)) {
59 Dom.get(local_attrs.container).appendChild(oConfig.element);
64 if (!oConfig.element.id) {
65 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId());
68 var cont = document.createElement('DIV');
69 oConfig.attributes.cont = cont;
70 Dom.addClass(cont, 'yui-toolbar-subcont')
71 oConfig.element.appendChild(cont);
73 oConfig.attributes.element = oConfig.element;
74 oConfig.attributes.id = oConfig.element.id;
76 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
82 * @method _addMenuClasses
84 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
85 * @param {String} ev The event that fired.
86 * @param {Array} na Array of event information.
87 * @param {Object} o Button config object.
90 function _addMenuClasses(ev, na, o) {
91 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu');
92 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) {
93 Dom.addClass(this.element, 'yui-toolbar-select-menu');
95 var items = this.getItems();
96 for (var i = 0; i < items.length; i++) {
97 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()));
98 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-')));
103 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
106 * @description The DragDrop instance associated with the Toolbar
111 * @property _colorData
112 * @description Object reference containing colors hex and text values.
117 '#111111': 'Obsidian',
118 '#2D2D2D': 'Dark Gray',
122 '#8B8B8B': 'Concrete',
124 '#B9B9B9': 'Titanium',
126 '#D0D0D0': 'Light Gray',
129 '#BFBF00': 'Pumpkin',
132 '#FFFF80': 'Pale Yellow',
134 '#525330': 'Raw Siena',
137 '#7F7F00': 'Paprika',
142 '#80FF00': 'Chartreuse',
144 '#C0FF80': 'Pale Lime',
145 '#DFFFBF': 'Light Mint',
147 '#668F5A': 'Lime Gray',
150 '#8A9B55': 'Pistachio',
151 '#B7C296': 'Light Jade',
152 '#E6EBD5': 'Breakwater',
153 '#00BF00': 'Spring Frost',
154 '#00FF80': 'Pastel Green',
155 '#40FFA0': 'Light Emerald',
156 '#80FFC0': 'Sea Foam',
157 '#BFFFDF': 'Sea Mist',
158 '#033D21': 'Dark Forrest',
160 '#7FA37C': 'Medium Green',
162 '#8DAE94': 'Yellow Gray Green',
163 '#ACC6B5': 'Aqua Lung',
164 '#DDEBE2': 'Sea Vapor',
167 '#40FFFF': 'Turquoise Blue',
168 '#80FFFF': 'Light Aqua',
169 '#BFFFFF': 'Pale Cyan',
170 '#033D3D': 'Dark Teal',
171 '#347D7E': 'Gray Turquoise',
172 '#609A9F': 'Green Blue',
173 '#007F7F': 'Seaweed',
174 '#96BDC4': 'Green Gray',
175 '#B5D1D7': 'Soapstone',
176 '#E2F1F4': 'Light Turquoise',
177 '#0060BF': 'Summer Sky',
178 '#0080FF': 'Sky Blue',
179 '#40A0FF': 'Electric Blue',
180 '#80C0FF': 'Light Azure',
181 '#BFDFFF': 'Ice Blue',
184 '#57708F': 'Dusty Blue',
185 '#00407F': 'Sea Blue',
186 '#7792AC': 'Sky Blue Gray',
187 '#A8BED1': 'Morning Sky',
189 '#0000BF': 'Deep Blue',
191 '#4040FF': 'Cerulean Blue',
192 '#8080FF': 'Evening Blue',
193 '#BFBFFF': 'Light Blue',
194 '#212143': 'Deep Indigo',
195 '#373E68': 'Sea Blue',
196 '#444F75': 'Night Blue',
197 '#00007F': 'Indigo Blue',
198 '#585E82': 'Dockside',
199 '#8687A4': 'Blue Gray',
200 '#D2D1E1': 'Light Blue Gray',
201 '#6000BF': 'Neon Violet',
202 '#8000FF': 'Blue Violet',
203 '#A040FF': 'Violet Purple',
204 '#C080FF': 'Violet Dusk',
205 '#DFBFFF': 'Pale Lavender',
206 '#302449': 'Cool Shale',
207 '#54466F': 'Dark Indigo',
208 '#655A7F': 'Dark Violet',
210 '#726284': 'Smoky Violet',
211 '#9E8FA9': 'Slate Gray',
212 '#DCD1DF': 'Violet White',
213 '#BF00BF': 'Royal Violet',
214 '#FF00FF': 'Fuchsia',
215 '#FF40FF': 'Magenta',
217 '#FFBFFF': 'Pale Magenta',
218 '#4A234A': 'Dark Purple',
219 '#794A72': 'Medium Purple',
220 '#936386': 'Cool Granite',
222 '#9D7292': 'Purple Moon',
223 '#C0A0B6': 'Pale Purple',
224 '#ECDAE5': 'Pink Cloud',
225 '#BF005F': 'Hot Pink',
226 '#FF007F': 'Deep Pink',
228 '#FF80BF': 'Electric Pink',
230 '#451528': 'Purple Red',
231 '#823857': 'Purple Dino',
232 '#A94A76': 'Purple Gray',
234 '#BC6F95': 'Antique Mauve',
235 '#D8A5BB': 'Cool Marble',
236 '#F7DDE9': 'Pink Granite',
238 '#FF0000': 'Fire Truck',
239 '#FF4040': 'Pale Red',
241 '#FFC0C0': 'Warm Pink',
245 '#800000': 'Brick Red',
247 '#D8A3A4': 'Shrimp Pink',
248 '#F8DDDD': 'Shell Pink',
249 '#BF5F00': 'Dark Orange',
251 '#FF9F40': 'Grapefruit',
252 '#FFBF80': 'Canteloupe',
254 '#482C1B': 'Dark Brick',
258 '#C49B71': 'Mustard',
259 '#E1C4A8': 'Pale Tan',
264 * @property _colorPicker
265 * @description The HTML Element containing the colorPicker
270 * @property STR_COLLAPSE
271 * @description String for Toolbar Collapse Button
274 STR_COLLAPSE: 'Collapse Toolbar',
276 * @property STR_SPIN_LABEL
277 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
280 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.',
282 * @property STR_SPIN_UP
283 * @description String for spinbutton up
286 STR_SPIN_UP: 'Click to increase the value of this input',
288 * @property STR_SPIN_DOWN
289 * @description String for spinbutton down
292 STR_SPIN_DOWN: 'Click to decrease the value of this input',
294 * @property _titlebar
295 * @description Object reference to the titlebar
300 * @property _disabled
301 * @description Object to track button status when enabling/disabling the toolbar
307 * @description Standard browser detection
310 browser: YAHOO.env.ua,
313 * @property _buttonList
314 * @description Internal property list of current buttons in the toolbar
320 * @property _buttonGroupList
321 * @description Internal property list of current button groups in the toolbar
324 _buttonGroupList: null,
328 * @description Internal reference to the separator HTML Element for cloning
334 * @property _sepCount
335 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
341 * @property draghandle
347 * @property _toolbarConfigs
355 * @property CLASS_CONTAINER
356 * @description Default CSS class to apply to the toolbar container element
359 CLASS_CONTAINER: 'yui-toolbar-container',
362 * @property CLASS_DRAGHANDLE
363 * @description Default CSS class to apply to the toolbar's drag handle element
366 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
369 * @property CLASS_SEPARATOR
370 * @description Default CSS class to apply to all separators in the toolbar
373 CLASS_SEPARATOR: 'yui-toolbar-separator',
376 * @property CLASS_DISABLED
377 * @description Default CSS class to apply when the toolbar is disabled
380 CLASS_DISABLED: 'yui-toolbar-disabled',
383 * @property CLASS_PREFIX
384 * @description Default prefix for dynamically created class names
387 CLASS_PREFIX: 'yui-toolbar',
390 * @description The Toolbar class's initialization method
392 init: function(p_oElement, p_oAttributes) {
393 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes);
396 * @method initAttributes
397 * @description Initializes all of the configuration attributes used to create
399 * @param {Object} attr Object literal specifying a set of
400 * configuration attributes used to create the toolbar.
402 initAttributes: function(attr) {
403 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr);
404 var el = this.get('element');
405 this.addClass(this.CLASS_CONTAINER);
410 * @description Object specifying the buttons to include in the toolbar
414 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
415 * { type: 'separator' },
416 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
418 * { text: "Left", value: 'alignleft' },
419 * { text: "Center", value: 'aligncenter' },
420 * { text: "Right", value: 'alignright' }
428 this.setAttributeConfig('buttons', {
431 method: function(data) {
432 for (var i in data) {
433 if (Lang.hasOwnProperty(data, i)) {
434 if (data[i].type == 'separator') {
436 } else if (data[i].group != undefined) {
437 this.addButtonGroup(data[i]);
439 this.addButton(data[i]);
448 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
452 this.setAttributeConfig('disabled', {
454 method: function(disabled) {
455 if (!Lang.isObject(this._disabled)) {
459 this.addClass(this.CLASS_DISABLED);
460 this.set('draggable', false);
462 this.removeClass(this.CLASS_DISABLED);
463 if (this._configs.draggable._initialConfig.value) {
464 //Draggable by default, set it back
465 this.set('draggable', true);
468 var len = this._buttonList.length;
469 for (var i = 0; i < len; i++) {
471 //If it's already disabled, flag it
472 if (this._buttonList[i].get('disabled')) {
473 this._disabled[i] = true;
475 this._disabled[i] = null;
477 this.disableButton(this._buttonList[i].get('id'));
479 //Check to see if it was disabled by default and skip it
480 var _button = this._buttonList[i];
481 var _check = _button._configs.disabled._initialConfig.value;
482 if (this._disabled[i]) {
486 this.enableButton(_button.get('id'));
494 * @config grouplabels
495 * @description Boolean indicating if the toolbar should show the group label's text string.
499 this.setAttributeConfig('grouplabels', {
506 * @description Boolean indicating if the toolbar should show the group label's text string.
510 this.setAttributeConfig('cont', {
517 * @description Boolean indicating if the the titlebar should have a collapse button.
518 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
522 this.setAttributeConfig('collapse', {
527 * @description Boolean indicating if the toolbar should have a titlebar. If
528 * passed a string, it will use that as the titlebar text
530 * @type Boolean or String
532 this.setAttributeConfig('titlebar', {
534 method: function(titlebar) {
536 if (this._titlebar && this._titlebar.parentNode) {
537 this._titlebar.parentNode.removeChild(this._titlebar);
539 this._titlebar = document.createElement('DIV');
540 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar');
541 if (Lang.isString(titlebar)) {
542 var h2 = document.createElement('h2');
544 h2.innerHTML = titlebar;
545 this._titlebar.appendChild(h2);
547 if (this.get('collapse')) {
548 var collapse = document.createElement('SPAN');
549 collapse.innerHTML = 'X';
550 collapse.title = this.STR_COLLAPSE;
552 Dom.addClass(collapse, 'collapse');
553 this._titlebar.appendChild(collapse);
554 Event.addListener(collapse, 'click', function() {
555 if (Dom.getStyle(this.get('cont'), 'display') == 'none') {
556 Dom.setStyle(this.get('cont'), 'display', 'block');
557 Dom.removeClass(collapse, 'collapsed');
558 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this });
560 Dom.setStyle(this.get('cont'), 'display', 'none');
561 Dom.addClass(collapse, 'collapsed');
562 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
566 if (this.get('draggable')) {
567 this.dd = new YAHOO.util.DD(this.get('id'));
568 this.dd.setHandleElId(this._titlebar);
569 Dom.addClass(this._titlebar, 'draggable');
571 if (this.get('firstChild')) {
572 this.insertBefore(this._titlebar, this.get('firstChild'));
574 this.appendChild(this._titlebar);
576 } else if (this._titlebar) {
577 if (this._titlebar && this._titlebar.parentNode) {
578 this._titlebar.parentNode.removeChild(this._titlebar);
587 * @description Boolean indicating if the toolbar should be draggable.
592 this.setAttributeConfig('draggable', {
593 value: (attr.draggable || false),
594 method: function(draggable) {
595 var el = this.get('element');
597 if (draggable && !this.get('titlebar')) {
598 if (!this._dragHandle) {
599 this._dragHandle = document.createElement('SPAN');
600 this._dragHandle.innerHTML = '|';
601 this._dragHandle.setAttribute('title', 'Click to drag the toolbar');
602 this._dragHandle.id = this.get('id') + '_draghandle';
603 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE);
604 if (this.get('cont').hasChildNodes()) {
605 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild);
607 this.get('cont').appendChild(this._dragHandle);
611 * @description The DragDrop instance associated with the Toolbar
614 this.dd = new YAHOO.util.DD(this.get('id'));
615 this.dd.setHandleElId(this._dragHandle.id);
619 if (this._dragHandle) {
620 this._dragHandle.parentNode.removeChild(this._dragHandle);
621 this._dragHandle = null;
625 if (this._titlebar) {
627 this.dd = new YAHOO.util.DD(this.get('id'));
628 this.dd.setHandleElId(this._titlebar);
629 Dom.addClass(this._titlebar, 'draggable');
631 Dom.removeClass(this._titlebar, 'draggable');
639 validator: function(value) {
641 if (!YAHOO.util.DD) {
650 * @method addButtonGroup
651 * @description Add a new button group to the toolbar. (uses addButton)
652 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs)
654 addButtonGroup: function(oGroup) {
655 if (!this.get('element')) {
656 this._queue[this._queue.length] = ['addButtonGroup', arguments];
660 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) {
661 this.addClass(this.CLASS_PREFIX + '-grouped');
663 var div = document.createElement('DIV');
664 Dom.addClass(div, this.CLASS_PREFIX + '-group');
665 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group);
666 if (oGroup.label && this.get('grouplabels')) {
667 var label = document.createElement('h3');
668 label.innerHTML = oGroup.label;
669 div.appendChild(label);
672 this.get('cont').appendChild(div);
674 //For accessibility, let's put all of the group buttons in an Unordered List
675 var ul = document.createElement('ul');
678 if (!this._buttonGroupList) {
679 this._buttonGroupList = {};
682 this._buttonGroupList[oGroup.group] = ul;
684 for (var i = 0; i < oGroup.buttons.length; i++) {
685 var li = document.createElement('li');
687 if ((oGroup.buttons[i].type != undefined) && oGroup.buttons[i].type == 'separator') {
688 this.addSeparator(li);
690 oGroup.buttons[i].container = li;
691 this.addButton(oGroup.buttons[i]);
696 * @method addButtonToGroup
697 * @description Add a new button to a toolbar group. Buttons supported:
698 * push, split, menu, select, color, spin
699 * @param {Object} oButton Object literal reference to the Button's Config
700 * @param {String} group The Group identifier passed into the initial config
701 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
703 addButtonToGroup: function(oButton, group, after) {
704 var groupCont = this._buttonGroupList[group];
705 var li = document.createElement('li');
706 oButton.container = li;
707 this.addButton(oButton, after);
708 groupCont.appendChild(li);
712 * @description Add a new button to the toolbar. Buttons supported:
713 * push, split, menu, select, color, spin
714 * @param {Object} oButton Object literal reference to the Button's Config
715 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
717 addButton: function(oButton, after) {
718 if (!this.get('element')) {
719 this._queue[this._queue.length] = ['addButton', arguments];
722 if (!this._buttonList) {
723 this._buttonList = [];
725 //Add to .get('buttons') manually
726 this._configs.buttons.value[this._configs.buttons.value.length] = oButton;
727 if (!oButton.container) {
728 oButton.container = this.get('cont');
731 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
732 if (Lang.isArray(oButton.menu)) {
733 for (var i in oButton.menu) {
734 if (Lang.hasOwnProperty(oButton.menu, i)) {
736 fn: function(ev, x, oMenu) {
737 if (!oButton.menucmd) {
738 oButton.menucmd = oButton.value;
740 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
741 //This line made Opera fire the click event and the mousedown,
742 // so events for menus where firing twice.
743 //this._buttonClick('click', oButton);
747 oButton.menu[i].onclick = funcObject;
753 for (var i in oButton) {
754 if (Lang.hasOwnProperty(oButton, i)) {
755 if (!this._toolbarConfigs[i]) {
756 _oButton[i] = oButton[i];
760 if (oButton.type == 'select') {
761 _oButton.type = 'menu';
763 if (oButton.type == 'spin') {
764 _oButton.type = 'push';
766 if (_oButton.type == 'color') {
767 _oButton = this._makeColorButton(_oButton);
770 if (oButton.menu instanceof YAHOO.widget.Overlay) {
771 oButton.menu.showEvent.subscribe(function() {
772 this._button = _oButton;
775 for (var i = 0; i < _oButton.menu.length; i++) {
776 if (!_oButton.menu[i].value) {
777 _oButton.menu[i].value = _oButton.menu[i].text;
780 if (this.browser.webkit) {
781 _oButton.focusmenu = false;
785 var tmp = new YAHOO.widget.Button(_oButton);
786 if (this.get('disabled')) {
787 //Toolbar is disabled, disable the new button too!
788 tmp.set('disabled', true);
791 oButton.id = tmp.get('id');
795 var el = tmp.get('element');
798 nextSib = after.get('element').nextSibling;
799 } else if (after.nextSibling) {
800 nextSib = after.nextSibling;
803 nextSib.parentNode.insertBefore(el, nextSib);
806 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value'));
807 var icon = document.createElement('span');
808 icon.className = this.CLASS_PREFIX + '-icon';
809 tmp.get('element').insertBefore(icon, tmp.get('firstChild'));
810 //Replace the Button HTML Element with an a href
811 var a = document.createElement('a');
812 a.innerHTML = tmp._button.innerHTML;
814 Event.on(a, 'click', function(ev) {
817 tmp._button.parentNode.replaceChild(a, tmp._button);
820 if (oButton.type == 'select') {
821 tmp.addClass(this.CLASS_PREFIX + '-select');
823 if (oButton.type == 'spin') {
824 if (!Lang.isArray(oButton.range)) {
825 oButton.range = [ 10, 100 ];
827 this._makeSpinButton(tmp, oButton);
830 tmp.get('element').setAttribute('title', tmp.get('label'));
832 if (oButton.type != 'spin') {
833 if (_oButton.menu instanceof YAHOO.widget.Overlay) {
834 var showPicker = function(ev) {
836 if (ev.keyCode && (ev.keyCode == 9)) {
840 this._colorPicker._button = oButton.value;
841 var menuEL = tmp.getMenu().element;
842 if (menuEL.style.visibility == 'hidden') {
843 tmp.getMenu().show();
845 tmp.getMenu().hide();
848 YAHOO.util.Event.stopEvent(ev);
850 tmp.on('mousedown', showPicker, oButton, this);
851 tmp.on('keydown', showPicker, oButton, this);
853 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) {
854 tmp.on('keypress', this._buttonClick, oButton, this);
855 tmp.on('mousedown', function(ev) {
856 this._buttonClick(ev, oButton);
857 YAHOO.util.Event.stopEvent(ev);
860 //Stop the mousedown event so we can trap the selection in the editor!
861 tmp.on('mousedown', function(ev) {
862 YAHOO.util.Event.stopEvent(ev);
864 tmp.on('click', function(ev) {
865 YAHOO.util.Event.stopEvent(ev);
868 //Hijack the mousedown event in the menu and make it fire a button click..
869 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) {
871 YAHOO.util.Event.stopEvent(args[0]);
872 tmp._onMenuClick(args[0], tmp);
873 if (!oButton.menucmd) {
874 oButton.menucmd = oButton.value;
876 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue);
877 self._buttonClick.call(self, args[1], oButton);
881 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
882 YAHOO.util.Event.stopEvent(args[0]);
886 //Stop the mousedown event so we can trap the selection in the editor!
887 tmp.on('mousedown', function(ev) {
888 YAHOO.util.Event.stopEvent(ev);
890 tmp.on('click', function(ev) {
891 YAHOO.util.Event.stopEvent(ev);
894 if (this.browser.ie) {
895 //Add a couple of new events for IE
896 tmp.DOM_EVENTS.focusin = true;
897 tmp.DOM_EVENTS.focusout = true;
899 //Stop them so we don't loose focus in the Editor
900 tmp.on('focusin', function(ev) {
901 YAHOO.util.Event.stopEvent(ev);
904 tmp.on('focusout', function(ev) {
905 YAHOO.util.Event.stopEvent(ev);
907 tmp.on('click', function(ev) {
908 YAHOO.util.Event.stopEvent(ev);
911 if (this.browser.webkit) {
912 //This will keep the document from gaining focus and the editor from loosing it..
913 //Forcefully remove the focus calls in button!
914 tmp.hasFocus = function() {
918 this._buttonList[this._buttonList.length] = tmp;
919 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) {
920 if (Lang.isArray(oButton.menu)) {
921 var menu = tmp.getMenu();
922 menu.renderEvent.subscribe(_addMenuClasses, tmp);
923 if (oButton.renderer) {
924 menu.renderEvent.subscribe(oButton.renderer, tmp);
931 * @method addSeparator
932 * @description Add a new button separator to the toolbar.
933 * @param {HTMLElement} cont Optional HTML element to insert this button into.
934 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
936 addSeparator: function(cont, after) {
937 if (!this.get('element')) {
938 this._queue[this._queue.length] = ['addSeparator', arguments];
941 var sepCont = ((cont) ? cont : this.get('cont'));
942 if (!this.get('element')) {
943 this._queue[this._queue.length] = ['addSeparator', arguments];
946 if (this._sepCount == null) {
950 this._sep = document.createElement('SPAN');
951 Dom.addClass(this._sep, this.CLASS_SEPARATOR);
952 this._sep.innerHTML = '|';
954 var _sep = this._sep.cloneNode(true);
956 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
960 nextSib = after.get('element').nextSibling;
961 } else if (after.nextSibling) {
962 nextSib = after.nextSibling;
967 if (nextSib == after) {
968 nextSib.parentNode.appendChild(_sep);
970 nextSib.parentNode.insertBefore(_sep, nextSib);
974 sepCont.appendChild(_sep);
979 * @method _createColorPicker
981 * @description Creates the core DOM reference to the color picker menu item.
982 * @param {String} id the id of the toolbar to prefix this DOM container with.
984 _createColorPicker: function(id) {
985 if (Dom.get(id + '_colors')) {
986 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors'));
988 var picker = document.createElement('div');
989 picker.className = 'yui-toolbar-colors';
990 picker.id = id + '_colors';
991 picker.style.display = 'none';
992 Event.on(window, 'load', function() {
993 document.body.appendChild(picker);
996 this._colorPicker = picker;
999 for (var i in this._colorData) {
1000 if (Lang.hasOwnProperty(this._colorData, i)) {
1001 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>';
1004 html += '<span><em>X</em><strong></strong></span>';
1005 picker.innerHTML = html;
1006 var em = picker.getElementsByTagName('em')[0];
1007 var strong = picker.getElementsByTagName('strong')[0];
1009 Event.on(picker, 'mouseover', function(ev) {
1010 var tar = Event.getTarget(ev);
1011 if (tar.tagName.toLowerCase() == 'a') {
1012 em.style.backgroundColor = tar.style.backgroundColor;
1013 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML;
1016 Event.on(picker, 'focus', function(ev) {
1017 Event.stopEvent(ev);
1019 Event.on(picker, 'click', function(ev) {
1020 Event.stopEvent(ev);
1022 Event.on(picker, 'mousedown', function(ev) {
1023 Event.stopEvent(ev);
1024 var tar = Event.getTarget(ev);
1025 if (tar.tagName.toLowerCase() == 'a') {
1026 this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } );
1027 this.getButtonByValue(this._colorPicker._button).getMenu().hide();
1032 * @method _resetColorPicker
1034 * @description Clears the currently selected color or mouseover color in the color picker.
1036 _resetColorPicker: function() {
1037 var em = this._colorPicker.getElementsByTagName('em')[0];
1038 var strong = this._colorPicker.getElementsByTagName('strong')[0];
1039 em.style.backgroundColor = 'transparent';
1040 strong.innerHTML = '';
1043 * @method _makeColorButton
1045 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1046 * @param {Object} _oButton <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> reference
1048 _makeColorButton: function(_oButton) {
1049 if (!this._colorPicker) {
1050 this._createColorPicker(this.get('id'));
1052 _oButton.type = 'color';
1053 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visbile: false, position: 'absolute' });
1054 _oButton.menu.setBody('');
1055 _oButton.menu.render(this.get('cont'));
1056 _oButton.menu.beforeShowEvent.subscribe(function() {
1057 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1058 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1059 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1060 this._resetColorPicker();
1061 var _p = this._colorPicker;
1062 if (_p.parentNode) {
1063 //if (_p.parentNode != _oButton.menu.body) {
1064 _p.parentNode.removeChild(_p);
1067 _oButton.menu.setBody('');
1068 _oButton.menu.appendToBody(_p);
1069 this._colorPicker.style.display = 'block';
1075 * @method _makeSpinButton
1076 * @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.
1077 * @param {Object} _button <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> reference
1078 * @param {Object} oButton Object literal containing the buttons initial config
1080 _makeSpinButton: function(_button, oButton) {
1081 _button.addClass(this.CLASS_PREFIX + '-spinbutton');
1083 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child
1084 range = oButton.range,
1085 _b1 = document.createElement('a'),
1086 _b2 = document.createElement('a');
1090 //Setup the up and down arrows
1091 _b1.className = 'up';
1092 _b1.title = this.STR_SPIN_UP;
1093 _b1.innerHTML = this.STR_SPIN_UP;
1094 _b2.className = 'down';
1095 _b2.title = this.STR_SPIN_DOWN;
1096 _b2.innerHTML = this.STR_SPIN_DOWN;
1098 //Append them to the container
1099 _par.appendChild(_b1);
1100 _par.appendChild(_b2);
1102 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') });
1103 _button.set('title', label);
1105 var cleanVal = function(value) {
1106 value = ((value < range[0]) ? range[0] : value);
1107 value = ((value > range[1]) ? range[1] : value);
1111 var br = this.browser;
1113 var strLabel = this.STR_SPIN_LABEL;
1114 if (this._titlebar && this._titlebar.firstChild) {
1115 tbar = this._titlebar.firstChild;
1118 var _intUp = function(ev) {
1119 YAHOO.util.Event.stopEvent(ev);
1120 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1121 var value = parseInt(_button.get('label'));
1123 value = cleanVal(value);
1124 _button.set('label', ''+value);
1125 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1126 _button.set('title', label);
1127 if (!br.webkit && tbar) {
1128 //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
1131 self._buttonClick(ev, oButton);
1135 var _intDown = function(ev) {
1136 YAHOO.util.Event.stopEvent(ev);
1137 if (!_button.get('disabled') && (ev.keyCode != 9)) {
1138 var value = parseInt(_button.get('label'));
1140 value = cleanVal(value);
1142 _button.set('label', ''+value);
1143 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') });
1144 _button.set('title', label);
1145 if (!br.webkit && tbar) {
1146 //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
1149 self._buttonClick(ev, oButton);
1153 var _intKeyUp = function(ev) {
1154 if (ev.keyCode == 38) {
1156 } else if (ev.keyCode == 40) {
1158 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1160 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1165 //Handle arrow keys..
1166 _button.on('keydown', _intKeyUp, this, true);
1168 //Listen for the click on the up button and act on it
1169 //Listen for the click on the down button and act on it
1170 Event.on(_b1, 'mousedown',function(ev) {
1171 Event.stopEvent(ev);
1173 Event.on(_b2, 'mousedown', function(ev) {
1174 Event.stopEvent(ev);
1176 Event.on(_b1, 'click', _intUp, this, true);
1177 Event.on(_b2, 'click', _intDown, this, true);
1181 * @method _buttonClick
1182 * @description Click handler for all buttons in the toolbar.
1183 * @param {String} ev The event that was passed in.
1184 * @param {Object} info Object literal of information about the button that was clicked.
1186 _buttonClick: function(ev, info) {
1189 if (ev && ev.type == 'keypress') {
1190 if (ev.keyCode == 9) {
1192 } else if ((ev.keyCode == 13) || (ev.keyCode == 0) || (ev.keyCode == 32)) {
1200 this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info });
1204 this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info });
1207 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info });
1209 if (info.type == 'select') {
1210 var button = this.getButtonById(info.id);
1211 var txt = info.value;
1212 for (var i = 0; i < info.menu.length; i++) {
1213 if (info.menu[i].value == info.value) {
1214 txt = info.menu[i].text;
1218 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1219 var _items = button.getMenu().getItems();
1220 for (var m = 0; m < _items.length; m++) {
1221 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1222 _items[m].cfg.setProperty('checked', true);
1224 _items[m].cfg.setProperty('checked', false);
1230 Event.stopEvent(ev);
1234 * @method getButtonById
1235 * @description Gets a button instance from the toolbar by is Dom id.
1236 * @param {String} id The Dom id to query for.
1237 * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
1239 getButtonById: function(id) {
1240 var len = this._buttonList.length;
1241 for (var i = 0; i < len; i++) {
1242 if (this._buttonList[i].get('id') == id) {
1243 return this._buttonList[i];
1249 * @method getButtonByValue
1250 * @description Gets a button instance or a menuitem instance from the toolbar by it's value.
1251 * @param {String} value The button value to query for.
1252 * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1254 getButtonByValue: function(value) {
1255 var _buttons = this.get('buttons');
1256 var len = _buttons.length;
1257 for (var i = 0; i < len; i++) {
1258 if (_buttons[i].group != undefined) {
1259 for (var m = 0; m < _buttons[i].buttons.length; m++) {
1260 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) {
1261 return this.getButtonById(_buttons[i].buttons[m].id);
1263 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values
1264 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) {
1265 if (_buttons[i].buttons[m].menu[s].value == value) {
1266 return this.getButtonById(_buttons[i].buttons[m].id);
1272 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) {
1273 return this.getButtonById(_buttons[i].id);
1275 if (_buttons[i].menu) { //Menu Button, loop through the values
1276 for (var s = 0; s < _buttons[i].menu.length; s++) {
1277 if (_buttons[i].menu[s].value == value) {
1278 return this.getButtonById(_buttons[i].id);
1287 * @method getButtonByIndex
1288 * @description Gets a button instance from the toolbar by is index in _buttonList.
1289 * @param {Number} index The index of the button in _buttonList.
1290 * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
1292 getButtonByIndex: function(index) {
1293 if (this._buttonList[index]) {
1294 return this._buttonList[index];
1300 * @method getButtons
1301 * @description Returns an array of buttons in the current toolbar
1304 getButtons: function() {
1305 return this._buttonList;
1308 * @method disableButton
1309 * @description Disables a button in the toolbar.
1310 * @param {String/Number} button Disable a button by it's id or index.
1313 disableButton: function(button) {
1314 if (Lang.isString(button)) {
1315 var button = this.getButtonById(button);
1317 if (Lang.isNumber(button)) {
1318 var button = this.getButtonByIndex(button);
1320 if (button instanceof YAHOO.widget.Button) {
1321 button.set('disabled', true);
1327 * @method enableButton
1328 * @description Enables a button in the toolbar.
1329 * @param {String/Number} button Enable a button by it's id or index.
1332 enableButton: function(button) {
1333 if (Lang.isString(button)) {
1334 var button = this.getButtonById(button);
1336 if (Lang.isNumber(button)) {
1337 var button = this.getButtonByIndex(button);
1339 if (button instanceof YAHOO.widget.Button) {
1340 button.set('disabled', false);
1346 * @method selectButton
1347 * @description Selects a button in the toolbar.
1348 * @param {String/Number} button select a button by it's id or index.
1351 selectButton: function(button, value) {
1353 if (Lang.isString(button)) {
1354 var button = this.getButtonById(button);
1356 if (Lang.isNumber(button)) {
1357 var button = this.getButtonByIndex(button);
1359 if (button instanceof YAHOO.widget.Button) {
1360 button.addClass('yui-button-selected');
1361 button.addClass('yui-button-' + button.get('value') + '-selected');
1363 var _items = button.getMenu().getItems();
1364 for (var m = 0; m < _items.length; m++) {
1365 if (_items[m].value == value) {
1366 _items[m].cfg.setProperty('checked', true);
1367 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1369 _items[m].cfg.setProperty('checked', false);
1379 * @method deselectButton
1380 * @description Deselects a button in the toolbar.
1381 * @param {String/Number} button Deselect a button by it's id or index.
1384 deselectButton: function(button) {
1385 if (Lang.isString(button)) {
1386 var button = this.getButtonById(button);
1388 if (Lang.isNumber(button)) {
1389 var button = this.getButtonByIndex(button);
1391 if (button instanceof YAHOO.widget.Button) {
1392 button.removeClass('yui-button-selected');
1393 button.removeClass('yui-button-' + button.get('value') + '-selected');
1394 button.removeClass('yui-button-hover');
1400 * @method deselectAllButtons
1401 * @description Deselects all buttons in the toolbar.
1404 deselectAllButtons: function() {
1405 var len = this._buttonList.length;
1406 for (var i = 0; i < len; i++) {
1407 this.deselectButton(this._buttonList[i]);
1411 * @method destroyButton
1412 * @description Destroy a button in the toolbar.
1413 * @param {String/Number} button Destroy a button by it's id or index.
1416 destroyButton: function(button) {
1417 if (Lang.isString(button)) {
1418 var button = this.getButtonById(button);
1420 if (Lang.isNumber(button)) {
1421 var button = this.getButtonByIndex(button);
1423 if (button instanceof YAHOO.widget.Button) {
1424 var id = button.get('id');
1427 var len = this._buttonList.length;
1428 for (var i = 0; i < len; i++) {
1429 if (this._buttonList[i].get('id') == id) {
1430 this._buttonList[i] = null;
1440 * @description Destroys the toolbar, all of it's elements and objects.
1443 destroy: function() {
1444 this.get('element').innerHTML = '';
1445 this.get('element').className = '';
1446 //Brutal Object Destroy
1447 for (var i in this) {
1448 if (Lang.hasOwnProperty(this, i)) {
1456 * @description Returns a string representing the toolbar.
1459 toString: function() {
1460 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.';
1464 * @event buttonClick
1465 * @param {Object} o The object passed to this handler is the button config used to create the button.
1466 * @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.
1467 * @type YAHOO.util.CustomEvent
1471 * @param {Object} o The object passed to this handler is the button config used to create the button.
1472 * @description This is a special dynamic event that is created and dispatched based on the value property
1473 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
1477 * { type: 'button', value: 'test', value: 'testButton' }
1480 * With the valueClick event you could subscribe to this buttons click event with this:
1481 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
1482 * @type YAHOO.util.CustomEvent
1485 * @event toolbarExpanded
1486 * @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.
1487 * @type YAHOO.util.CustomEvent
1490 * @event toolbarCollapsed
1491 * @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.
1492 * @type YAHOO.util.CustomEvent
1496 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
1497 Code licensed under the BSD License:
1498 http://developer.yahoo.net/yui/license.txt
1502 * @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>
1503 * @namespace YAHOO.widget
1504 * @requires yahoo, dom, element, event, toolbar, container, menu, button
1505 * @optional dragdrop, animation
1510 var Dom = YAHOO.util.Dom,
1511 Event = YAHOO.util.Event,
1514 Toolbar = YAHOO.widget.Toolbar;
1518 * 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.
1521 * @extends YAHOO.util.Element
1522 * @param {String/HTMLElement} el The textarea element to turn into an editor.
1523 * @param {Object} attrs Object liternal containing configuration parameters.
1526 YAHOO.widget.Editor = function(el, attrs) {
1530 attributes: (attrs || {})
1533 if (Lang.isString(el)) {
1534 oConfig.attributes.textarea = Dom.get(el);
1537 var element_cont = document.createElement('DIV');
1538 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
1539 id: oConfig.attributes.textarea.id + '_container'
1541 oConfig.attributes.element_cont.setStyle('display', 'none');
1543 oConfig.element = oConfig.attributes.textarea;
1545 var div = document.createElement('div');
1546 oConfig.attributes.element_cont.appendChild(div);
1548 if (!oConfig.attributes.toolbar_cont) {
1549 oConfig.attributes.toolbar_cont = document.createElement('DIV');
1550 oConfig.attributes.toolbar_cont.id = oConfig.attributes.textarea.id + '_toolbar';
1551 div.appendChild(oConfig.attributes.toolbar_cont);
1554 if (!oConfig.attributes.iframe) {
1555 oConfig.attributes.iframe = _createIframe(oConfig.attributes.textarea.id);
1556 var editorWrapper = document.createElement('DIV');
1557 editorWrapper.appendChild(oConfig.attributes.iframe.get('element'));
1558 div.appendChild(editorWrapper);
1561 Event.onDOMReady(function() {
1562 this.DOMReady = true;
1566 YAHOO.widget.Editor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
1570 * @private _cleanClassName
1571 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
1572 * @param {String} str The classname to clean up
1575 function _cleanClassName(str) {
1576 return str.replace(/ /g, '-').toLowerCase();
1580 * @private _createIframe
1581 * @description Creates the DOM and YUI Element for the iFrame editor area.
1582 * @param {String} id The string ID to prefix the iframe with
1583 * @returns {Object} iFrame object
1585 function _createIframe(id) {
1586 var ifrmID = id + '_editor';
1587 var ifrmDom = document.createElement('iframe');
1588 ifrmDom.id = ifrmID;
1596 allowTransparency: 'true',
1598 src: 'javascript:false'
1600 for (var i in config) {
1601 if (Lang.hasOwnProperty(config, i)) {
1602 ifrmDom.setAttribute(i, config[i]);
1606 var ifrm = new YAHOO.util.Element(ifrmDom);
1607 ifrm.setStyle('zIndex', '-1');
1611 YAHOO.extend(YAHOO.widget.Editor, YAHOO.util.Element, {
1613 * @property DOMReady
1615 * @description Flag to determine if DOM is ready or not
1620 * @property _selection
1622 * @description Holder for caching iframe selections
1629 * @description DOM Element holder for the editor Mask when disabled
1634 * @property _showingHiddenElements
1636 * @description Status of the hidden elements button
1639 _showingHiddenElements: null,
1641 * @property currentWindow
1642 * @description A reference to the currently open EditorWindow
1645 currentWindow: null,
1647 * @property currentEvent
1648 * @description A reference to the current editor event
1653 * @property operaEvent
1655 * @description setTimeout holder for Opera and Image DoubleClick event..
1660 * @property currentFont
1661 * @description A reference to the last font selected from the Toolbar
1666 * @property currentElement
1667 * @description A reference to the current working element in the editor
1673 * @description A reference to the dompath container for writing the current working dom path to.
1678 * @property beforeElement
1679 * @description A reference to the H2 placed before the editor for Accessibilty.
1682 beforeElement: null,
1684 * @property afterElement
1685 * @description A reference to the H2 placed after the editor for Accessibilty.
1690 * @property invalidHTML
1691 * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found.
1708 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
1709 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
1714 * @property _contentTimer
1715 * @description setTimeout holder for documentReady check
1717 _contentTimer: null,
1720 * @property _contentTimerCounter
1721 * @description Counter to check the number of times the body is polled for before giving up
1724 _contentTimerCounter: 0,
1727 * @property _disabled
1728 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
1731 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent', 'outdent' ],
1734 * @property _alwaysDisabled
1735 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
1738 _alwaysDisabled: { },
1741 * @property _alwaysEnabled
1742 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
1745 _alwaysEnabled: { hiddenelements: true },
1748 * @property _semantic
1749 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
1752 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
1755 * @property _tag2cmd
1756 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
1765 'blockquote': 'formatblock',
1766 'sup': 'superscript',
1768 'img': 'insertimage',
1770 'ul' : 'insertunorderedlist',
1771 'ol' : 'insertorderedlist',
1772 'indent' : 'indent',
1773 'outdent' : 'outdent'
1778 * @description Get the Document of the IFRAME
1781 _getDoc: function() {
1782 //if (this.get && this.get('iframe') && this.get('iframe').get && this.get('iframe').get('element') && this.get('iframe').get('element').contentWindow && this.get('iframe').get('element').contentWindow.document) {
1785 if (this.get('iframe')) {
1786 if (this.get('iframe').get) {
1787 if (this.get('iframe').get('element')) {
1789 if (this.get('iframe').get('element').contentWindow) {
1790 if (this.get('iframe').get('element').contentWindow.document) {
1791 value = this.get('iframe').get('element').contentWindow.document;
1803 * @method _getWindow
1804 * @description Get the Window of the IFRAME
1807 _getWindow: function() {
1808 return this.get('iframe').get('element').contentWindow;
1812 * @method _focusWindow
1813 * @description Attempt to set the focus of the iframes window.
1814 * @param {Boolean} onLoad Safari needs some special care to set the cursor in the iframe
1816 _focusWindow: function(onLoad) {
1817 if (this.browser.webkit) {
1820 * @knownissue Safari Cursor Position
1821 * @browser Safari 2.x
1822 * @description Can't get Safari to place the cursor at the beginning of the text..
1823 * This workaround at least set's the toolbar into the proper state.
1825 this._getSelection().setBaseAndExtent(this._getDoc().body, 0, this._getDoc().body, 1);
1826 this._getSelection().collapse(false);
1828 this._getSelection().setBaseAndExtent(this._getDoc().body, 1, this._getDoc().body, 1);
1829 this._getSelection().collapse(false);
1831 this._getWindow().focus();
1833 if (this._getDoc().queryCommandEnabled('insertimage')) {
1834 this.browser.webkit3 = true;
1837 this._getWindow().focus();
1842 * @method _hasSelection
1843 * @description Determines if there is a selection in the editor document.
1844 * @returns {Boolean}
1846 _hasSelection: function() {
1847 var sel = this._getSelection();
1848 var range = this._getRange();
1852 if (this.browser.ie || this.browser.opera) {
1860 if ((sel != '') && (sel != undefined)) {
1868 * @method _getSelection
1869 * @description Handles the different selection objects across the A-Grade list.
1870 * @returns {Object} Selection Object
1872 _getSelection: function() {
1874 if (this._getDoc() && this._getWindow()) {
1875 if (this._getDoc().selection) {
1876 _sel = this._getDoc().selection;
1878 _sel = this._getWindow().getSelection();
1880 //Handle Safari's lack of Selection Object
1881 if (this.browser.webkit) {
1882 if (_sel.baseNode) {
1883 this._selection = new Object();
1884 this._selection.baseNode = _sel.baseNode;
1885 this._selection.baseOffset = _sel.baseOffset;
1886 this._selection.extentNode = _sel.extentNode;
1887 this._selection.extentOffset = _sel.extentOffset;
1888 } else if (this._selection != null) {
1889 _sel = this._getWindow().getSelection();
1890 _sel.setBaseAndExtent(
1891 this._selection.baseNode,
1892 this._selection.baseOffset,
1893 this._selection.extentNode,
1894 this._selection.extentOffset
1896 this._selection = null;
1905 * @description Handles the different range objects across the A-Grade list.
1906 * @returns {Object} Range Object
1908 _getRange: function(sel) {
1909 var sel = this._getSelection();
1915 if (this.browser.webkit && !sel.getRangeAt) {
1916 var _range = this._getDoc().createRange();
1918 _range.setStart(sel.anchorNode, sel.anchorOffset);
1919 _range.setEnd(sel.focusNode, sel.focusOffset);
1921 _range = this._getWindow().getSelection()+'';
1926 if (this.browser.ie || this.browser.opera) {
1927 return sel.createRange();
1930 if (sel.rangeCount > 0) {
1931 return sel.getRangeAt(0);
1937 * @method _setDesignMode
1938 * @description Sets the designMode of the iFrame document.
1939 * @param {String} state This should be either on or off
1941 _setDesignMode: function(state) {
1943 this._getDoc().designMode = state;
1948 * @method _toggleDesignMode
1949 * @description Toggles the designMode of the iFrame document on and off.
1950 * @returns {String} The state that it was set to.
1952 _toggleDesignMode: function() {
1953 var _dMode = this._getDoc().designMode,
1955 if (_dMode == 'on') {
1958 this._setDesignMode(_state);
1963 * @method _initEditor
1964 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
1966 _initEditor: function() {
1967 if (this.browser.ie) {
1968 this._getDoc().body.style.margin = '0';
1970 this._setDesignMode('on');
1972 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
1973 //Setup Listeners on iFrame
1974 Event.addListener(this._getDoc(), 'mouseup', this._handleMouseUp, this, true);
1975 Event.addListener(this._getDoc(), 'mousedown', this._handleMouseDown, this, true);
1976 Event.addListener(this._getDoc(), 'click', this._handleClick, this, true);
1977 Event.addListener(this._getDoc(), 'dblclick', this._handleDoubleClick, this, true);
1978 Event.addListener(this._getDoc(), 'keypress', this._handleKeyPress, this, true);
1979 Event.addListener(this._getDoc(), 'keyup', this._handleKeyUp, this, true);
1980 Event.addListener(this._getDoc(), 'keydown', this._handleKeyDown, this, true);
1981 this.toolbar.set('disabled', false);
1982 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
1983 if (this.get('dompath')) {
1985 window.setTimeout(function() {
1986 self._writeDomPath.call(self);
1992 * @method _checkLoaded
1993 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
1995 _checkLoaded: function() {
1996 this._contentTimerCounter++;
1997 if (this._contentTimer) {
1998 window.clearTimeout(this._contentTimer);
2000 if (this._contentTimerCounter > 250) {
2001 alert('ERROR: Body Did Not load');
2004 if (this._getDoc() && this._getDoc().body && (this._getDoc().body._rteLoaded == true)) {
2005 //The onload event has fired, clean up after ourselves and fire the _initEditor method
2007 if (!this.browser.ie) {
2008 //IE Doesn't like this..
2009 delete this._getDoc().body._rteLoaded;
2010 this._getDoc().body.removeAttribute('onload');
2016 this._contentTimer = window.setTimeout(function() {
2017 self._checkLoaded.call(self);
2023 * @method _setInitialContent
2024 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
2026 _setInitialContent: function() {
2027 var title = this.STR_TITLE;
2028 var html = this.get('html');
2029 html = html.replace('{TITLE}', title);
2030 html = html.replace('{CONTENT}', this.get('textarea').value);
2031 html = html.replace('{CSS}', this.get('css'));
2032 html = html.replace('{HIDDEN_CSS}', this.get('hiddencss'));
2034 this._getDoc().open();
2035 this._getDoc().write(html);
2036 this._getDoc().close();
2038 this._checkLoaded();
2042 * @method _setMarkupType
2043 * @param {String} action The action to take. Possible values are: css, default or semantic
2044 * @description This method will turn on/off the useCSS execCommand.
2046 _setMarkupType: function(action) {
2047 switch (this.get('markup')) {
2049 this._setEditorStyle(true);
2052 this._setEditorStyle(false);
2055 if (this._semantic[action]) {
2056 this._setEditorStyle(false);
2058 this._setEditorStyle(true);
2064 * Set the editor to use CSS instead of HTML
2065 * @param {Booleen} stat True/False
2067 _setEditorStyle: function(stat) {
2069 this._getDoc().execCommand('useCSS', false, !stat);
2075 * @method _getSelectedElement
2076 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
2077 * @returns {HTMLElement} The currently selected element.
2079 _getSelectedElement: function() {
2080 var doc = this._getDoc();
2081 if (this.browser.ie) {
2082 var range = this._getRange(), elm = null;
2084 elm = range.item ? range.item(0) : range.parentElement();
2085 if (elm == doc.body) {
2090 var sel = this._getSelection(),
2091 range = this._getRange(),
2093 if (!sel || !range) {
2097 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
2098 if (sel.anchorNode.parentNode) { //next check parentNode
2099 elm = sel.anchorNode.parentNode;
2101 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
2102 elm = sel.anchorNode.nextSibling;
2106 if (elm && elm.tagName && (elm.tagName.toLowerCase() == 'br')) {
2111 elm = range.commonAncestorContainer;
2112 if (!range.collapsed) {
2113 if (range.startContainer == range.endContainer) {
2114 if (range.startOffset - range.endOffset < 2) {
2115 if (range.startContainer.hasChildNodes()) {
2116 elm = range.startContainer.childNodes[range.startOffset];
2124 if (this.currentEvent) {
2125 //elm = Event.getTarget(this.currentEvent);
2130 if (!elm && (this.currentElement[0] || this.currentEvent)) {
2131 if (this.currentEvent && (this.currentEvent.keyCode == undefined) && Event.getTarget(this.currentEvent)) {
2132 elm = Event.getTarget(this.currentEvent);
2133 } else if (this.currentEvent && (this.currentEvent.keyCode != undefined) && Event.getTarget(this.currentEvent)) {
2135 elm = this.currentElement[0];
2137 } else if ((elm == this._getDoc().body) && this.currentElement[0] && !this._hasSelection()) {
2138 elm = this.currentElement[0];
2141 if (this.browser.opera || this.browser.webkit) {
2142 if (this.currentEvent && !elm) {
2143 elm = Event.getTarget(this.currentEvent);
2147 if (!elm || !elm.tagName) {
2150 if (elm && elm.tagName && elm.tagName.toLowerCase() == 'html') {
2151 //Safari sometimes gives us the HTML node back..
2159 * @method _getDomPath
2160 * @description This method will attempt to build the DOM path from the currently selected element.
2161 * @returns {Array} An array of node references that will create the DOM Path.
2163 _getDomPath: function() {
2164 var el = this._getSelectedElement();
2168 if (el.ownerDocument != this._getDoc()) {
2171 //Check to see if we get el.nodeName and nodeType
2172 if (el.nodeName && (el.nodeType == 1)) {
2173 domPath[domPath.length] = el;
2176 if (el.nodeName.toUpperCase() == "BODY") {
2182 if (domPath.length == 0) {
2183 if (this._getDoc() && this._getDoc().body) {
2184 domPath[0] = this._getDoc().body;
2187 return domPath.reverse();
2191 * @method _writeDomPath
2192 * @description Write the current DOM path out to the dompath container below the editor.
2194 _writeDomPath: function() {
2195 var path = this._getDomPath(),
2197 for (var i = 0; i < path.length; i++) {
2198 var tag = path[i].tagName.toLowerCase();
2199 if ((tag == 'ol') && (path[i].type)) {
2200 tag += ':' + path[i].type;
2202 if (Dom.hasClass(path[i], 'yui-tag')) {
2203 tag = path[i].getAttribute('tag');
2205 if ((this.get('markup') == 'semantic')) {
2207 case 'b': tag = 'strong'; break;
2208 case 'i': tag = 'em'; break;
2211 if (!Dom.hasClass(path[i], 'yui-non')) {
2212 if (Dom.hasClass(path[i], 'yui-tag')) {
2215 if (path[i].getAttribute('href')) {
2216 pathStr += ':' + path[i].getAttribute('href').replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
2220 var classPath = ((path[i].className != '') ? '.' + path[i].className.replace(/ /g, '.') : '');
2221 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
2224 var pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath;
2226 if (pathStr.length > 10) {
2227 pathStr = pathStr.substring(0, 10) + '...';
2229 pathArr[pathArr.length] = pathStr;
2232 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' ');
2233 //Prevent flickering
2234 if (this.dompath.innerHTML != str) {
2235 this.dompath.innerHTML = str;
2241 * @description Fix href and imgs as well as remove invalid HTML.
2243 _fixNodes: function() {
2244 for (var i in this.invalidHTML) {
2245 if (Lang.hasOwnProperty(this.invalidHTML, i)) {
2246 var tags = this._getDoc().body.getElementsByTagName(i);
2247 for (var h = 0; h < tags.length; h++) {
2248 if (tags[h].parentNode) {
2249 tags[h].parentNode.removeChild(tags[h]);
2254 var as = this._getDoc().body.getElementsByTagName('a');
2256 for (var i = 0; i < as.length; i++) {
2257 var el = this._getDoc().createElement('span');
2258 Dom.addClass(el, 'yui-tag-a');
2259 Dom.addClass(el, 'yui-tag');
2260 el.innerHTML = as[i].innerHTML;
2261 el.setAttribute('tag', 'a');
2262 el.setAttribute('href', as[i].getAttribute('href'));
2263 if (as[i].getAttribute('target') != null) {
2264 el.setAttribute('target', as[i].getAttribute('target'));
2266 as[i].parentNode.replaceChild(el, as[i]);
2270 var imgs = this._getDoc().getElementsByTagName('img');
2271 Dom.addClass(imgs, 'yui-img');
2273 for (var i = 0; i < imgs.length; i++) {
2274 if (imgs[i].getAttribute('href', 2)) {
2275 var url = imgs[i].getAttribute('src', 2);
2276 if ((url != '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
2277 Dom.addClass(imgs[i], this.CLASS_LOCAL_FILE);
2279 Dom.removeClass(imgs[i], this.CLASS_LOCAL_FILE);
2284 var fakeAs = this._getDoc().body.getElementsByTagName('span');
2285 for (var i = 0; i < fakeAs.length; i++) {
2286 if (fakeAs[i].getAttribute('href', 2)) {
2287 var url = fakeAs[i].getAttribute('href', 2);
2288 if ((url != '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
2289 Dom.addClass(fakeAs[i], this.CLASS_LOCAL_FILE);
2291 Dom.removeClass(fakeAs[i], this.CLASS_LOCAL_FILE);
2298 * @method _showHidden
2299 * @description Toggle on/off the hidden.css file.
2301 _showHidden: function() {
2302 if (this._showingHiddenElements) {
2303 this._showingHiddenElements = false;
2304 this.toolbar.deselectButton(this.toolbar.getButtonByValue('hiddenelements'));
2305 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN);
2307 this._showingHiddenElements = true;
2308 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN);
2309 this.toolbar.selectButton(this.toolbar.getButtonByValue('hiddenelements'));
2314 * @method _setCurrentEvent
2315 * @param {Event} ev The event to cache
2316 * @description Sets the current event property
2318 _setCurrentEvent: function(ev) {
2319 if (ev && ev.type) {
2321 this.currentEvent = ev;
2325 * @method _handleClick
2326 * @param {Event} ev The event we are working on.
2327 * @description Handles all click events inside the iFrame document.
2329 _handleClick: function(ev) {
2330 this._setCurrentEvent(ev);
2331 if (this.currentWindow) {
2334 if (!this.browser.webkit) {
2340 * @method _handleMouseUp
2341 * @param {Event} ev The event we are working on.
2342 * @description Handles all mouseup events inside the iFrame document.
2344 _handleMouseUp: function(ev) {
2345 this._setCurrentEvent(ev);
2346 if (this.browser.opera) {
2348 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
2350 * @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.
2352 var sel = Event.getTarget(ev);
2353 if (sel && sel.tagName && (sel.tagName.toLowerCase() == 'img')) {
2356 if (this.operaEvent) {
2357 clearTimeout(this.operaEvent);
2358 this.operaEvent = null;
2359 this._handleDoubleClick(ev);
2361 this.operaEvent = window.setTimeout(function() {
2362 self.operaEvent = false;
2367 //This will stop Safari from selecting the entire document if you select all the text in the editor
2368 if (this.browser.webkit || this.browser.opera) {
2369 if (this.browser.webkit) {
2370 Event.stopEvent(ev);
2374 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
2378 * @method _handleMouseDown
2379 * @param {Event} ev The event we are working on.
2380 * @description Handles all mousedown events inside the iFrame document.
2382 _handleMouseDown: function(ev) {
2383 this._setCurrentEvent(ev);
2384 var sel = Event.getTarget(ev);
2385 if (sel && sel.tagName && (sel.tagName.toLowerCase() == 'img')) {
2386 if (this.browser.webkit) {
2388 Event.stopEvent(ev);
2391 //this.nodeChange();
2392 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
2396 * @method _handleDoubleClick
2397 * @param {Event} ev The event we are working on.
2398 * @description Handles all doubleclick events inside the iFrame document.
2400 _handleDoubleClick: function(ev) {
2401 this._setCurrentEvent(ev);
2402 var sel = Event.getTarget(ev);
2403 if (sel && sel.tagName && (sel.tagName.toLowerCase() == 'img')) {
2404 this.currentElement[0] = sel;
2405 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar });
2406 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
2407 } else if (sel && sel.getAttribute && sel.getAttribute('tag') && (sel.getAttribute('tag').toLowerCase() == 'a')) {
2408 this.currentElement[0] = sel;
2409 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
2410 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
2413 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
2417 * @method _handleKeyUp
2418 * @param {Event} ev The event we are working on.
2419 * @description Handles all keyup events inside the iFrame document.
2421 _handleKeyUp: function(ev) {
2422 this._setCurrentEvent(ev);
2423 switch (ev.keyCode) {
2424 case 37: //Left Arrow
2426 case 39: //Right Arrow
2427 case 40: //Down Arrow
2428 case 46: //Forward Delete
2430 case 65: //The letter a (for ctrl + a and cmd + a)
2431 case 27: //Escape key if window is open
2432 if ((ev.keyCode == 27) && this.currentWindow) {
2438 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
2442 * @method _handleKeyPress
2443 * @param {Event} ev The event we are working on.
2444 * @description Handles all keypress events inside the iFrame document.
2446 _handleKeyPress: function(ev) {
2447 this._setCurrentEvent(ev);
2448 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev });
2452 * @method _handleKeyDown
2453 * @param {Event} ev The event we are working on.
2454 * @description Handles all keydown events inside the iFrame document.
2456 _handleKeyDown: function(ev) {
2457 this._setCurrentEvent(ev);
2458 if (this.currentWindow) {
2464 if (ev.shiftKey && ev.ctrlKey) {
2467 switch (ev.keyCode) {
2468 case 84: //Focus Toolbar Header -- Ctrl + Shift + T
2469 if (ev.shiftKey && ev.ctrlKey) {
2470 this.toolbar._titlebar.firstChild.focus();
2471 Event.stopEvent(ev);
2475 case 27: //Focus After Element - Ctrl + Shift + Esc
2477 this.afterElement.focus();
2478 Event.stopEvent(ev);
2483 action = 'justifyleft';
2486 action = 'justifycenter';
2489 action = 'justifyright';
2492 if (this._hasSelection()) {
2493 this.execCommand('createlink', '');
2494 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar });
2495 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
2506 action = 'underline';
2509 if (this.browser.safari) {
2510 this._getDoc().execCommand('inserttext', false, '\t');
2511 Event.stopEvent(ev);
2515 if (this.browser.ie) {
2516 //Insert a <br> instead of a <p></p> in Internet Explorer
2517 var _range = this._getRange();
2518 var tar = this._getSelectedElement();
2519 if (tar && tar.tagName && (tar.tagName.toLowerCase() != 'li')) {
2521 _range.pasteHTML('<br>');
2522 _range.collapse(false);
2525 Event.stopEvent(ev);
2529 if (doExec && action) {
2530 this.execCommand(action, null);
2531 Event.stopEvent(ev);
2534 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev });
2537 * @method nodeChange
2538 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
2540 nodeChange: function() {
2543 this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this });
2544 if (this.get('dompath')) {
2545 this._writeDomPath();
2547 //Check to see if we are disabled before continuing
2548 if (!this.get('disabled')) {
2549 if (this.STOP_NODE_CHANGE) {
2550 //Reset this var for next action
2551 this.STOP_NODE_CHANGE = false;
2554 var sel = this._getSelection();
2555 var range = this._getRange();
2557 //Handle disabled buttons
2558 for (var i = 0; i < this._disabled.length; i++) {
2559 var _button = this.toolbar.getButtonByValue(this._disabled[i]);
2560 if (_button && _button.get) {
2561 if (!this._hasSelection()) {
2562 //No Selection - disable
2563 this.toolbar.disableButton(_button.get('id'));
2565 if (!this._alwaysDisabled[this._disabled[i]]) {
2566 this.toolbar.enableButton(_button.get('id'));
2569 if (!this._alwaysEnabled[this._disabled[i]]) {
2570 this.toolbar.deselectButton(_button);
2574 //Handle updating the toolbar with active buttons
2575 for (var i = 0; i < this.toolbar._buttonList.length; i++) {
2576 if (!this._alwaysEnabled[this.toolbar._buttonList[i].get('value')]) {
2577 this.toolbar.deselectButton(this.toolbar._buttonList[i]);
2580 var path = this._getDomPath();
2582 for (var i = 0; i < path.length; i++) {
2583 var tag = path[i].tagName.toLowerCase();
2584 if (path[i].getAttribute('tag')) {
2585 var tag = path[i].getAttribute('tag').toLowerCase();
2587 var cmd = this._tag2cmd[tag];
2589 //Bold and Italic styles
2590 if (path[i].style.fontWeight.toLowerCase() == 'bold') {
2593 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
2596 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
2601 olType = path[i].type;
2607 if (!Lang.isArray(cmd)) {
2610 for (var j = 0; j < cmd.length; j++) {
2611 var button = this.toolbar.getButtonByValue(cmd[j]);
2612 this.toolbar.selectButton(button);
2613 this.toolbar.enableButton(button);
2617 switch (path[i].style.textAlign.toLowerCase()) {
2622 var alignType = path[i].style.textAlign.toLowerCase();
2623 if (path[i].style.textAlign.toLowerCase() == 'justify') {
2626 var button = this.toolbar.getButtonByValue('justify' + alignType);
2627 this.toolbar.selectButton(button);
2628 this.toolbar.enableButton(button);
2631 //Handle Ordered List Drop Down - it will reset if olType is null
2632 //this._updateMenuChecked('insertorderedlist', olType);
2636 //Reset Font Family and Size to the inital configs
2637 var fn_button = this.toolbar.getButtonByValue('fontname');
2639 var family = fn_button._configs.label._initialConfig.value;
2640 fn_button.set('label', '<span class="yui-toolbar-fontname-' + _cleanClassName(family) + '">' + family + '</span>');
2641 this._updateMenuChecked('fontname', family);
2644 var fs_button = this.toolbar.getButtonByValue('fontsize');
2646 fs_button.set('label', fs_button._configs.label._initialConfig.value);
2649 var hd_button = this.toolbar.getButtonByValue('heading');
2651 hd_button.set('label', hd_button._configs.label._initialConfig.value);
2652 this._updateMenuChecked('heading', 'none');
2657 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
2661 * @method _updateMenuChecked
2662 * @param {Object} button The command identifier of the button you want to check
2663 * @param {String} value The value of the menu item you want to check
2664 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
2665 * @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.
2667 _updateMenuChecked: function(button, value, tbar) {
2669 tbar = this.toolbar;
2671 var _button = tbar.getButtonByValue(button);
2672 var _menuItems = _button.getMenu().getItems();
2673 if (_menuItems.length == 0) {
2674 _button.getMenu()._onBeforeShow();
2675 _menuItems = _button.getMenu().getItems();
2677 for (var i = 0; i < _menuItems.length; i++) {
2678 _menuItems[i].cfg.setProperty('checked', false);
2679 if (_menuItems[i].value == value) {
2680 _menuItems[i].cfg.setProperty('checked', true);
2686 * @method _handleToolbarClick
2687 * @param {Event} ev The event that triggered the button click
2688 * @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.
2690 _handleToolbarClick: function(ev) {
2693 var cmd = ev.button.value;
2694 if (ev.button.menucmd) {
2696 cmd = ev.button.menucmd;
2698 if (this.STOP_EXEC_COMMAND) {
2699 this.STOP_EXEC_COMMAND = false;
2702 this.execCommand(cmd, value);
2704 Event.stopEvent(ev);
2708 * @method _setupAfterElement
2709 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
2711 _setupAfterElement: function() {
2712 if (!this.afterElement) {
2713 this.afterElement = document.createElement('h2');
2714 this.afterElement.className = 'yui-editor-skipheader';
2715 this.afterElement.tabIndex = '-1';
2716 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR;
2717 this.get('element_cont').get('firstChild').appendChild(this.afterElement);
2721 * @property EDITOR_PANEL_ID
2722 * @description HTML id to give the properties window in the DOM.
2725 EDITOR_PANEL_ID: 'yui-editor-panel',
2727 * @property SEP_DOMPATH
2728 * @description The value to place in between the Dom path items
2733 * @property STR_LEAVE_EDITOR
2734 * @description The accessibility string for the element after the iFrame
2737 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.',
2739 * @property STR_BEFORE_EDITOR
2740 * @description The accessibility string for the element before the iFrame
2743 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Control + Shift + T to place focus on the toolbar and navigate between option heading names. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift [ aligns text left</li> <li>Control Shift | centers text</li> <li>Control Shift ] aligns text right</li> <li>Control Shift L adds an HTML link</li> <li>To exit this text editor use the keyboard shortcut Control Shift ESC.</li></ul>',
2745 * @property STR_CLOSE_WINDOW
2746 * @description The Title of the close button in the Editor Window
2749 STR_CLOSE_WINDOW: 'Close Window',
2751 * @property STR_CLOSE_WINDOW_NOTE
2752 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
2755 STR_CLOSE_WINDOW_NOTE: 'To close this window use the Escape key',
2757 * @property STR_TITLE
2758 * @description The Title of the HTML document that is created in the iFrame
2761 STR_TITLE: 'Rich Text Area.',
2763 * @property STR_IMAGE_HERE
2764 * @description The text to place in the URL textbox when using the blankimage.
2767 STR_IMAGE_HERE: 'Image Url Here',
2769 * @property STR_IMAGE_PROP_TITLE
2770 * @description The title for the Image Property Editor Window
2773 STR_IMAGE_PROP_TITLE: 'Image Options',
2775 * @property STR_IMAGE_URL
2776 * @description The label string for Image URL
2779 STR_IMAGE_URL: 'Image Url',
2781 * @property STR_IMAGE_TITLE
2782 * @description The label string for Image Description
2785 STR_IMAGE_TITLE: 'Description',
2787 * @property STR_IMAGE_SIZE
2788 * @description The label string for Image Size
2791 STR_IMAGE_SIZE: 'Size',
2793 * @property STR_IMAGE_ORIG_SIZE
2794 * @description The label string for Original Image Size
2797 STR_IMAGE_ORIG_SIZE: 'Original Size',
2799 * @property STR_IMAGE_COPY
2800 * @description The label string for the image copy and paste message for Opera and Safari
2803 STR_IMAGE_COPY: '<span class="tip"><span class="icon icon-info"></span><strong>Note:</strong>To move this image just highlight it, cut, and paste where ever you\'d like.</span>',
2805 * @property STR_IMAGE_PADDING
2806 * @description The label string for the image padding.
2809 STR_IMAGE_PADDING: 'Padding',
2811 * @property STR_IMAGE_BORDER
2812 * @description The label string for the image border.
2815 STR_IMAGE_BORDER: 'Border',
2817 * @property STR_IMAGE_TEXTFLOW
2818 * @description The label string for the image text flow.
2821 STR_IMAGE_TEXTFLOW: 'Text Flow',
2823 * @property STR_LOCAL_FILE_WARNING
2824 * @description The label string for the local file warning.
2827 STR_LOCAL_FILE_WARNING: '<span class="tip"><span class="icon icon-warn"></span><strong>Note:</strong>This image/link points to a file on your computer and will not be accessible to others on the internet.</span>',
2829 * @property STR_LINK_PROP_TITLE
2830 * @description The label string for the Link Property Editor Window.
2833 STR_LINK_PROP_TITLE: 'Link Options',
2835 * @property STR_LINK_PROP_REMOVE
2836 * @description The label string for the Remove link from text link inside the property editor.
2839 STR_LINK_PROP_REMOVE: 'Remove link from text',
2841 * @property STR_LINK_URL
2842 * @description The label string for the Link URL.
2845 STR_LINK_URL: 'Link URL',
2847 * @property STR_LINK_NEW_WINDOW
2848 * @description The string for the open in a new window label.
2851 STR_LINK_NEW_WINDOW: 'Open in a new window.',
2853 * @property STR_LINK_TITLE
2854 * @description The string for the link description.
2857 STR_LINK_TITLE: 'Description',
2860 * @property STOP_EXEC_COMMAND
2861 * @description Set to true when you want the default execCommand function to not process anything
2864 STOP_EXEC_COMMAND: false,
2867 * @property STOP_NODE_CHANGE
2868 * @description Set to true when you want the default nodeChange function to not process anything
2871 STOP_NODE_CHANGE: false,
2874 * @property CLASS_HIDDEN
2875 * @description CSS class applied to the body when the hiddenelements button is pressed.
2878 CLASS_HIDDEN: 'hidden',
2881 * @property CLASS_LOCAL_FILE
2882 * @description CSS class applied to an element when it's found to have a local url.
2885 CLASS_LOCAL_FILE: 'warning-localfile',
2888 * @property CLASS_CONTAINER
2889 * @description Default CSS class to apply to the editors container element
2892 CLASS_CONTAINER: 'yui-editor-container',
2895 * @property CLASS_EDITABLE
2896 * @description Default CSS class to apply to the editors iframe element
2899 CLASS_EDITABLE: 'yui-editor-editable',
2902 * @property CLASS_EDITABLE_CONT
2903 * @description Default CSS class to apply to the editors iframe's parent element
2906 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
2909 * @property CLASS_PREFIX
2910 * @description Default prefix for dynamically created class names
2913 CLASS_PREFIX: 'yui-editor',
2916 * @description Standard browser detection
2919 browser: YAHOO.env.ua,
2922 * @description The Editor class' initialization method
2924 init: function(p_oElement, p_oAttributes) {
2925 YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes);
2926 this.get('element_cont').addClass(this.CLASS_CONTAINER);
2927 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT);
2928 this.get('iframe').addClass(this.CLASS_EDITABLE);
2931 * @method initAttributes
2932 * @description Initializes all of the configuration attributes used to create
2934 * @param {Object} attr Object literal specifying a set of
2935 * configuration attributes used to create the editor.
2937 initAttributes: function(attr) {
2938 YAHOO.widget.Editor.superclass.initAttributes.call(this, attr);
2944 * @description A reference to the textarea element that we are replacing
2948 this.setAttributeConfig('textarea', {
2949 value: attr.textarea,
2954 * @description The height of the editor iframe container, not including the toolbar..
2955 * @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
2958 this.setAttributeConfig('height', {
2959 value: attr.height || Dom.getStyle(self.get('textarea'), 'height'),
2964 * @description The width of the editor container.
2965 * @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
2968 this.setAttributeConfig('width', {
2969 value: attr.width || Dom.getStyle(this.get('textarea'), 'width'),
2974 * @config blankimage
2975 * @description The CSS used to show/hide hidden elements on the page
2976 * @default 'assets/blankimage.png'
2979 this.setAttributeConfig('blankimage', {
2980 value: attr.blankimage || this._getBlankImage()
2984 * @description The CSS used to show/hide hidden elements on the page, these rules must be prefixed with the class provided in <code>this.CLASS_HIDDEN</code>
2985 * @default <code><pre>
2986 .hidden div, .hidden p, .hidden span, .hidden img {
2987 border: 1px dotted #ccc;
2997 this.setAttributeConfig('hiddencss', {
2998 value: attr.hiddencss || '.hidden div,.hidden p,.hidden span,.hidden img { border: 1px dotted #ccc; } .hidden .yui-non { border: none; } .hidden img { padding: 2px; }',
3003 * @description The Base CSS used to format the content of the editor
3004 * @default <code><pre>body {
3005 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
3008 color: blue; text-decoration: underline;
3010 span.yui-tag-blockquote {
3011 margin: 1em; display: block;
3013 span.yui-tag-indent {
3014 margin-left: 1em; display: block;
3016 .warning-localfile {
3017 border-bottom: 1px dashed red !important;
3021 this.setAttributeConfig('css', {
3022 value: attr.css || 'body { padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } span.yui-tag-a { color: blue; text-decoration: underline; } span.yui-tag-blockquote { margin: 1em; display: block; } span.yui-tag-indent { margin-left: 1em; display: block; } .warning-localfile { border-bottom: 1px dashed red !important; }',
3027 * @description The default HTML to be written to the iframe document before the contents are loaded
3028 * @default This HTML requires a few things if you are to override:
3029 <p><code>{TITLE}, {CSS}, {HIDDEN_CSS}</code> and <code>{CONTENT}</code> need to be there, they are passed to YAHOO.lang.substitute to be replace with other strings.<p>
3030 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
3033 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
3036 <title>{TITLE}</title>
3037 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
3045 <body onload="document.body._rteLoaded = true;">
3053 this.setAttributeConfig('html', {
3054 value: attr.html || '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd"><html><head><title>{TITLE}</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><style>{CSS}</style><style>{HIDDEN_CSS}</style></head><body onload="document.body._rteLoaded = true;">{CONTENT}</body></html>',
3059 * @config handleSubmit
3060 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
3061 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
3062 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
3066 this.setAttributeConfig('handleSubmit', {
3069 method: function(exec) {
3071 var ta = this.get('textarea');
3073 Event.addListener(ta.form, 'submit', function() {
3083 * @description Internal config for holding the iframe element.
3087 this.setAttributeConfig('iframe', {
3093 * @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.
3094 All Toolbar buttons are also disabled so they cannot be used.
3099 this.setAttributeConfig('disabled', {
3101 method: function(disabled) {
3104 this._setDesignMode('off');
3105 this.toolbar.set('disabled', true);
3106 this._mask = document.createElement('DIV');
3107 Dom.setStyle(this._mask, 'height', '100%');
3108 Dom.setStyle(this._mask, 'width', '100%');
3109 Dom.setStyle(this._mask, 'position', 'absolute');
3110 Dom.setStyle(this._mask, 'top', '0');
3111 Dom.setStyle(this._mask, 'left', '0');
3112 Dom.setStyle(this._mask, 'opacity', '.5');
3113 Dom.addClass(this._mask, 'yui-editor-masked');
3114 this.get('iframe').get('parentNode').appendChild(this._mask);
3118 this._mask.parentNode.removeChild(this._mask);
3120 this.toolbar.set('disabled', false);
3121 this._setDesignMode('on');
3122 this._focusWindow();
3128 * @config element_cont
3129 * @description Internal config for the editors container
3133 this.setAttributeConfig('element_cont', {
3138 * @config toolbar_cont
3139 * @description Internal config for the toolbars container
3143 this.setAttributeConfig('toolbar_cont', {
3149 * @description The default toolbar config.
3150 * @default This config is too large to display here, view the code to see it: <a href="editor.js.html"></a>
3153 this.setAttributeConfig('toolbar', {
3154 value: attr.toolbar || {
3155 /* {{{ Defaut Toolbar Config */
3157 titlebar: 'Text Editing Tools',
3160 { group: 'fontstyle', label: 'Font Name and Size',
3162 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
3164 { text: 'Arial', checked: true },
3165 { text: 'Arial Black' },
3166 { text: 'Comic Sans MS' },
3167 { text: 'Courier New' },
3168 { text: 'Lucida Console' },
3170 { text: 'Times New Roman' },
3171 { text: 'Trebuchet MS' },
3175 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
3178 { type: 'separator' },
3179 { group: 'textstyle', label: 'Font Style',
3181 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
3182 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
3183 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' },
3184 { type: 'separator' },
3185 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true },
3186 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true },
3187 { type: 'separator' },
3188 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true },
3189 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true },
3190 { type: 'separator' },
3191 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true },
3192 { type: 'push', label: 'Hidden Elements', value: 'hiddenelements' }
3195 { type: 'separator' },
3196 { group: 'alignment', label: 'Alignment',
3198 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
3199 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
3200 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
3201 { type: 'push', label: 'Justify', value: 'justifyfull' }
3204 { type: 'separator' },
3205 { group: 'parastyle', label: 'Paragraph Style',
3207 { type: 'select', label: 'Normal', value: 'heading', disabled: true,
3209 { text: 'Normal', value: 'none', checked: true },
3210 { text: 'Header 1', value: 'h1' },
3211 { text: 'Header 2', value: 'h2' },
3212 { text: 'Header 3', value: 'h3' },
3213 { text: 'Header 4', value: 'h4' },
3214 { text: 'Header 5', value: 'h5' },
3215 { text: 'Header 6', value: 'h6' }
3220 { type: 'separator' },
3221 { group: 'indentlist', label: 'Indenting and Lists',
3223 { type: 'push', label: 'Indent', value: 'indent', disabled: true },
3224 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
3225 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
3226 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
3228 { type: 'menu', label: 'Create an Ordered List', value: 'insertorderedlist',
3230 { text: '1,2,3,4', value: '1', checked: true },
3231 { text: 'A,B,C,D', value: 'A' },
3232 { text: 'a,b,c,d', value: 'a' },
3233 { text: 'I,II,III,IV', value: 'I' },
3234 { text: 'i,ii,iii,iv', value: 'i' }
3240 { type: 'separator' },
3241 { group: 'insertitem', label: 'Insert Item',
3243 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
3244 { type: 'push', label: 'Insert Image', value: 'insertimage' }
3251 method: function(toolbar) {
3256 * @description Should the editor animate window movements
3257 * @default false unless Animation is found, then true
3260 this.setAttributeConfig('animate', {
3262 validator: function(value) {
3264 if (!YAHOO.util.Anim) {
3272 * @description A reference to the panel we are using for windows.
3276 this.setAttributeConfig('panel', {
3279 validator: function(value) {
3281 if (!YAHOO.widget.Panel) {
3288 * @config localFileWarning
3289 * @description Should we throw the warning if we detect a file that is local to their machine?
3293 this.setAttributeConfig('localFileWarning', {
3298 * @description Toggle the display of the current Dom path below the editor
3302 this.setAttributeConfig('dompath', {
3304 method: function(dompath) {
3305 if (dompath && !this.dompath) {
3306 this.dompath = document.createElement('DIV');
3307 this.dompath.id = this.get('id') + '_dompath';
3308 Dom.addClass(this.dompath, 'dompath');
3309 this.get('element_cont').get('firstChild').appendChild(this.dompath);
3310 if (this.get('iframe')) {
3311 this._writeDomPath();
3313 } else if (!dompath && this.dompath) {
3314 this.dompath.parentNode.removeChild(this.dompath);
3315 this.dompath = null;
3317 this._setupAfterElement();
3322 * @description Should we try to adjust the markup for the following types: semantic, css or default
3323 * @default "semantic"
3326 this.setAttributeConfig('markup', {
3328 validator: function(markup) {
3329 switch (markup.toLowerCase()) {
3340 this.on('afterRender', function() {
3341 this._renderPanel();
3346 * @method _getBlankImage
3347 * @description Retrieves the full url of the image to use as the blank image.
3348 * @returns {String} The URL to the blank image
3350 _getBlankImage: function() {
3351 if (!this.DOMReady) {
3352 this._queue[this._queue.length] = ['_getBlankImage', arguments];
3355 var div = document.createElement('div');
3356 div.style.position = 'absolute';
3357 div.style.top = '-9999px';
3358 div.style.left = '-9999px';
3359 div.className = this.CLASS_PREFIX + '-blankimage';
3360 document.body.appendChild(div);
3361 var img = YAHOO.util.Dom.getStyle(div, 'background-image');
3362 img = img.replace('url(', '').replace(')', '').replace(/"/g, '');
3363 this.set('blankimage', img);
3368 * @method _handleFontSize
3369 * @description Handles the font size button in the toolbar.
3370 * @param {Object} o Object returned from Toolbar's buttonClick Event
3372 _handleFontSize: function(o) {
3373 var button = this.toolbar.getButtonById(o.button.id);
3374 var value = button.get('label') + 'px';
3375 this.execCommand('fontsize', value);
3376 this.STOP_EXEC_COMMAND = true;
3380 * @method _handleColorPicker
3381 * @description Handles the colorpicker buttons in the toolbar.
3382 * @param {Object} o Object returned from Toolbar's buttonClick Event
3384 _handleColorPicker: function(o) {
3386 var value = '#' + o.color;
3387 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
3388 this.execCommand(cmd, value);
3393 * @method _handleAlign
3394 * @description Handles the alignment buttons in the toolbar.
3395 * @param {Object} o Object returned from Toolbar's buttonClick Event
3397 _handleAlign: function(o) {
3398 var button = this.toolbar.getButtonById(o.button.id);
3400 for (var i = 0; i < o.button.menu.length; i++) {
3401 if (o.button.menu[i].value == o.button.value) {
3402 cmd = o.button.menu[i].value;
3405 var value = this._getSelection();
3407 this.execCommand(cmd, value);
3408 this.STOP_EXEC_COMMAND = true;
3412 * @method _handleAfterNodeChange
3413 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
3415 _handleAfterNodeChange: function() {
3416 var path = this._getDomPath();
3417 for (var i = 0; i < path.length; i++) {
3419 tag = elm.tagName.toLowerCase(),
3424 if (elm.getAttribute('tag')) {
3425 tag = elm.getAttribute('tag');
3428 family = elm.getAttribute('face');
3429 if (Dom.getStyle(elm, 'font-family')) {
3430 family = Dom.getStyle(elm, 'font-family');
3432 var fn_button = this.toolbar.getButtonByValue('fontname');
3434 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
3435 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
3437 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
3441 family = fn_button._configs.label._initialConfig.value;
3443 fn_button.set('label', '<span class="yui-toolbar-fontname-' + _cleanClassName(family) + '">' + family + '</span>');
3444 this._updateMenuChecked('fontname', family);
3447 var fs_button = this.toolbar.getButtonByValue('fontsize');
3449 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'));
3450 if ((fontsize == null) || isNaN(fontsize)) {
3451 fontsize = fs_button._configs.label._initialConfig.value;
3453 fs_button.set('label', ''+fontsize);
3456 if (tag.substring(0, 1) == 'h') {
3457 var hd_button = this.toolbar.getButtonByValue('heading');
3459 for (var b = 0; b < hd_button._configs.menu.value.length; b++) {
3460 if (hd_button._configs.menu.value[b].value.toLowerCase() == tag) {
3461 hd_button.set('label', hd_button._configs.menu.value[b].text);
3464 this._updateMenuChecked('heading', tag);
3469 if (elm && elm.tagName && (elm.tagName.toLowerCase() != 'body')) {
3470 this.toolbar.enableButton(fn_button);
3471 this.toolbar.enableButton(fs_button);
3477 * @method _handleInsertImageClick
3478 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
3480 _handleInsertImageClick: function() {
3481 //this.toolbar.disableButton(this.toolbar.getButtonByValue('insertimage'));
3482 this.on('afterExecCommand', function() {
3483 var el = this.currentElement[0],
3491 win = new YAHOO.widget.EditorWindow('insertimage', {
3496 el = this._getSelectedElement();
3499 if (el.getAttribute('src')) {
3500 src = el.getAttribute('src', 2);
3501 if (src.indexOf(this.get('blankimage')) != -1) {
3502 src = this.STR_IMAGE_HERE;
3506 if (el.getAttribute('alt', 2)) {
3507 title = el.getAttribute('alt', 2);
3509 if (el.getAttribute('title', 2)) {
3510 title = el.getAttribute('title', 2);
3512 height = parseInt(el.height);
3513 width = parseInt(el.width);
3514 if (el.style.height) {
3515 height = parseInt(el.style.height);
3517 if (el.style.width) {
3518 width = parseInt(el.style.width);
3520 if (el.style.margin) {
3521 padding = parseInt(el.style.margin);
3524 el._height = height;
3529 var oheight = el._height;
3530 var owidth = el._width;
3533 var str = '<label for="insertimage_url"><strong>' + this.STR_IMAGE_URL + ':</strong> <input type="text" id="insertimage_url" value="' + src + '" size="40"></label>';
3534 var body = document.createElement('div');
3535 body.innerHTML = str;
3537 var tbarCont = document.createElement('div');
3538 tbarCont.id = 'img_toolbar';
3539 body.appendChild(tbarCont);
3541 var str2 = '<label for="insertimage_title"><strong>' + this.STR_IMAGE_TITLE + ':</strong> <input type="text" id="insertimage_title" value="' + title + '" size="40"></label>';
3542 var div = document.createElement('div');
3543 div.innerHTML = str2;
3544 body.appendChild(div);
3550 var tbar = new YAHOO.widget.Toolbar(tbarCont, {
3553 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
3555 { type: 'spin', label: ''+padding, value: 'padding', range: [0, 50] }
3558 { type: 'separator' },
3559 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
3561 { type: 'select', label: 'Border Size', value: 'bordersize',
3563 { text: 'none', value: '0', checked: true },
3564 { text: '----', value: '1' },
3565 { text: '----', value: '2' },
3566 { text: '----', value: '3' },
3567 { text: '----', value: '4' },
3568 { text: '----', value: '5' }
3571 { type: 'select', label: 'Border Type', value: 'bordertype', disabled: true,
3573 { text: '----', value: 'solid', checked: true },
3574 { text: '----', value: 'dashed' },
3575 { text: '----', value: 'dotted' }
3578 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
3581 { type: 'separator' },
3582 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
3584 { type: 'push', label: 'Left', value: 'left' },
3585 { type: 'push', label: 'Inline', value: 'inline' },
3586 { type: 'push', label: 'Block', value: 'block' },
3587 { type: 'push', label: 'Right', value: 'right' }
3595 var btype = 'solid';
3596 if (el.style.borderLeftWidth) {
3597 bsize = parseInt(el.style.borderLeftWidth);
3599 if (el.style.borderLeftStyle) {
3600 btype = el.style.borderLeftStyle;
3602 var bs_button = tbar.getButtonByValue('bordersize');
3603 var bSizeStr = ((parseInt(bsize) > 0) ? '----' : 'none');
3604 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">'+bSizeStr+'</span>');
3605 this._updateMenuChecked('bordersize', bsize, tbar);
3607 var bs_button = tbar.getButtonByValue('bordertype');
3608 bs_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">----</span>');
3609 this._updateMenuChecked('bordertype', btype, tbar);
3610 if (parseInt(bsize) > 0) {
3611 tbar.enableButton(tbar.getButtonByValue('bordertype'));
3612 tbar.enableButton(tbar.getButtonByValue('bordercolor'));
3615 var cont = tbar.get('cont');
3616 var hw = document.createElement('div');
3617 hw.className = 'yui-toolbar-group yui-toolbar-group-padding height-width';
3618 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
3620 if ((height != oheight) || (width != owidth)) {
3621 orgSize = '<span class="info">' + this.STR_IMAGE_ORIG_SIZE + '<br>'+ owidth +' x ' + oheight + '</span>';
3623 hw.innerHTML += '<span><input type="text" size="3" value="'+width+'" id="insertimage_width"> x <input type="text" size="3" value="'+height+'" id="insertimage_height"></span>' + orgSize;
3624 cont.insertBefore(hw, cont.firstChild);
3626 Event.onAvailable('insertimage_width', function() {
3627 Event.on('insertimage_width', 'blur', function() {
3628 var value = parseInt(Dom.get('insertimage_width').value);
3630 el.style.width = value + 'px';
3635 Event.onAvailable('insertimage_height', function() {
3636 Event.on('insertimage_height', 'blur', function() {
3637 var value = parseInt(Dom.get('insertimage_height').value);
3639 el.style.height = value + 'px';
3645 if (el.align == 'right') {
3646 tbar.selectButton(tbar.getButtonByValue('right'));
3647 } else if (el.align == 'left') {
3648 tbar.selectButton(tbar.getButtonByValue('left'));
3649 } else if (el.style.display == 'block') {
3650 tbar.selectButton(tbar.getButtonByValue('block'));
3652 tbar.selectButton(tbar.getButtonByValue('inline'));
3654 if (parseInt(el.style.marginLeft) > 0) {
3655 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft));
3657 if (el.style.borderSize) {
3658 tbar.selectButton(tbar.getButtonByValue('bordersize'));
3659 tbar.selectButton(tbar.getButtonByValue(parseInt(el.style.borderSize)));
3662 tbar.on('colorPickerClicked', function(o) {
3663 var size = '1', type = 'solid', color = 'black';
3665 if (el.style.borderLeftWidth) {
3666 size = parseInt(el.style.borderLeftWidth);
3668 if (el.style.borderLeftStyle) {
3669 type = el.style.borderLeftStyle;
3671 if (el.style.borderLeftColor) {
3672 color = el.style.borderLeftColor;
3674 var borderString = size + 'px ' + type + ' #' + o.color;
3675 el.style.border = borderString;
3676 }, this.toolbar, true);
3678 tbar.on('buttonClick', function(o) {
3679 var value = o.button.value;
3680 if (o.button.menucmd) {
3681 value = o.button.menucmd
3683 var size = '1', type = 'solid', color = 'black';
3685 /* All border calcs are done on the left border
3686 since our default interface only supports
3687 one border size/type and color */
3688 if (el.style.borderLeftWidth) {
3689 size = parseInt(el.style.borderLeftWidth);
3691 if (el.style.borderLeftStyle) {
3692 type = el.style.borderLeftStyle;
3694 if (el.style.borderLeftColor) {
3695 color = el.style.borderLeftColor;
3699 var borderString = parseInt(o.button.value) + 'px ' + type + ' ' + color;
3700 el.style.border = borderString;
3701 if (parseInt(o.button.value) > 0) {
3702 tbar.enableButton(tbar.getButtonByValue('bordertype'));
3703 tbar.enableButton(tbar.getButtonByValue('bordercolor'));
3705 tbar.disableButton(tbar.getButtonByValue('bordertype'));
3706 tbar.disableButton(tbar.getButtonByValue('bordercolor'));
3710 var borderString = size + 'px ' + o.button.value + ' ' + color;
3711 el.style.border = borderString;
3715 tbar.deselectAllButtons();
3716 el.style.display = '';
3717 el.align = o.button.value;
3720 tbar.deselectAllButtons();
3721 el.style.display = '';
3725 tbar.deselectAllButtons();
3726 el.style.display = 'block';
3727 el.align = 'center';
3730 var _button = tbar.getButtonById(o.button.id);
3731 el.style.margin = _button.get('label') + 'px';
3734 tbar.selectButton(tbar.getButtonByValue(o.button.value));
3738 win.setHeader(this.STR_IMAGE_PROP_TITLE);
3740 if ((this.browser.webkit && !this.browser.webkit3) || this.browser.opera) {
3741 var str = this.STR_IMAGE_COPY;
3744 this.openWindow(win);
3747 //Set event after openWindow..
3748 Event.onAvailable('insertimage_url', function() {
3749 window.setTimeout(function() {
3750 YAHOO.util.Dom.get('insertimage_url').focus();
3752 YAHOO.util.Dom.get('insertimage_url').select();
3756 if (this.get('localFileWarning')) {
3757 Event.on('insertimage_url', 'blur', function() {
3758 var url = Dom.get('insertimage_url');
3759 if ((url.value != '') && ((url.value.indexOf('file:/') != -1) || (url.value.indexOf(':\\') != -1))) {
3760 //Local File throw Warning
3761 Dom.addClass(url, 'warning');
3762 var str = this.STR_LOCAL_FILE_WARNING;
3763 this.get('panel').setFooter(str);
3765 Dom.removeClass(url, 'warning');
3766 this.get('panel').setFooter(' ');
3767 if ((this.browser.webkit && !this.browser.webkit3) || this.browser.opera) {
3768 var str = this.STR_IMAGE_COPY;
3769 this.get('panel').setFooter(str);
3772 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
3773 this.currentElement[0].setAttribute('src', url.value);
3774 var img = new Image();
3776 window.setTimeout(function() {
3777 YAHOO.util.Dom.get('insertimage_height').value = img.height;
3778 YAHOO.util.Dom.get('insertimage_width').value = img.width;
3779 if (!self.currentElement[0]._height) {
3780 self.currentElement[0]._height = img.height;
3782 if (!self.currentElement[0]._width) {
3783 self.currentElement[0]._width = img.width;
3788 img.src = url.value;
3798 * @method _handleInsertImageWindowClose
3799 * @description Handles the closing of the Image Properties Window.
3801 _handleInsertImageWindowClose: function() {
3802 var url = Dom.get('insertimage_url');
3803 var title = Dom.get('insertimage_title');
3804 var el = this.currentElement[0];
3805 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
3806 el.setAttribute('src', url.value);
3807 el.setAttribute('title', title.value);
3808 el.setAttribute('alt', title.value);
3809 //this.toolbar.enableButton(this.toolbar.getButtonByValue('insertimage'));
3811 //No url/src given, remove the node from the document
3812 el.parentNode.removeChild(el);
3817 * @method _handleCreateLinkClick
3818 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
3820 _handleCreateLinkClick: function() {
3821 this.on('afterExecCommand', function() {
3823 var win = new YAHOO.widget.EditorWindow('createlink', {
3827 var el = this.currentElement[0],
3833 if (el.getAttribute('href') != null) {
3834 url = el.getAttribute('href');
3835 if ((url != '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
3836 //Local File throw Warning
3837 var str = this.STR_LOCAL_FILE_WARNING;
3844 if (el.getAttribute('title') != null) {
3845 title = el.getAttribute('title');
3847 if (el.getAttribute('target') != null) {
3848 target = el.getAttribute('target');
3851 var str = '<label for="createlink_url"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="createlink_url" id="createlink_url" value="' + url + '"' + ((localFile) ? ' class="warning"' : '') + '></label>';
3852 str += '<label for="createlink_target"><strong> </strong><input type="checkbox" name="createlink_target_" id="createlink_target" value="_blank"' + ((target) ? ' checked' : '') + '> ' + this.STR_LINK_NEW_WINDOW + '</label>';
3853 str += '<label for="createlink_title"><strong>' + this.STR_LINK_TITLE + ':</strong> <input type="text" name="createlink_title" id="createlink_title" value="' + title + '"></label>';
3855 var body = document.createElement('div');
3856 body.innerHTML = str;
3858 var unlinkCont = document.createElement('div');
3859 unlinkCont.className = 'removeLink';
3860 var unlink = document.createElement('a');
3862 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
3863 unlink.title = this.STR_LINK_PROP_REMOVE;
3864 Event.on(unlink, 'click', function(ev) {
3865 Event.stopEvent(ev);
3866 this.execCommand('unlink');
3869 unlinkCont.appendChild(unlink);
3870 body.appendChild(unlinkCont);
3872 win.setHeader(this.STR_LINK_PROP_TITLE);
3875 Event.onAvailable('createlink_url', function() {
3876 window.setTimeout(function() {
3878 YAHOO.util.Dom.get('createlink_url').focus();
3881 Event.on('createlink_url', 'blur', function() {
3882 var url = Dom.get('createlink_url');
3883 if ((url.value != '') && ((url.value.indexOf('file:/') != -1) || (url.value.indexOf(':\\') != -1))) {
3884 //Local File throw Warning
3885 Dom.addClass(url, 'warning');
3886 var str = this.STR_LOCAL_FILE_WARNING;
3887 this.get('panel').setFooter(str);
3889 Dom.removeClass(url, 'warning');
3890 this.get('panel').setFooter(' ');
3895 this.openWindow(win);
3900 * @method _handleCreateLinkWindowClose
3901 * @description Handles the closing of the Link Properties Window.
3903 _handleCreateLinkWindowClose: function() {
3904 var url = Dom.get('createlink_url');
3905 var target = Dom.get('createlink_target');
3906 var title = Dom.get('createlink_title');
3907 var el = this.currentElement[0];
3908 if (url && url.value) {
3909 var urlValue = url.value;
3910 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
3911 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
3912 //Found an @ sign, prefix with mailto:
3913 urlValue = 'mailto:' + urlValue;
3915 /* :// not found adding */
3916 urlValue = 'http:/'+'/' + urlValue;
3919 el.setAttribute('href', urlValue);
3920 if (target.checked) {
3921 el.setAttribute('target', target.value);
3923 el.setAttribute('target', '');
3925 el.setAttribute('title', ((title.value) ? title.value : ''));
3928 el.removeAttribute('tag');
3929 Dom.removeClass(el, 'yui-tag-a');
3930 Dom.removeClass(el, 'yui-tag');
3931 Dom.addClass(el, 'yui-non');
3937 * @description Causes the toolbar and the editor to render and replace the textarea.
3939 render: function() {
3940 if (!this.DOMReady) {
3941 this._queue[this._queue.length] = ['render', arguments];
3945 var tbarConf = this.get('toolbar');
3946 //Set the toolbar to disabled until content is loaded
3947 tbarConf.disabled = true;
3948 //Create Toolbar instance
3949 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
3950 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
3953 this.toolbar.on('toolbarCollapsed', function() {
3954 if (this.currentWindow) {
3958 this.toolbar.on('toolbarExpanded', function() {
3959 if (this.currentWindow) {
3963 this.toolbar.on('fontsizeClick', function(o) {
3964 this._handleFontSize(o);
3967 this.toolbar.on('colorPickerClicked', function(o) {
3968 this._handleColorPicker(o);
3971 this.toolbar.on('alignClick', function(o) {
3972 this._handleAlign(o);
3974 this.on('afterNodeChange', function() {
3975 this._handleAfterNodeChange();
3977 this.toolbar.on('insertimageClick', function() {
3978 this._handleInsertImageClick();
3980 this.on('windowinsertimageClose', function() {
3981 this._handleInsertImageWindowClose();
3983 this.toolbar.on('createlinkClick', function() {
3984 this._handleCreateLinkClick();
3986 this.on('windowcreatelinkClose', function() {
3987 this._handleCreateLinkWindowClose();
3991 //Replace Textarea with editable area
3993 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
3996 if (!this.beforeElement) {
3997 this.beforeElement = document.createElement('h2');
3998 this.beforeElement.className = 'yui-editor-skipheader';
3999 this.beforeElement.tabIndex = '-1';
4000 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4001 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4004 Dom.setStyle(this.get('textarea'), 'display', 'none');
4005 this.get('element_cont').appendChild(this.get('element'));
4006 this.get('element_cont').setStyle('display', 'block');
4009 //Set height and width of editor container
4010 this.get('element_cont').setStyle('width', this.get('width'));
4011 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
4013 this.get('iframe').setStyle('width', '100%'); //WIDTH
4014 //this.get('iframe').setStyle('_width', '99%'); //WIDTH
4015 this.get('iframe').setStyle('height', '100%');
4019 window.setTimeout(function() {
4020 self._setInitialContent.call(self);
4023 this.fireEvent('afterRender', { type: 'afterRender', target: this });
4026 * @method execCommand
4027 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml)
4028 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
4029 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
4031 execCommand: function(action, value) {
4032 this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
4033 if (this.STOP_EXEC_COMMAND) {
4034 this.STOP_EXEC_COMMAND = false;
4037 this._setMarkupType(action);
4038 if (this.browser.ie) {
4039 this._getWindow().focus();
4042 var _sel = this._getSelection();
4043 var _range = this._getRange();
4044 var _selEl = this._getSelectedElement();
4048 switch (action.toLowerCase()) {
4050 if (this.browser.ie) {
4051 action = 'formatblock';
4053 if (value == 'none') {
4054 if ((_sel && _sel.tagName && (_sel.tagName.toLowerCase().substring(0,1) == 'h')) || (_sel && _sel.parentNode && _sel.parentNode.tagName && (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h'))) {
4055 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
4056 _sel = _sel.parentNode;
4058 var _span = this._getDoc().createElement('span');
4059 _span.className = 'yui-non';
4060 _span.innerHTML = _sel.innerHTML;
4061 _sel.parentNode.replaceChild(_span, _sel);
4065 if (this.browser.ie || this.browser.webkit || this.browser.opera) {
4066 this._createCurrentElement(value);
4072 if (this.browser.gecko || this.browser.opera) {
4073 this._setEditorStyle(true);
4074 action = 'hilitecolor';
4077 case 'hiddenelements':
4082 //var el = this._getSelectedElement();
4083 var el = this.currentElement[0];
4084 el.removeAttribute('title');
4085 el.removeAttribute('tag');
4086 el.removeAttribute('target');
4087 el.removeAttribute('href');
4088 YAHOO.util.Dom.addClass(el, 'yui-non');
4089 YAHOO.util.Dom.removeClass(el, 'yui-tag-a');
4090 YAHOO.util.Dom.removeClass(el, 'yui-tag');
4094 var el = this._getSelectedElement();
4095 if (!el || (el.getAttribute('tag') != 'a')) {
4096 this._createCurrentElement('a');
4098 this.currentElement[0] = el;
4104 value = this.get('blankimage');
4108 * @browser Safari 2.x
4109 * @description The issue here is that we have no way of knowing where the cursor position is
4110 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4113 var el = this._getSelectedElement();
4115 if (el && el.tagName && (el.tagName.toLowerCase() == 'img')) {
4116 this.currentElement[0] = el;
4119 if (this._getDoc().queryCommandEnabled(action)) {
4120 this._getDoc().execCommand('insertimage', false, value);
4121 var imgs = this._getDoc().getElementsByTagName('img');
4122 for (var i = 0; i < imgs.length; i++) {
4123 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
4124 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
4125 this.currentElement[0] = imgs[i];
4130 this._createCurrentElement('img');
4131 var _img = this._getDoc().createElement('img');
4132 _img.setAttribute('src', value);
4133 YAHOO.util.Dom.addClass(_img, 'yui-img');
4134 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
4135 this.currentElement[0] = _img;
4144 * @browser Safari 2.x
4145 * @description The issue here is that we have no way of knowing where the cursor position is
4146 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4148 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
4149 this._createCurrentElement('img');
4150 var _span = this._getDoc().createElement('span');
4151 _span.innerHTML = value;
4152 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
4154 } else if (this.browser.ie) {
4155 var _range = this._getRange();
4157 _range.item(0).outerHTML = value;
4159 _range.pasteHTML(value);
4164 case 'removeformat':
4166 * @knownissue Remove Format issue
4167 * @browser Safari 2.x
4168 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
4169 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
4170 * So here we are making the best possible guess and acting on it.
4172 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
4173 this._createCurrentElement('span');
4174 YAHOO.util.Dom.addClass(this.currentElement[0], 'yui-non');
4175 var re= /<\S[^><]*>/g;
4176 var str = this.currentElement[0].innerHTML.replace(re, '');
4177 var _txt = this._getDoc().createTextNode(str);
4178 this.currentElement[0].parentNode.parentNode.replaceChild(_txt, this.currentElement[0].parentNode);
4185 if (this.browser.webkit) {
4186 var tag = action.toLowerCase().substring(0, 3);
4187 this._createCurrentElement(tag);
4188 if (this.currentElement[0].parentNode.tagName.toLowerCase() == tag) {
4189 var span = this._getDoc().createElement('span');
4190 span.innerHTML = this.currentElement[0].innerHTML;
4191 YAHOO.util.Dom.addClass(span, 'yui-non');
4192 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4195 var _sub = this._getDoc().createElement(tag);
4196 _sub.innerHTML = this.currentElement[0].innerHTML;
4197 this.currentElement[0].parentNode.replaceChild(_sub, this.currentElement[0]);
4203 value = 'blockquote';
4204 if (this.browser.webkit) {
4205 this._createCurrentElement('blockquote');
4206 if (YAHOO.util.Dom.hasClass(this.currentElement[0].parentNode, 'yui-tag-blockquote')) {
4207 var span = this._getDoc().createElement('span');
4208 span.innerHTML = this.currentElement[0].innerHTML;
4209 YAHOO.util.Dom.addClass(span, 'yui-non');
4210 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4214 var tar = Event.getTarget(this.currentEvent);
4215 if (tar && tar.tagName && (tar.tagName.toLowerCase() == 'blockquote')) {
4216 var span = this._getDoc().createElement('span');
4217 span.innerHTML = tar.innerHTML;
4218 YAHOO.util.Dom.addClass(span, 'yui-non');
4219 tar.parentNode.replaceChild(span, tar);
4226 this._createCurrentElement(action.toLowerCase());
4227 if (this.currentElement[0].parentNode) {
4228 if (action.toLowerCase() == 'outdent') {
4229 if (YAHOO.util.Dom.hasClass(this.currentElement[0].parentNode, 'yui-tag-indent')) {
4230 var span = this._getDoc().createElement('span');
4231 span.innerHTML = this.currentElement[0].innerHTML;
4232 YAHOO.util.Dom.addClass(span, 'yui-non');
4233 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4239 case 'insertorderedlist':
4240 case 'insertunorderedlist':
4242 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
4243 * @browser Safari 2.x
4244 * The issue with this workaround is that when applied to a set of text
4245 * that has BR's in it, Safari may or may not pick up the individual items as
4246 * list items. This is fixed in WebKit (Safari 3)
4248 var tag = ((action.toLowerCase() == 'insertorderedlist') ? 'ol' : 'ul');
4249 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) || this.browser.opera) {
4250 if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
4251 var selEl = this._getSelectedElement();
4252 if ((selEl.tagName.toLowerCase() == 'li') && (selEl.parentNode.tagName.toLowerCase() == tag)) {
4253 var el = selEl.parentNode;
4254 var list = this._getDoc().createElement('span');
4255 YAHOO.util.Dom.addClass(list, 'yui-non');
4257 var lis = el.getElementsByTagName('li');
4258 for (var i = 0; i < lis.length; i++) {
4259 str += lis[i].innerHTML + '<br>';
4261 list.innerHTML = str;
4264 this._createCurrentElement(tag.toLowerCase());
4265 var el = this.currentElement[0];
4266 var list = this._getDoc().createElement(tag);
4270 var li = this._getDoc().createElement('li');
4271 li.innerHTML = el.innerHTML + ' ';
4272 list.appendChild(li);
4274 el.parentNode.replaceChild(list, el);
4277 var el = this._getSelectedElement();
4278 if ((el.tagName.toLowerCase() == 'li') && (el.parentNode.tagName.toLowerCase() == tag) || (this.browser.ie && this._getRange().parentElement && this._getRange().parentElement.tagName && (this._getRange().parentElement.tagName.toLowerCase() == 'li'))) { //we are in a list..
4279 if (this.browser.ie) {
4282 var lis = el.parentNode.getElementsByTagName('li');
4283 for (var i = 0; i < lis.length; i++) {
4284 str += lis[i].innerHTML + '<br>';
4286 var newEl = this._getDoc().createElement('span');
4287 newEl.innerHTML = str;
4288 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
4291 this._getDoc().execCommand(action, '', el.parentNode);
4295 if (this.browser.opera) {
4297 window.setTimeout(function() {
4298 var lis = self._getDoc().getElementsByTagName('li');
4299 for (var i = 0; i < lis.length; i++) {
4300 if (lis[i].innerHTML.toLowerCase() == '<br>') {
4301 lis[i].parentNode.parentNode.removeChild(lis[i].parentNode);
4306 if (this.browser.ie && exec) {
4308 if (this._getRange().html) {
4309 html = '<li>' + this._getRange().html+ '</li>';
4311 html = '<li>' + this._getRange().text + '</li>';
4314 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
4320 var selEl = this._getSelectedElement();
4321 this.currentFont = value;
4322 if (selEl && selEl.tagName && !this._hasSelection()) {
4323 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
4328 if ((this.currentElement.length > 0) && (!this._hasSelection())) {
4329 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
4331 this._createCurrentElement('span', {'fontSize': value });
4338 this._getDoc().execCommand(action, false, value);
4343 this.on('afterExecCommand', function() {
4344 this.unsubscribeAll('afterExecCommand');
4347 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
4352 * @method _createCurrentElement
4353 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
4354 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
4355 * @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.
4356 * It will then search the document for a span 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
4357 * <code>this.currentElement</code>, so we now have an element reference to the element that was just modified. At this point we can use standard DOM manipulation to change it as we see fit.
4359 _createCurrentElement: function(tagName, tagStyle) {
4360 var tagName = ((tagName) ? tagName : 'a'),
4361 sel = this._getSelection(),
4364 _doc = this._getDoc();
4366 if (this.currentFont) {
4370 tagStyle.fontFamily = this.currentFont;
4371 this.currentFont = null;
4373 this.currentElement = [];
4375 var _elCreate = function() {
4383 var el = _doc.createElement(tagName);
4386 var el = _doc.createElement('span');
4387 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
4388 YAHOO.util.Dom.addClass(el, 'yui-tag');
4389 el.setAttribute('tag', tagName);
4392 for (var i in tagStyle) {
4393 if (YAHOO.util.Lang.hasOwnProperty(tagStyle, i)) {
4394 el.style[i] = tagStyle[i];
4402 if (!this._hasSelection()) {
4403 if (this._getDoc().queryCommandEnabled('insertimage')) {
4404 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
4405 var imgs = this._getDoc().getElementsByTagName('img');
4406 for (var i = 0; i < imgs.length; i++) {
4407 if (imgs[i].getAttribute('src', 2) == 'yui-tmp-img') {
4409 imgs[i].parentNode.replaceChild(el, imgs[i]);
4410 this.currentElement[this.currentElement.length] = el;
4411 //this.currentElement = el;
4415 if (this.currentEvent) {
4416 tar = YAHOO.util.Event.getTarget(this.currentEvent);
4419 tar = this._getDoc().body;
4425 * @browser Safari 2.x
4426 * @description The issue here is that we have no way of knowing where the cursor position is
4427 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4430 if (tar.tagName.toLowerCase() == 'body') {
4431 tar.appendChild(el);
4432 } else if (tar.nextSibling) {
4433 tar.parentNode.insertBefore(el, tar.nextSibling);
4435 tar.parentNode.appendChild(el);
4437 //this.currentElement = el;
4438 this.currentElement[this.currentElement.length] = el;
4439 this.currentEvent = null;
4440 if (this.browser.webkit) {
4441 //Force Safari to focus the new element
4442 this._getSelection().setBaseAndExtent(el, 0, el, 0);
4443 this._getSelection().collapse(true);
4447 //Force CSS Styling for this action...
4448 this._setEditorStyle(true);
4449 this._getDoc().execCommand('fontname', false, 'yui-tmp');
4451 /* TODO: This needs to be cleaned up.. */
4452 var _tmp1 = this._getDoc().getElementsByTagName('font');
4453 var _tmp2 = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
4454 var _tmp3 = this._getDoc().getElementsByTagName('span');
4455 var _tmp4 = this._getDoc().getElementsByTagName('i');
4456 var _tmp5 = this._getDoc().getElementsByTagName('b');
4457 for (var e = 0; e < _tmp1.length; e++) {
4458 _tmp[_tmp.length] = _tmp1[e];
4460 for (var e = 0; e < _tmp2.length; e++) {
4461 _tmp[_tmp.length] = _tmp2[e];
4463 for (var e = 0; e < _tmp3.length; e++) {
4464 _tmp[_tmp.length] = _tmp3[e];
4466 for (var e = 0; e < _tmp4.length; e++) {
4467 _tmp[_tmp.length] = _tmp4[e];
4469 for (var e = 0; e < _tmp5.length; e++) {
4470 _tmp[_tmp.length] = _tmp5[e];
4472 for (var i = 0; i < _tmp.length; i++) {
4473 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
4474 var el = _elCreate();
4475 el.innerHTML = _tmp[i].innerHTML;
4476 if (_tmp[i].parentNode) {
4477 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
4478 //this.currentElement = el;
4479 this.currentElement[this.currentElement.length] = el;
4480 this.currentEvent = null;
4481 if (this.browser.webkit) {
4482 //Force Safari to focus the new element
4483 this._getSelection().setBaseAndExtent(el, 0, el, 0);
4484 this._getSelection().collapse(true);
4486 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
4487 this._getSelection().empty();
4489 if (this.browser.gecko) {
4490 this._getSelection().collapseToStart();
4495 var len = this.currentElement.length;
4496 for (var i = 0; i < len; i++) {
4497 if ((i + 1) != len) { //Skip the last one in the list
4498 if (this.currentElement[i] && this.currentElement[i].nextSibling) {
4499 if (this.currentElement[i].tagName && (this.currentElement[i].tagName.toLowerCase() != 'br')) {
4500 this.currentElement[this.currentElement.length] = this.currentElement[i].nextSibling;
4509 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
4511 saveHTML: function() {
4512 var html = this.cleanHTML();
4513 this.get('textarea').value = html;
4517 * @method setEditorHTML
4518 * @param {String} html The html content to load into the editor
4519 * @description Loads HTML into the editors body
4521 setEditorHTML: function(html) {
4522 this._getDoc().body.innerHTML = html;
4526 * @method getEditorHTML
4527 * @description Gets the unprocessed/unfiltered HTML from the editor
4529 getEditorHTML: function() {
4530 return this._getDoc().body.innerHTML;
4534 * @param {String} html The unfiltered HTML
4535 * @description Process the HTML with a few regexes to clean it up and stabilize the output
4536 * @returns {String} The filtered HTML
4538 cleanHTML: function(html) {
4539 //Start Filtering Output
4542 var html = this.getEditorHTML();
4544 //Make some backups...
4545 html = html.replace(/<div><br><\/div>/gi, '<YUI_BR>');
4546 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
4547 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
4548 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
4549 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>');
4550 html = html.replace(/<br>/gi, '<YUI_BR>');
4551 html = html.replace(/<br\/>/gi, '<YUI_BR>');
4552 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
4553 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
4554 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
4556 //Convert b and i tags to strong and em tags
4557 html = html.replace(/<i([^>]*)>/gi, '<em$1>');
4558 html = html.replace(/<\/i>/gi, '</em>');
4559 html = html.replace(/<b([^>]*)>/gi, '<strong$1>');
4560 html = html.replace(/<\/b>/gi, '</strong>');
4562 html = html.replace(/<font/gi, '<font');
4563 html = html.replace(/<\/font>/gi, '</font>');
4564 html = html.replace(/<span/gi, '<span');
4565 html = html.replace(/<\/span>/gi, '</span>');
4566 html = html.replace(/<u/gi, '<u');
4567 html = html.replace(/\/u>/gi, '/u>');
4569 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
4570 html = html.replace(/\/ol>/gi, '/ol>');
4571 html = html.replace(/<li/gi, '<li');
4572 html = html.replace(/\/li>/gi, '/li>');
4574 //Handle the sudo A tags
4575 html = html.replace(new RegExp('<span ([^>]*) tag="a" ([^>]*)>([^>]*)<\/span>', 'gi'), '<a $1 $2>$3</a>');
4577 //Safari only regexes
4578 if (this.browser.webkit) {
4579 //<DIV><SPAN class="Apple-style-span" style="line-height: normal;">Test THis</SPAN></DIV>
4580 html = html.replace(/Apple-style-span/gi, '');
4581 html = html.replace(/style="line-height: normal;"/gi, '');
4584 //yui-tag-a yui-tag yui-non yui-img
4585 html = html.replace(/yui-tag-a/gi, '');
4586 html = html.replace(/yui-tag-span/gi, '');
4587 html = html.replace(/yui-tag/gi, '');
4588 html = html.replace(/yui-non/gi, '');
4589 html = html.replace(/yui-img/gi, '');
4590 html = html.replace(/ tag="span"/gi, '');
4591 html = html.replace(/ class=""/gi, '');
4592 html = html.replace(/ class=" "/gi, '');
4593 html = html.replace(/ class=" "/gi, '');
4594 html = html.replace(/ target=""/gi, '');
4595 html = html.replace(/ title=""/gi, '');
4597 //Other string cleanup
4598 html = html.replace(/<br><li/gi, '<li');
4600 //html = html.replace(/<span >([^>]*)<\/span>/gi, '$1');
4601 //html = html.replace(/<div>([^>]*)<\/div>/gi, '$1');
4604 //Replace our backups with the real thing
4605 html = html.replace(/<YUI_BR>/g, '<br>');
4606 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img$1>');
4607 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
4608 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
4613 * @method clearEditorDoc
4614 * @description Clear the doc of the Editor
4616 clearEditorDoc: function() {
4617 this._getDoc().body.innerHTML = ' ';
4621 * @method _renderPanel
4622 * @description Renders the panel used for Editor Windows to the document so we can start using it..
4623 * @returns {<a href="YAHOO.widget.Panel.html">YAHOO.widget.Panel</a>}
4625 _renderPanel: function() {
4626 if (!YAHOO.widget.EditorInfo.panel) {
4627 var panel = new YAHOO.widget.Panel(this.EDITOR_PANEL_ID, {
4635 YAHOO.widget.EditorInfo.panel = panel;
4637 var panel = YAHOO.widget.EditorInfo.panel;
4639 this.set('panel', panel);
4641 this.get('panel').setBody('---');
4642 this.get('panel').setHeader(' ');
4643 this.get('panel').setFooter(' ');
4644 if (this.DOMReady) {
4645 this.get('panel').render(document.body);
4646 Dom.addClass(this.get('panel').element, 'yui-editor-panel');
4648 Event.onDOMReady(function() {
4649 this.get('panel').render(document.body);
4650 Dom.addClass(this.get('panel').element, 'yui-editor-panel');
4653 this.get('panel').showEvent.subscribe(function() {
4654 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
4656 return this.get('panel');
4659 * @method openWindow
4660 * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance
4661 * @description Opens a new "window/panel"
4663 openWindow: function(win) {
4664 this.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
4665 Event.addListener(document, 'keypress', this._closeWindow, this, true);
4666 if (YAHOO.widget.EditorInfo.window.win && YAHOO.widget.EditorInfo.window.scope) {
4667 YAHOO.widget.EditorInfo.window.scope.closeWindow.call(YAHOO.widget.EditorInfo.window.scope);
4669 YAHOO.widget.EditorInfo.window.win = win;
4670 YAHOO.widget.EditorInfo.window.scope = this;
4673 xy = Dom.getXY(this.currentElement[0]),
4674 elXY = Dom.getXY(this.get('iframe').get('element')),
4675 panel = this.get('panel'),
4676 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
4677 wWidth = (parseInt(win.attrs.width) / 2),
4680 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
4682 body = document.createElement('div');
4683 body.className = this.CLASS_PREFIX + '-body-cont';
4685 var _note = document.createElement('h3');
4686 _note.className = 'yui-editor-skipheader';
4687 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
4688 body.appendChild(_note);
4689 form = document.createElement('form');
4690 form.setAttribute('method', 'GET');
4691 var windowName = win.name;
4692 Event.addListener(form, 'submit', function(ev) {
4693 var evName = 'window' + windowName + 'Submit';
4694 self.fireEvent(evName, { type: evName, target: this });
4695 Event.stopEvent(ev);
4697 body.appendChild(form);
4699 Dom.setStyle(panel.element.firstChild, 'width', win.attrs.width);
4700 if (Lang.isObject(win.body)) { //Assume it's a reference
4701 form.appendChild(win.body);
4702 } else { //Assume it's a string
4703 var _tmp = document.createElement('div');
4704 _tmp.innerHTML = win.body;
4705 form.appendChild(_tmp);
4707 var _close = document.createElement('span');
4708 _close.innerHTML = 'X';
4709 _close.title = this.STR_CLOSE_WINDOW;
4710 _close.className = 'close';
4711 Event.addListener(_close, 'click', function() {
4714 var _knob = document.createElement('span');
4715 _knob.innerHTML = '^';
4716 _knob.className = 'knob';
4719 var _header = document.createElement('h3');
4720 _header.innerHTML = win.header;
4722 panel.cfg.setProperty('width', win.attrs.width);
4723 panel.setHeader(' '); //Clear the current header
4724 panel.appendToHeader(_header);
4725 _header.appendChild(_close);
4726 _header.appendChild(_knob);
4727 panel.setBody(' '); //Clear the current body
4728 panel.setFooter(' '); //Clear the current footer
4729 if (win.footer != null) {
4730 panel.setFooter(win.footer);
4732 panel.appendToBody(body); //Append the new DOM node to it
4733 panel.showEvent.subscribe(function() {
4734 Event.addListener(panel.element, 'click', function(ev) {
4735 Event.stopPropagation(ev);
4738 panel.hideEvent.subscribe(function() {
4739 this.currentWindow = null;
4740 var evName = 'window' + windowName + 'Close';
4741 this.fireEvent(evName, { type: evName, target: this });
4744 this.currentWindow = win;
4745 this.moveWindow(true);
4747 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
4750 * @method moveWindow
4751 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
4752 * @description Realign the window with the currentElement and reposition the knob above the panel.
4754 moveWindow: function(force) {
4755 if (!this.currentWindow) {
4758 var win = this.currentWindow,
4759 xy = Dom.getXY(this.currentElement[0]),
4760 elXY = Dom.getXY(this.get('iframe').get('element')),
4761 panel = this.get('panel'),
4762 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
4763 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
4764 wWidth = (parseInt(win.attrs.width) / 2),
4766 orgXY = panel.cfg.getProperty('xy'),
4769 newXY[0] = ((newXY[0] - wWidth) + 20);
4770 //Account for the Scroll bars in a scrolled editor window.
4771 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
4772 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
4776 if (this.currentElement[0].tagName && (this.currentElement[0].tagName.toLowerCase() == 'img')) {
4777 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
4778 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
4779 newXY[1] = (newXY[1] + 75); //Placeholder sizea
4781 var w = parseInt(this.currentElement[0].width);
4782 var h = parseInt(this.currentElement[0].height);
4783 newXY[0] = (newXY[0] + (w / 2));
4784 newXY[1] = (newXY[1] + h);
4786 newXY[1] = newXY[1] + 15;
4788 if (Dom.getStyle(this.currentElement[0], 'fontSize').indexOf('px') != -1) {
4789 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize')) + 5;
4791 newXY[1] = newXY[1] + 20;
4794 if (newXY[0] < elXY[0]) {
4795 newXY[0] = elXY[0] + 5;
4799 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth))) {
4800 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth)) - (wWidth * 2) - 5);
4805 var xDiff = (newXY[0] - orgXY[0]);
4806 var yDiff = (newXY[1] - orgXY[1]);
4812 //Convert negative numbers to positive so we can get the difference in distance
4813 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
4814 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
4816 if (((xDiff > 10) || (yDiff > 10)) || force) { //Only move the window if it's supposed to move more than 10px or force was passed (new window)
4820 if (this.currentElement[0].width) {
4821 elW = (parseInt(this.currentElement[0].width) / 2);
4824 var leftOffset = xy[0] + elXY[0] + elW;
4825 _knobLeft = leftOffset - newXY[0];
4826 //Check to see if the knob will go off either side & reposition it
4827 if (_knobLeft > (parseInt(win.attrs.width) - 40)) {
4828 _knobLeft = parseInt(win.attrs.width) - 40;
4829 } else if (_knobLeft < 40) {
4832 if (isNaN(_knobLeft)) {
4837 _knob.style.left = _knobLeft + 'px';
4839 if (this.get('animate')) {
4840 Dom.setStyle(panel.element, 'opacity', '0');
4841 var anim = new YAHOO.util.Anim(panel.element, {
4846 }, .1, YAHOO.util.Easing.easeOut);
4847 panel.cfg.setProperty('xy', newXY);
4848 anim.onComplete.subscribe(function() {
4849 if (this.browser.ie) {
4850 panel.element.style.filter = 'none';
4855 panel.cfg.setProperty('xy', newXY);
4858 if (this.get('animate')) {
4859 var anim = new YAHOO.util.Anim(panel.element, {}, .5, YAHOO.util.Easing.easeOut);
4868 anim.onComplete.subscribe(function() {
4869 panel.cfg.setProperty('xy', newXY);
4871 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
4872 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, .5, YAHOO.util.Easing.easeOut)
4874 var _knobAnim = new YAHOO.util.Anim(_knob, {
4878 }, .75, YAHOO.util.Easing.easeOut);
4880 iframeAnim.animate();
4881 _knobAnim.animate();
4883 _knob.style.left = _knobLeft + 'px';
4884 panel.cfg.setProperty('xy', newXY);
4891 * @method _closeWindow
4892 * @description Close the currently open EditorWindow with the Escape key.
4893 * @param {Event} ev The keypress Event that we are trapping
4895 _closeWindow: function(ev) {
4896 if (ev.keyCode == 27) {
4897 if (this.currentWindow) {
4903 * @method closeWindow
4904 * @description Close the currently open EditorWindow.
4906 closeWindow: function() {
4907 YAHOO.widget.EditorInfo.window = {};
4908 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
4909 this.currentWindow = null;
4910 this.get('panel').hide();
4911 this.get('panel').cfg.setProperty('xy', [-900,-900]);
4912 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
4913 this.unsubscribeAll('afterExecCommand');
4914 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
4915 this._focusWindow();
4916 Event.removeListener(document, 'keypress', this._closeWindow);
4920 * @description Destroys the editor, all of it's elements and objects.
4923 destroy: function() {
4925 this.toolbar.destroy();
4926 Dom.setStyle(this.get('textarea'), 'display', 'block');
4927 var textArea = this.get('textarea');
4928 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
4929 this.get('element_cont').get('element').innerHTML = '';
4930 //Brutal Object Destroy
4931 for (var i in this) {
4932 if (Lang.hasOwnProperty(this, i)) {
4940 * @description Returns a string representing the editor.
4943 toString: function() {
4945 if (this.get && this.get('element_cont')) {
4946 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
4953 * @event toolbarLoaded
4954 * @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.
4955 * @type YAHOO.util.CustomEvent
4958 * @event afterRender
4959 * @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.
4960 * @type YAHOO.util.CustomEvent
4963 * @event editorContentLoaded
4964 * @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.
4965 * @type YAHOO.util.CustomEvent
4968 * @event editorMouseUp
4969 * @param {Event} ev The DOM Event that occured
4970 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
4971 * @type YAHOO.util.CustomEvent
4974 * @event editorMouseDown
4975 * @param {Event} ev The DOM Event that occured
4976 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
4977 * @type YAHOO.util.CustomEvent
4980 * @event editorDoubleClick
4981 * @param {Event} ev The DOM Event that occured
4982 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
4983 * @type YAHOO.util.CustomEvent
4986 * @event editorKeyUp
4987 * @param {Event} ev The DOM Event that occured
4988 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
4989 * @type YAHOO.util.CustomEvent
4992 * @event editorKeyPress
4993 * @param {Event} ev The DOM Event that occured
4994 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
4995 * @type YAHOO.util.CustomEvent
4998 * @event editorKeyDown
4999 * @param {Event} ev The DOM Event that occured
5000 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5001 * @type YAHOO.util.CustomEvent
5004 * @event beforeNodeChange
5005 * @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.
5006 * @type YAHOO.util.CustomEvent
5009 * @event afterNodeChange
5010 * @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.
5011 * @type YAHOO.util.CustomEvent
5014 * @event beforeExecCommand
5015 * @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.
5016 * @type YAHOO.util.CustomEvent
5019 * @event afterExecCommand
5020 * @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.
5021 * @type YAHOO.util.CustomEvent
5024 * @event beforeOpenWindow
5025 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
5026 * @param {Overlay} panel The Overlay object that is used to create the window.
5027 * @description Event fires before an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5028 * @type YAHOO.util.CustomEvent
5031 * @event afterOpenWindow
5032 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
5033 * @param {Overlay} panel The Overlay object that is used to create the window.
5034 * @description Event fires after an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5035 * @type YAHOO.util.CustomEvent
5038 * @event closeWindow
5039 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
5040 * @description Event fires after an Editor Window is closed. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5041 * @type YAHOO.util.CustomEvent
5044 * @event windowCMDOpen
5045 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
5046 * @param {Overlay} panel The Overlay object that is used to create the window.
5047 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is opened.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkOpen event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5048 * @type YAHOO.util.CustomEvent
5051 * @event windowCMDClose
5052 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object
5053 * @param {Overlay} panel The Overlay object that is used to create the window.
5054 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is closed.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkClose event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
5055 * @type YAHOO.util.CustomEvent
5059 * @description Singleton object used to track the open window objects and panels across the various open editors
5063 YAHOO.widget.EditorInfo = {
5067 * @description A reference to the currently open window object in any editor on the page.
5068 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>
5074 * @description A reference to the currently open panel in any editor on the page.
5075 * @type Object <a href="YAHOO.widget.Panel.html">YAHOO.widget.Panel</a>
5081 * @description Class to hold Window information between uses. We use the same panel to show the windows, so using this will allow you to configure a window before it is shown.
5082 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
5083 * @class EditorWindow
5084 * @param {String} name The name of the window.
5085 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
5087 YAHOO.widget.EditorWindow = function(name, attrs) {
5091 * @description A unique name for the window
5093 this.name = name.replace(' ', '_');
5097 * @description The window attributes
5102 YAHOO.widget.EditorWindow.prototype = {
5106 * @description Holds a cache of the DOM for the window so we only have to build it once..
5112 * @description Holder for the header of the window, used in Editor.openWindow
5118 * @description Holder for the body of the window, used in Editor.openWindow
5124 * @description Holder for the footer of the window, used in Editor.openWindow
5129 * @description Sets the header for the window.
5130 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
5132 setHeader: function(str) {
5137 * @description Sets the body for the window.
5138 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
5140 setBody: function(str) {
5145 * @description Sets the footer for the window.
5146 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
5148 setFooter: function(str) {
5153 * @description Returns a string representing the EditorWindow.
5156 toString: function() {
5157 return 'Editor Window (' + this.name + ')';
5164 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.3.0", build: "442"});