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
) {
35 YAHOO
.log('Toolbar Initalizing', 'info', 'Toolbar');
36 YAHOO
.log(arguments
.length
+ ' arguments passed to constructor', 'info', 'Toolbar');
38 if (Lang
.isObject(arguments
[0]) && !Dom
.get(el
).nodeType
) {
41 var local_attrs
= (attrs
|| {});
45 attributes
: local_attrs
49 if (Lang
.isString(el
) && Dom
.get(el
)) {
50 oConfig
.element
= Dom
.get(el
);
51 } else if (Lang
.isObject(el
) && Dom
.get(el
) && Dom
.get(el
).nodeType
) {
52 oConfig
.element
= Dom
.get(el
);
56 if (!oConfig
.element
) {
57 YAHOO
.log('No element defined, creating toolbar container', 'warn', 'Toolbar');
58 oConfig
.element
= document
.createElement('DIV');
59 oConfig
.element
.id
= Dom
.generateId();
61 if (local_attrs
.container
&& Dom
.get(local_attrs
.container
)) {
62 YAHOO
.log('Container found in config appending to it (' + Dom
.get(local_attrs
.container
).id
+ ')', 'info', 'Toolbar');
63 Dom
.get(local_attrs
.container
).appendChild(oConfig
.element
);
68 if (!oConfig
.element
.id
) {
69 oConfig
.element
.id
= ((Lang
.isString(el
)) ? el
: Dom
.generateId());
70 YAHOO
.log('No element ID defined for toolbar container, creating..', 'warn', 'Toolbar');
72 YAHOO
.log('Initing toolbar with id: ' + oConfig
.element
.id
, 'info', 'Toolbar');
74 var cont
= document
.createElement('DIV');
75 oConfig
.attributes
.cont
= cont
;
76 Dom
.addClass(cont
, 'yui-toolbar-subcont')
77 oConfig
.element
.appendChild(cont
);
79 oConfig
.attributes
.element
= oConfig
.element
;
80 oConfig
.attributes
.id
= oConfig
.element
.id
;
82 YAHOO
.widget
.Toolbar
.superclass
.constructor.call(this, oConfig
.element
, oConfig
.attributes
);
88 * @method _addMenuClasses
90 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items
91 * @param {String} ev The event that fired.
92 * @param {Array} na Array of event information.
93 * @param {Object} o Button config object.
96 function _addMenuClasses(ev
, na
, o
) {
97 Dom
.addClass(this.element
, 'yui-toolbar-' + o
.get('value') + '-menu');
98 if (Dom
.hasClass(o
._button
.parentNode
.parentNode
, 'yui-toolbar-select')) {
99 Dom
.addClass(this.element
, 'yui-toolbar-select-menu');
101 var items
= this.getItems();
102 for (var i
= 0; i
< items
.length
; i
++) {
103 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()));
104 Dom
.addClass(items
[i
].element
, 'yui-toolbar-' + o
.get('value') + '-' + ((items
[i
].value
) ? items
[i
].value
.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g
, '-')));
109 YAHOO
.extend(YAHOO
.widget
.Toolbar
, YAHOO
.util
.Element
, {
112 * @description The DragDrop instance associated with the Toolbar
117 * @property _colorData
118 * @description Object reference containing colors hex and text values.
123 '#111111': 'Obsidian',
124 '#2D2D2D': 'Dark Gray',
128 '#8B8B8B': 'Concrete',
130 '#B9B9B9': 'Titanium',
132 '#D0D0D0': 'Light Gray',
135 '#BFBF00': 'Pumpkin',
138 '#FFFF80': 'Pale Yellow',
140 '#525330': 'Raw Siena',
143 '#7F7F00': 'Paprika',
148 '#80FF00': 'Chartreuse',
150 '#C0FF80': 'Pale Lime',
151 '#DFFFBF': 'Light Mint',
153 '#668F5A': 'Lime Gray',
156 '#8A9B55': 'Pistachio',
157 '#B7C296': 'Light Jade',
158 '#E6EBD5': 'Breakwater',
159 '#00BF00': 'Spring Frost',
160 '#00FF80': 'Pastel Green',
161 '#40FFA0': 'Light Emerald',
162 '#80FFC0': 'Sea Foam',
163 '#BFFFDF': 'Sea Mist',
164 '#033D21': 'Dark Forrest',
166 '#7FA37C': 'Medium Green',
168 '#8DAE94': 'Yellow Gray Green',
169 '#ACC6B5': 'Aqua Lung',
170 '#DDEBE2': 'Sea Vapor',
173 '#40FFFF': 'Turquoise Blue',
174 '#80FFFF': 'Light Aqua',
175 '#BFFFFF': 'Pale Cyan',
176 '#033D3D': 'Dark Teal',
177 '#347D7E': 'Gray Turquoise',
178 '#609A9F': 'Green Blue',
179 '#007F7F': 'Seaweed',
180 '#96BDC4': 'Green Gray',
181 '#B5D1D7': 'Soapstone',
182 '#E2F1F4': 'Light Turquoise',
183 '#0060BF': 'Summer Sky',
184 '#0080FF': 'Sky Blue',
185 '#40A0FF': 'Electric Blue',
186 '#80C0FF': 'Light Azure',
187 '#BFDFFF': 'Ice Blue',
190 '#57708F': 'Dusty Blue',
191 '#00407F': 'Sea Blue',
192 '#7792AC': 'Sky Blue Gray',
193 '#A8BED1': 'Morning Sky',
195 '#0000BF': 'Deep Blue',
197 '#4040FF': 'Cerulean Blue',
198 '#8080FF': 'Evening Blue',
199 '#BFBFFF': 'Light Blue',
200 '#212143': 'Deep Indigo',
201 '#373E68': 'Sea Blue',
202 '#444F75': 'Night Blue',
203 '#00007F': 'Indigo Blue',
204 '#585E82': 'Dockside',
205 '#8687A4': 'Blue Gray',
206 '#D2D1E1': 'Light Blue Gray',
207 '#6000BF': 'Neon Violet',
208 '#8000FF': 'Blue Violet',
209 '#A040FF': 'Violet Purple',
210 '#C080FF': 'Violet Dusk',
211 '#DFBFFF': 'Pale Lavender',
212 '#302449': 'Cool Shale',
213 '#54466F': 'Dark Indigo',
214 '#655A7F': 'Dark Violet',
216 '#726284': 'Smoky Violet',
217 '#9E8FA9': 'Slate Gray',
218 '#DCD1DF': 'Violet White',
219 '#BF00BF': 'Royal Violet',
220 '#FF00FF': 'Fuchsia',
221 '#FF40FF': 'Magenta',
223 '#FFBFFF': 'Pale Magenta',
224 '#4A234A': 'Dark Purple',
225 '#794A72': 'Medium Purple',
226 '#936386': 'Cool Granite',
228 '#9D7292': 'Purple Moon',
229 '#C0A0B6': 'Pale Purple',
230 '#ECDAE5': 'Pink Cloud',
231 '#BF005F': 'Hot Pink',
232 '#FF007F': 'Deep Pink',
234 '#FF80BF': 'Electric Pink',
236 '#451528': 'Purple Red',
237 '#823857': 'Purple Dino',
238 '#A94A76': 'Purple Gray',
240 '#BC6F95': 'Antique Mauve',
241 '#D8A5BB': 'Cool Marble',
242 '#F7DDE9': 'Pink Granite',
244 '#FF0000': 'Fire Truck',
245 '#FF4040': 'Pale Red',
247 '#FFC0C0': 'Warm Pink',
251 '#800000': 'Brick Red',
253 '#D8A3A4': 'Shrimp Pink',
254 '#F8DDDD': 'Shell Pink',
255 '#BF5F00': 'Dark Orange',
257 '#FF9F40': 'Grapefruit',
258 '#FFBF80': 'Canteloupe',
260 '#482C1B': 'Dark Brick',
264 '#C49B71': 'Mustard',
265 '#E1C4A8': 'Pale Tan',
270 * @property _colorPicker
271 * @description The HTML Element containing the colorPicker
276 * @property STR_COLLAPSE
277 * @description String for Toolbar Collapse Button
280 STR_COLLAPSE
: 'Collapse Toolbar',
282 * @property STR_SPIN_LABEL
283 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
286 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.',
288 * @property STR_SPIN_UP
289 * @description String for spinbutton up
292 STR_SPIN_UP
: 'Click to increase the value of this input',
294 * @property STR_SPIN_DOWN
295 * @description String for spinbutton down
298 STR_SPIN_DOWN
: 'Click to decrease the value of this input',
300 * @property _titlebar
301 * @description Object reference to the titlebar
306 * @property _disabled
307 * @description Object to track button status when enabling/disabling the toolbar
313 * @description Standard browser detection
316 browser
: YAHOO
.env
.ua
,
319 * @property _buttonList
320 * @description Internal property list of current buttons in the toolbar
326 * @property _buttonGroupList
327 * @description Internal property list of current button groups in the toolbar
330 _buttonGroupList
: null,
334 * @description Internal reference to the separator HTML Element for cloning
340 * @property _sepCount
341 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
347 * @property draghandle
353 * @property _toolbarConfigs
361 * @property CLASS_CONTAINER
362 * @description Default CSS class to apply to the toolbar container element
365 CLASS_CONTAINER
: 'yui-toolbar-container',
368 * @property CLASS_DRAGHANDLE
369 * @description Default CSS class to apply to the toolbar's drag handle element
372 CLASS_DRAGHANDLE
: 'yui-toolbar-draghandle',
375 * @property CLASS_SEPARATOR
376 * @description Default CSS class to apply to all separators in the toolbar
379 CLASS_SEPARATOR
: 'yui-toolbar-separator',
382 * @property CLASS_DISABLED
383 * @description Default CSS class to apply when the toolbar is disabled
386 CLASS_DISABLED
: 'yui-toolbar-disabled',
389 * @property CLASS_PREFIX
390 * @description Default prefix for dynamically created class names
393 CLASS_PREFIX
: 'yui-toolbar',
396 * @description The Toolbar class's initialization method
398 init: function(p_oElement
, p_oAttributes
) {
399 YAHOO
.widget
.Toolbar
.superclass
.init
.call(this, p_oElement
, p_oAttributes
);
402 * @method initAttributes
403 * @description Initializes all of the configuration attributes used to create
405 * @param {Object} attr Object literal specifying a set of
406 * configuration attributes used to create the toolbar.
408 initAttributes: function(attr
) {
409 YAHOO
.widget
.Toolbar
.superclass
.initAttributes
.call(this, attr
);
410 var el
= this.get('element');
411 this.addClass(this.CLASS_CONTAINER
);
416 * @description Object specifying the buttons to include in the toolbar
420 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
421 * { type: 'separator' },
422 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
424 * { text: "Left", value: 'alignleft' },
425 * { text: "Center", value: 'aligncenter' },
426 * { text: "Right", value: 'alignright' }
434 this.setAttributeConfig('buttons', {
437 method: function(data
) {
438 for (var i
in data
) {
439 if (Lang
.hasOwnProperty(data
, i
)) {
440 if (data
[i
].type
== 'separator') {
442 } else if (data
[i
].group
!= undefined) {
443 this.addButtonGroup(data
[i
]);
445 this.addButton(data
[i
]);
454 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
458 this.setAttributeConfig('disabled', {
460 method: function(disabled
) {
461 if (!Lang
.isObject(this._disabled
)) {
465 this.addClass(this.CLASS_DISABLED
);
466 this.set('draggable', false);
468 this.removeClass(this.CLASS_DISABLED
);
469 if (this._configs
.draggable
._initialConfig
.value
) {
470 //Draggable by default, set it back
471 this.set('draggable', true);
474 var len
= this._buttonList
.length
;
475 for (var i
= 0; i
< len
; i
++) {
477 //If it's already disabled, flag it
478 if (this._buttonList
[i
].get('disabled')) {
479 this._disabled
[i
] = true;
481 this._disabled
[i
] = null;
483 this.disableButton(this._buttonList
[i
].get('id'));
485 //Check to see if it was disabled by default and skip it
486 var _button
= this._buttonList
[i
];
487 var _check
= _button
._configs
.disabled
._initialConfig
.value
;
488 if (this._disabled
[i
]) {
492 this.enableButton(_button
.get('id'));
500 * @config grouplabels
501 * @description Boolean indicating if the toolbar should show the group label's text string.
505 this.setAttributeConfig('grouplabels', {
512 * @description Boolean indicating if the toolbar should show the group label's text string.
516 this.setAttributeConfig('cont', {
523 * @description Boolean indicating if the the titlebar should have a collapse button.
524 * The collapse button will not remove the toolbar, it will minimize it to the titlebar
528 this.setAttributeConfig('collapse', {
533 * @description Boolean indicating if the toolbar should have a titlebar. If
534 * passed a string, it will use that as the titlebar text
536 * @type Boolean or String
538 this.setAttributeConfig('titlebar', {
540 method: function(titlebar
) {
542 if (this._titlebar
&& this._titlebar
.parentNode
) {
543 this._titlebar
.parentNode
.removeChild(this._titlebar
);
545 this._titlebar
= document
.createElement('DIV');
546 Dom
.addClass(this._titlebar
, this.CLASS_PREFIX
+ '-titlebar');
547 if (Lang
.isString(titlebar
)) {
548 var h2
= document
.createElement('h2');
550 h2
.innerHTML
= titlebar
;
551 this._titlebar
.appendChild(h2
);
553 if (this.get('collapse')) {
554 var collapse
= document
.createElement('SPAN');
555 collapse
.innerHTML
= 'X';
556 collapse
.title
= this.STR_COLLAPSE
;
558 Dom
.addClass(collapse
, 'collapse');
559 this._titlebar
.appendChild(collapse
);
560 Event
.addListener(collapse
, 'click', function() {
561 if (Dom
.getStyle(this.get('cont'), 'display') == 'none') {
562 Dom
.setStyle(this.get('cont'), 'display', 'block');
563 Dom
.removeClass(collapse
, 'collapsed');
564 this.fireEvent('toolbarExpanded', { type
: 'toolbarExpanded', target
: this });
566 Dom
.setStyle(this.get('cont'), 'display', 'none');
567 Dom
.addClass(collapse
, 'collapsed');
568 this.fireEvent('toolbarCollapsed', { type
: 'toolbarCollapsed', target
: this });
572 if (this.get('draggable')) {
573 this.dd
= new YAHOO
.util
.DD(this.get('id'));
574 this.dd
.setHandleElId(this._titlebar
);
575 Dom
.addClass(this._titlebar
, 'draggable');
577 if (this.get('firstChild')) {
578 this.insertBefore(this._titlebar
, this.get('firstChild'));
580 this.appendChild(this._titlebar
);
582 } else if (this._titlebar
) {
583 if (this._titlebar
&& this._titlebar
.parentNode
) {
584 this._titlebar
.parentNode
.removeChild(this._titlebar
);
593 * @description Boolean indicating if the toolbar should be draggable.
598 this.setAttributeConfig('draggable', {
599 value
: (attr
.draggable
|| false),
600 method: function(draggable
) {
601 var el
= this.get('element');
603 if (draggable
&& !this.get('titlebar')) {
604 YAHOO
.log('Dragging enabled', 'info', 'Toolbar');
605 if (!this._dragHandle
) {
606 this._dragHandle
= document
.createElement('SPAN');
607 this._dragHandle
.innerHTML
= '|';
608 this._dragHandle
.setAttribute('title', 'Click to drag the toolbar');
609 this._dragHandle
.id
= this.get('id') + '_draghandle';
610 Dom
.addClass(this._dragHandle
, this.CLASS_DRAGHANDLE
);
611 if (this.get('cont').hasChildNodes()) {
612 this.get('cont').insertBefore(this._dragHandle
, this.get('cont').firstChild
);
614 this.get('cont').appendChild(this._dragHandle
);
618 * @description The DragDrop instance associated with the Toolbar
621 this.dd
= new YAHOO
.util
.DD(this.get('id'));
622 this.dd
.setHandleElId(this._dragHandle
.id
);
626 YAHOO
.log('Dragging disabled', 'info', 'Toolbar');
627 if (this._dragHandle
) {
628 this._dragHandle
.parentNode
.removeChild(this._dragHandle
);
629 this._dragHandle
= null;
633 if (this._titlebar
) {
635 this.dd
= new YAHOO
.util
.DD(this.get('id'));
636 this.dd
.setHandleElId(this._titlebar
);
637 Dom
.addClass(this._titlebar
, 'draggable');
639 Dom
.removeClass(this._titlebar
, 'draggable');
647 validator: function(value
) {
649 if (!YAHOO
.util
.DD
) {
658 * @method addButtonGroup
659 * @description Add a new button group to the toolbar. (uses addButton)
660 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs)
662 addButtonGroup: function(oGroup
) {
663 if (!this.get('element')) {
664 this._queue
[this._queue
.length
] = ['addButtonGroup', arguments
];
668 if (!this.hasClass(this.CLASS_PREFIX
+ '-grouped')) {
669 this.addClass(this.CLASS_PREFIX
+ '-grouped');
671 var div
= document
.createElement('DIV');
672 Dom
.addClass(div
, this.CLASS_PREFIX
+ '-group');
673 Dom
.addClass(div
, this.CLASS_PREFIX
+ '-group-' + oGroup
.group
);
674 if (oGroup
.label
&& this.get('grouplabels')) {
675 var label
= document
.createElement('h3');
676 label
.innerHTML
= oGroup
.label
;
677 div
.appendChild(label
);
680 this.get('cont').appendChild(div
);
682 //For accessibility, let's put all of the group buttons in an Unordered List
683 var ul
= document
.createElement('ul');
686 if (!this._buttonGroupList
) {
687 this._buttonGroupList
= {};
690 this._buttonGroupList
[oGroup
.group
] = ul
;
692 for (var i
= 0; i
< oGroup
.buttons
.length
; i
++) {
693 var li
= document
.createElement('li');
695 if ((oGroup
.buttons
[i
].type
!= undefined) && oGroup
.buttons
[i
].type
== 'separator') {
696 this.addSeparator(li
);
698 oGroup
.buttons
[i
].container
= li
;
699 this.addButton(oGroup
.buttons
[i
]);
704 * @method addButtonToGroup
705 * @description Add a new button to a toolbar group. Buttons supported:
706 * push, split, menu, select, color, spin
707 * @param {Object} oButton Object literal reference to the Button's Config
708 * @param {String} group The Group identifier passed into the initial config
709 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
711 addButtonToGroup: function(oButton
, group
, after
) {
712 var groupCont
= this._buttonGroupList
[group
];
713 var li
= document
.createElement('li');
714 oButton
.container
= li
;
715 this.addButton(oButton
, after
);
716 groupCont
.appendChild(li
);
720 * @description Add a new button to the toolbar. Buttons supported:
721 * push, split, menu, select, color, spin
722 * @param {Object} oButton Object literal reference to the Button's Config
723 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
725 addButton: function(oButton
, after
) {
726 if (!this.get('element')) {
727 this._queue
[this._queue
.length
] = ['addButton', arguments
];
730 if (!this._buttonList
) {
731 this._buttonList
= [];
733 //Add to .get('buttons') manually
734 this._configs
.buttons
.value
[this._configs
.buttons
.value
.length
] = oButton
;
735 YAHOO
.log('Adding button of type: ' + oButton
.type
, 'info', 'Toolbar');
736 if (!oButton
.container
) {
737 oButton
.container
= this.get('cont');
740 if ((oButton
.type
== 'menu') || (oButton
.type
== 'split') || (oButton
.type
== 'select')) {
741 if (Lang
.isArray(oButton
.menu
)) {
742 for (var i
in oButton
.menu
) {
743 if (Lang
.hasOwnProperty(oButton
.menu
, i
)) {
745 fn: function(ev
, x
, oMenu
) {
746 if (!oButton
.menucmd
) {
747 oButton
.menucmd
= oButton
.value
;
749 oButton
.value
= ((oMenu
.value
) ? oMenu
.value
: oMenu
._oText
.nodeValue
);
750 //This line made Opera fire the click event and the mousedown,
751 // so events for menus where firing twice.
752 //this._buttonClick('click', oButton);
756 oButton
.menu
[i
].onclick
= funcObject
;
762 for (var i
in oButton
) {
763 if (Lang
.hasOwnProperty(oButton
, i
)) {
764 if (!this._toolbarConfigs
[i
]) {
765 _oButton
[i
] = oButton
[i
];
769 if (oButton
.type
== 'select') {
770 _oButton
.type
= 'menu';
772 if (oButton
.type
== 'spin') {
773 _oButton
.type
= 'push';
775 if (_oButton
.type
== 'color') {
776 _oButton
= this._makeColorButton(_oButton
);
779 if (oButton
.menu
instanceof YAHOO
.widget
.Overlay
) {
780 oButton
.menu
.showEvent
.subscribe(function() {
781 this._button
= _oButton
;
784 for (var i
= 0; i
< _oButton
.menu
.length
; i
++) {
785 if (!_oButton
.menu
[i
].value
) {
786 _oButton
.menu
[i
].value
= _oButton
.menu
[i
].text
;
789 if (this.browser
.webkit
) {
790 _oButton
.focusmenu
= false;
794 var tmp
= new YAHOO
.widget
.Button(_oButton
);
795 if (this.get('disabled')) {
796 //Toolbar is disabled, disable the new button too!
797 tmp
.set('disabled', true);
800 oButton
.id
= tmp
.get('id');
802 YAHOO
.log('Button created (' + oButton
.type
+ ')', 'info', 'Toolbar');
805 var el
= tmp
.get('element');
808 nextSib
= after
.get('element').nextSibling
;
809 } else if (after
.nextSibling
) {
810 nextSib
= after
.nextSibling
;
813 nextSib
.parentNode
.insertBefore(el
, nextSib
);
816 tmp
.addClass(this.CLASS_PREFIX
+ '-' + tmp
.get('value'));
817 var icon
= document
.createElement('span');
818 icon
.className
= this.CLASS_PREFIX
+ '-icon';
819 tmp
.get('element').insertBefore(icon
, tmp
.get('firstChild'));
820 //Replace the Button HTML Element with an a href
821 var a
= document
.createElement('a');
822 a
.innerHTML
= tmp
._button
.innerHTML
;
824 Event
.on(a
, 'click', function(ev
) {
827 tmp
._button
.parentNode
.replaceChild(a
, tmp
._button
);
830 if (oButton
.type
== 'select') {
831 tmp
.addClass(this.CLASS_PREFIX
+ '-select');
833 if (oButton
.type
== 'spin') {
834 if (!Lang
.isArray(oButton
.range
)) {
835 oButton
.range
= [ 10, 100 ];
837 this._makeSpinButton(tmp
, oButton
);
840 tmp
.get('element').setAttribute('title', tmp
.get('label'));
842 if (oButton
.type
!= 'spin') {
843 if (_oButton
.menu
instanceof YAHOO
.widget
.Overlay
) {
844 var showPicker = function(ev
) {
846 if (ev
.keyCode
&& (ev
.keyCode
== 9)) {
850 this._colorPicker
._button
= oButton
.value
;
851 var menuEL
= tmp
.getMenu().element
;
852 if (menuEL
.style
.visibility
== 'hidden') {
853 tmp
.getMenu().show();
855 tmp
.getMenu().hide();
858 YAHOO
.util
.Event
.stopEvent(ev
);
860 tmp
.on('mousedown', showPicker
, oButton
, this);
861 tmp
.on('keydown', showPicker
, oButton
, this);
863 } else if ((oButton
.type
!= 'menu') && (oButton
.type
!= 'select')) {
864 tmp
.on('keypress', this._buttonClick
, oButton
, this);
865 tmp
.on('mousedown', function(ev
) {
866 this._buttonClick(ev
, oButton
);
867 YAHOO
.util
.Event
.stopEvent(ev
);
870 //Stop the mousedown event so we can trap the selection in the editor!
871 tmp
.on('mousedown', function(ev
) {
872 YAHOO
.util
.Event
.stopEvent(ev
);
874 tmp
.on('click', function(ev
) {
875 YAHOO
.util
.Event
.stopEvent(ev
);
878 //Hijack the mousedown event in the menu and make it fire a button click..
879 tmp
.getMenu().mouseDownEvent
.subscribe(function(ev
, args
) {
880 YAHOO
.log('mouseDownEvent', 'warn', 'Toolbar');
882 YAHOO
.util
.Event
.stopEvent(args
[0]);
883 tmp
._onMenuClick(args
[0], tmp
);
884 if (!oButton
.menucmd
) {
885 oButton
.menucmd
= oButton
.value
;
887 oButton
.value
= ((oMenu
.value
) ? oMenu
.value
: oMenu
._oText
.nodeValue
);
888 self
._buttonClick
.call(self
, args
[1], oButton
);
892 tmp
.getMenu().clickEvent
.subscribe(function(ev
, args
) {
893 YAHOO
.log('clickEvent', 'warn', 'Toolbar');
894 YAHOO
.util
.Event
.stopEvent(args
[0]);
898 //Stop the mousedown event so we can trap the selection in the editor!
899 tmp
.on('mousedown', function(ev
) {
900 YAHOO
.util
.Event
.stopEvent(ev
);
902 tmp
.on('click', function(ev
) {
903 YAHOO
.util
.Event
.stopEvent(ev
);
906 if (this.browser
.ie
) {
907 //Add a couple of new events for IE
908 tmp
.DOM_EVENTS
.focusin
= true;
909 tmp
.DOM_EVENTS
.focusout
= true;
911 //Stop them so we don't loose focus in the Editor
912 tmp
.on('focusin', function(ev
) {
913 YAHOO
.util
.Event
.stopEvent(ev
);
916 tmp
.on('focusout', function(ev
) {
917 YAHOO
.util
.Event
.stopEvent(ev
);
919 tmp
.on('click', function(ev
) {
920 YAHOO
.util
.Event
.stopEvent(ev
);
923 if (this.browser
.webkit
) {
924 //This will keep the document from gaining focus and the editor from loosing it..
925 //Forcefully remove the focus calls in button!
926 tmp
.hasFocus = function() {
930 this._buttonList
[this._buttonList
.length
] = tmp
;
931 if ((oButton
.type
== 'menu') || (oButton
.type
== 'split') || (oButton
.type
== 'select')) {
932 if (Lang
.isArray(oButton
.menu
)) {
933 YAHOO
.log('Button type is (' + oButton
.type
+ '), doing extra renderer work.', 'info', 'Toolbar');
934 var menu
= tmp
.getMenu();
935 menu
.renderEvent
.subscribe(_addMenuClasses
, tmp
);
936 if (oButton
.renderer
) {
937 menu
.renderEvent
.subscribe(oButton
.renderer
, tmp
);
944 * @method addSeparator
945 * @description Add a new button separator to the toolbar.
946 * @param {HTMLElement} cont Optional HTML element to insert this button into.
947 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM.
949 addSeparator: function(cont
, after
) {
950 if (!this.get('element')) {
951 this._queue
[this._queue
.length
] = ['addSeparator', arguments
];
954 var sepCont
= ((cont
) ? cont
: this.get('cont'));
955 if (!this.get('element')) {
956 this._queue
[this._queue
.length
] = ['addSeparator', arguments
];
959 if (this._sepCount
== null) {
963 YAHOO
.log('Separator does not yet exist, creating', 'info', 'Toolbar');
964 this._sep
= document
.createElement('SPAN');
965 Dom
.addClass(this._sep
, this.CLASS_SEPARATOR
);
966 this._sep
.innerHTML
= '|';
968 YAHOO
.log('Separator does exist, cloning', 'info', 'Toolbar');
969 var _sep
= this._sep
.cloneNode(true);
971 Dom
.addClass(_sep
, this.CLASS_SEPARATOR
+ '-' + this._sepCount
);
975 nextSib
= after
.get('element').nextSibling
;
976 } else if (after
.nextSibling
) {
977 nextSib
= after
.nextSibling
;
982 if (nextSib
== after
) {
983 nextSib
.parentNode
.appendChild(_sep
);
985 nextSib
.parentNode
.insertBefore(_sep
, nextSib
);
989 sepCont
.appendChild(_sep
);
994 * @method _createColorPicker
996 * @description Creates the core DOM reference to the color picker menu item.
997 * @param {String} id the id of the toolbar to prefix this DOM container with.
999 _createColorPicker: function(id
) {
1000 if (Dom
.get(id
+ '_colors')) {
1001 Dom
.get(id
+ '_colors').parentNode
.removeChild(Dom
.get(id
+ '_colors'));
1003 var picker
= document
.createElement('div');
1004 picker
.className
= 'yui-toolbar-colors';
1005 picker
.id
= id
+ '_colors';
1006 picker
.style
.display
= 'none';
1007 Event
.on(window
, 'load', function() {
1008 document
.body
.appendChild(picker
);
1011 this._colorPicker
= picker
;
1014 for (var i
in this._colorData
) {
1015 if (Lang
.hasOwnProperty(this._colorData
, i
)) {
1016 html
+= '<a style="background-color: ' + i
+ '" href="#">' + i
.replace('#', '') + '</a>';
1019 html
+= '<span><em>X</em><strong></strong></span>';
1020 picker
.innerHTML
= html
;
1021 var em
= picker
.getElementsByTagName('em')[0];
1022 var strong
= picker
.getElementsByTagName('strong')[0];
1024 Event
.on(picker
, 'mouseover', function(ev
) {
1025 var tar
= Event
.getTarget(ev
);
1026 if (tar
.tagName
.toLowerCase() == 'a') {
1027 em
.style
.backgroundColor
= tar
.style
.backgroundColor
;
1028 strong
.innerHTML
= this._colorData
['#' + tar
.innerHTML
] + '<br>' + tar
.innerHTML
;
1031 Event
.on(picker
, 'focus', function(ev
) {
1032 Event
.stopEvent(ev
);
1034 Event
.on(picker
, 'click', function(ev
) {
1035 Event
.stopEvent(ev
);
1037 Event
.on(picker
, 'mousedown', function(ev
) {
1038 Event
.stopEvent(ev
);
1039 var tar
= Event
.getTarget(ev
);
1040 if (tar
.tagName
.toLowerCase() == 'a') {
1041 this.fireEvent('colorPickerClicked', { type
: 'colorPickerClicked', target
: this, button
: this._colorPicker
._button
, color
: tar
.innerHTML
, colorName
: this._colorData
['#' + tar
.innerHTML
] } );
1042 this.getButtonByValue(this._colorPicker
._button
).getMenu().hide();
1047 * @method _resetColorPicker
1049 * @description Clears the currently selected color or mouseover color in the color picker.
1051 _resetColorPicker: function() {
1052 var em
= this._colorPicker
.getElementsByTagName('em')[0];
1053 var strong
= this._colorPicker
.getElementsByTagName('strong')[0];
1054 em
.style
.backgroundColor
= 'transparent';
1055 strong
.innerHTML
= '';
1058 * @method _makeColorButton
1060 * @description Called to turn a "color" button into a menu button with an Overlay for the menu.
1061 * @param {Object} _oButton <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> reference
1063 _makeColorButton: function(_oButton
) {
1064 if (!this._colorPicker
) {
1065 this._createColorPicker(this.get('id'));
1067 _oButton
.type
= 'color';
1068 _oButton
.menu
= new YAHOO
.widget
.Overlay(this.get('id') + '_' + _oButton
.value
+ '_menu', { visbile
: false, position
: 'absolute' });
1069 _oButton
.menu
.setBody('');
1070 _oButton
.menu
.render(this.get('cont'));
1071 _oButton
.menu
.beforeShowEvent
.subscribe(function() {
1072 _oButton
.menu
.cfg
.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why.
1073 _oButton
.menu
.cfg
.setProperty('context', [this.getButtonById(_oButton
.id
).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why.
1074 //Move the DOM reference of the color picker to the Overlay that we are about to show.
1075 this._resetColorPicker();
1076 var _p
= this._colorPicker
;
1077 if (_p
.parentNode
) {
1078 //if (_p.parentNode != _oButton.menu.body) {
1079 _p
.parentNode
.removeChild(_p
);
1082 _oButton
.menu
.setBody('');
1083 _oButton
.menu
.appendToBody(_p
);
1084 this._colorPicker
.style
.display
= 'block';
1090 * @method _makeSpinButton
1091 * @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.
1092 * @param {Object} _button <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> reference
1093 * @param {Object} oButton Object literal containing the buttons initial config
1095 _makeSpinButton: function(_button
, oButton
) {
1096 _button
.addClass(this.CLASS_PREFIX
+ '-spinbutton');
1098 _par
= _button
._button
.parentNode
.parentNode
, //parentNode of Button Element for appending child
1099 range
= oButton
.range
,
1100 _b1
= document
.createElement('a'),
1101 _b2
= document
.createElement('a');
1105 //Setup the up and down arrows
1106 _b1
.className
= 'up';
1107 _b1
.title
= this.STR_SPIN_UP
;
1108 _b1
.innerHTML
= this.STR_SPIN_UP
;
1109 _b2
.className
= 'down';
1110 _b2
.title
= this.STR_SPIN_DOWN
;
1111 _b2
.innerHTML
= this.STR_SPIN_DOWN
;
1113 //Append them to the container
1114 _par
.appendChild(_b1
);
1115 _par
.appendChild(_b2
);
1117 var label
= YAHOO
.lang
.substitute(this.STR_SPIN_LABEL
, { VALUE
: _button
.get('label') });
1118 _button
.set('title', label
);
1120 var cleanVal = function(value
) {
1121 value
= ((value
< range
[0]) ? range
[0] : value
);
1122 value
= ((value
> range
[1]) ? range
[1] : value
);
1126 var br
= this.browser
;
1128 var strLabel
= this.STR_SPIN_LABEL
;
1129 if (this._titlebar
&& this._titlebar
.firstChild
) {
1130 tbar
= this._titlebar
.firstChild
;
1133 var _intUp = function(ev
) {
1134 YAHOO
.util
.Event
.stopEvent(ev
);
1135 if (!_button
.get('disabled') && (ev
.keyCode
!= 9)) {
1136 var value
= parseInt(_button
.get('label'));
1138 value
= cleanVal(value
);
1139 _button
.set('label', ''+value
);
1140 var label
= YAHOO
.lang
.substitute(strLabel
, { VALUE
: _button
.get('label') });
1141 _button
.set('title', label
);
1142 if (!br
.webkit
&& tbar
) {
1143 //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
1146 self
._buttonClick(ev
, oButton
);
1150 var _intDown = function(ev
) {
1151 YAHOO
.util
.Event
.stopEvent(ev
);
1152 if (!_button
.get('disabled') && (ev
.keyCode
!= 9)) {
1153 var value
= parseInt(_button
.get('label'));
1155 value
= cleanVal(value
);
1157 _button
.set('label', ''+value
);
1158 var label
= YAHOO
.lang
.substitute(strLabel
, { VALUE
: _button
.get('label') });
1159 _button
.set('title', label
);
1160 if (!br
.webkit
&& tbar
) {
1161 //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
1164 self
._buttonClick(ev
, oButton
);
1168 var _intKeyUp = function(ev
) {
1169 if (ev
.keyCode
== 38) {
1171 } else if (ev
.keyCode
== 40) {
1173 } else if (ev
.keyCode
== 107 && ev
.shiftKey
) { //Plus Key
1175 } else if (ev
.keyCode
== 109 && ev
.shiftKey
) { //Minus Key
1180 //Handle arrow keys..
1181 _button
.on('keydown', _intKeyUp
, this, true);
1183 //Listen for the click on the up button and act on it
1184 //Listen for the click on the down button and act on it
1185 Event
.on(_b1
, 'mousedown',function(ev
) {
1186 Event
.stopEvent(ev
);
1188 Event
.on(_b2
, 'mousedown', function(ev
) {
1189 Event
.stopEvent(ev
);
1191 Event
.on(_b1
, 'click', _intUp
, this, true);
1192 Event
.on(_b2
, 'click', _intDown
, this, true);
1196 * @method _buttonClick
1197 * @description Click handler for all buttons in the toolbar.
1198 * @param {String} ev The event that was passed in.
1199 * @param {Object} info Object literal of information about the button that was clicked.
1201 _buttonClick: function(ev
, info
) {
1204 if (ev
&& ev
.type
== 'keypress') {
1205 if (ev
.keyCode
== 9) {
1207 } else if ((ev
.keyCode
== 13) || (ev
.keyCode
== 0) || (ev
.keyCode
== 32)) {
1215 YAHOO
.log('fireEvent::' + info
.value
+ 'Click', 'info', 'Toolbar');
1216 this.fireEvent(info
.value
+ 'Click', { type
: info
.value
+ 'Click', target
: this.get('element'), button
: info
});
1220 YAHOO
.log('fireEvent::' + info
.menucmd
+ 'Click', 'info', 'Toolbar');
1221 this.fireEvent(info
.menucmd
+ 'Click', { type
: info
.menucmd
+ 'Click', target
: this.get('element'), button
: info
});
1224 YAHOO
.log('fireEvent::buttonClick', 'info', 'Toolbar');
1225 this.fireEvent('buttonClick', { type
: 'buttonClick', target
: this.get('element'), button
: info
});
1227 if (info
.type
== 'select') {
1228 var button
= this.getButtonById(info
.id
);
1229 var txt
= info
.value
;
1230 for (var i
= 0; i
< info
.menu
.length
; i
++) {
1231 if (info
.menu
[i
].value
== info
.value
) {
1232 txt
= info
.menu
[i
].text
;
1236 button
.set('label', '<span class="yui-toolbar-' + info
.menucmd
+ '-' + (info
.value
).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>');
1237 var _items = button.getMenu().getItems();
1238 for (var m = 0; m < _items.length; m++) {
1239 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) {
1240 _items[m].cfg.setProperty('checked
', true);
1242 _items[m].cfg.setProperty('checked
', false);
1248 Event.stopEvent(ev);
1252 * @method getButtonById
1253 * @description Gets a button instance from the toolbar by is Dom id.
1254 * @param {String} id The Dom id to query for.
1255 * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
1257 getButtonById: function(id) {
1258 var len = this._buttonList.length;
1259 for (var i = 0; i < len; i++) {
1260 if (this._buttonList[i].get('id
') == id) {
1261 return this._buttonList[i];
1267 * @method getButtonByValue
1268 * @description Gets a button instance or a menuitem instance from the toolbar by it's value
.
1269 * @param
{String
} value The button value to query
for.
1270 * @return {<a href
="YAHOO.widget.Button.html">YAHOO
.widget
.Button
</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>}
1272 getButtonByValue: function(value
) {
1273 var _buttons
= this.get('buttons');
1274 var len
= _buttons
.length
;
1275 for (var i
= 0; i
< len
; i
++) {
1276 if (_buttons
[i
].group
!= undefined) {
1277 for (var m
= 0; m
< _buttons
[i
].buttons
.length
; m
++) {
1278 if ((_buttons
[i
].buttons
[m
].value
== value
) || (_buttons
[i
].buttons
[m
].menucmd
== value
)) {
1279 return this.getButtonById(_buttons
[i
].buttons
[m
].id
);
1281 if (_buttons
[i
].buttons
[m
].menu
) { //Menu Button, loop through the values
1282 for (var s
= 0; s
< _buttons
[i
].buttons
[m
].menu
.length
; s
++) {
1283 if (_buttons
[i
].buttons
[m
].menu
[s
].value
== value
) {
1284 return this.getButtonById(_buttons
[i
].buttons
[m
].id
);
1290 if ((_buttons
[i
].value
== value
) || (_buttons
[i
].menucmd
== value
)) {
1291 return this.getButtonById(_buttons
[i
].id
);
1293 if (_buttons
[i
].menu
) { //Menu Button, loop through the values
1294 for (var s
= 0; s
< _buttons
[i
].menu
.length
; s
++) {
1295 if (_buttons
[i
].menu
[s
].value
== value
) {
1296 return this.getButtonById(_buttons
[i
].id
);
1305 * @method getButtonByIndex
1306 * @description Gets a button instance from the toolbar by is index in _buttonList.
1307 * @param {Number} index The index of the button in _buttonList.
1308 * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
1310 getButtonByIndex: function(index
) {
1311 if (this._buttonList
[index
]) {
1312 return this._buttonList
[index
];
1318 * @method getButtons
1319 * @description Returns an array of buttons in the current toolbar
1322 getButtons: function() {
1323 return this._buttonList
;
1326 * @method disableButton
1327 * @description Disables a button in the toolbar.
1328 * @param {String/Number} button Disable a button by it's id or index.
1331 disableButton: function(button
) {
1332 if (Lang
.isString(button
)) {
1333 var button
= this.getButtonById(button
);
1335 if (Lang
.isNumber(button
)) {
1336 var button
= this.getButtonByIndex(button
);
1338 if (button
instanceof YAHOO
.widget
.Button
) {
1339 button
.set('disabled', true);
1345 * @method enableButton
1346 * @description Enables a button in the toolbar.
1347 * @param {String/Number} button Enable a button by it's id or index.
1350 enableButton: function(button
) {
1351 if (Lang
.isString(button
)) {
1352 var button
= this.getButtonById(button
);
1354 if (Lang
.isNumber(button
)) {
1355 var button
= this.getButtonByIndex(button
);
1357 if (button
instanceof YAHOO
.widget
.Button
) {
1358 button
.set('disabled', false);
1364 * @method selectButton
1365 * @description Selects a button in the toolbar.
1366 * @param {String/Number} button select a button by it's id or index.
1369 selectButton: function(button
, value
) {
1371 if (Lang
.isString(button
)) {
1372 var button
= this.getButtonById(button
);
1374 if (Lang
.isNumber(button
)) {
1375 var button
= this.getButtonByIndex(button
);
1377 if (button
instanceof YAHOO
.widget
.Button
) {
1378 button
.addClass('yui-button-selected');
1379 button
.addClass('yui-button-' + button
.get('value') + '-selected');
1381 var _items
= button
.getMenu().getItems();
1382 for (var m
= 0; m
< _items
.length
; m
++) {
1383 if (_items
[m
].value
== value
) {
1384 _items
[m
].cfg
.setProperty('checked', true);
1385 button
.set('label', '<span class="yui-toolbar-' + button
.get('value') + '-' + (value
).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>');
1387 _items[m].cfg.setProperty('checked
', false);
1397 * @method deselectButton
1398 * @description Deselects a button in the toolbar.
1399 * @param {String/Number} button Deselect a button by it's id or index
.
1402 deselectButton: function(button
) {
1403 if (Lang
.isString(button
)) {
1404 var button
= this.getButtonById(button
);
1406 if (Lang
.isNumber(button
)) {
1407 var button
= this.getButtonByIndex(button
);
1409 if (button
instanceof YAHOO
.widget
.Button
) {
1410 button
.removeClass('yui-button-selected');
1411 button
.removeClass('yui-button-' + button
.get('value') + '-selected');
1412 button
.removeClass('yui-button-hover');
1418 * @method deselectAllButtons
1419 * @description Deselects all buttons in the toolbar.
1422 deselectAllButtons: function() {
1423 var len
= this._buttonList
.length
;
1424 for (var i
= 0; i
< len
; i
++) {
1425 this.deselectButton(this._buttonList
[i
]);
1429 * @method destroyButton
1430 * @description Destroy a button in the toolbar.
1431 * @param {String/Number} button Destroy a button by it's id or index.
1434 destroyButton: function(button
) {
1435 if (Lang
.isString(button
)) {
1436 var button
= this.getButtonById(button
);
1438 if (Lang
.isNumber(button
)) {
1439 var button
= this.getButtonByIndex(button
);
1441 if (button
instanceof YAHOO
.widget
.Button
) {
1442 var id
= button
.get('id');
1445 var len
= this._buttonList
.length
;
1446 for (var i
= 0; i
< len
; i
++) {
1447 if (this._buttonList
[i
].get('id') == id
) {
1448 this._buttonList
[i
] = null;
1458 * @description Destroys the toolbar, all of it's elements and objects.
1461 destroy: function() {
1462 this.get('element').innerHTML
= '';
1463 this.get('element').className
= '';
1464 //Brutal Object Destroy
1465 for (var i
in this) {
1466 if (Lang
.hasOwnProperty(this, i
)) {
1474 * @description Returns a string representing the toolbar.
1477 toString: function() {
1478 return 'Toolbar (#' + this.get('element').id
+ ') with ' + this._buttonList
.length
+ ' buttons.';
1482 * @event buttonClick
1483 * @param {Object} o The object passed to this handler is the button config used to create the button.
1484 * @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.
1485 * @type YAHOO.util.CustomEvent
1489 * @param {Object} o The object passed to this handler is the button config used to create the button.
1490 * @description This is a special dynamic event that is created and dispatched based on the value property
1491 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
1495 * { type: 'button', value: 'test', value: 'testButton' }
1498 * With the valueClick event you could subscribe to this buttons click event with this:
1499 * tbar.in('testButtonClick', function() { alert('test button clicked'); })
1500 * @type YAHOO.util.CustomEvent
1503 * @event toolbarExpanded
1504 * @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.
1505 * @type YAHOO.util.CustomEvent
1508 * @event toolbarCollapsed
1509 * @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.
1510 * @type YAHOO.util.CustomEvent
1514 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
1515 Code licensed under the BSD License:
1516 http://developer.yahoo.net/yui/license.txt
1520 * @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>
1521 * @namespace YAHOO.widget
1522 * @requires yahoo, dom, element, event, toolbar, container, menu, button
1523 * @optional dragdrop, animation
1528 var Dom
= YAHOO
.util
.Dom
,
1529 Event
= YAHOO
.util
.Event
,
1532 Toolbar
= YAHOO
.widget
.Toolbar
;
1536 * 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.
1539 * @extends YAHOO.util.Element
1540 * @param {String/HTMLElement} el The textarea element to turn into an editor.
1541 * @param {Object} attrs Object liternal containing configuration parameters.
1544 YAHOO
.widget
.Editor = function(el
, attrs
) {
1545 YAHOO
.log('Editor Initalizing', 'info', 'Editor');
1549 attributes
: (attrs
|| {})
1552 if (Lang
.isString(el
)) {
1553 oConfig
.attributes
.textarea
= Dom
.get(el
);
1556 var element_cont
= document
.createElement('DIV');
1557 oConfig
.attributes
.element_cont
= new YAHOO
.util
.Element(element_cont
, {
1558 id
: oConfig
.attributes
.textarea
.id
+ '_container'
1560 oConfig
.attributes
.element_cont
.setStyle('display', 'none');
1562 oConfig
.element
= oConfig
.attributes
.textarea
;
1564 var div
= document
.createElement('div');
1565 oConfig
.attributes
.element_cont
.appendChild(div
);
1567 if (!oConfig
.attributes
.toolbar_cont
) {
1568 oConfig
.attributes
.toolbar_cont
= document
.createElement('DIV');
1569 oConfig
.attributes
.toolbar_cont
.id
= oConfig
.attributes
.textarea
.id
+ '_toolbar';
1570 div
.appendChild(oConfig
.attributes
.toolbar_cont
);
1573 if (!oConfig
.attributes
.iframe
) {
1574 oConfig
.attributes
.iframe
= _createIframe(oConfig
.attributes
.textarea
.id
);
1575 var editorWrapper
= document
.createElement('DIV');
1576 editorWrapper
.appendChild(oConfig
.attributes
.iframe
.get('element'));
1577 div
.appendChild(editorWrapper
);
1580 Event
.onDOMReady(function() {
1581 this.DOMReady
= true;
1585 YAHOO
.widget
.Editor
.superclass
.constructor.call(this, oConfig
.element
, oConfig
.attributes
);
1589 * @private _cleanClassName
1590 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
1591 * @param {String} str The classname to clean up
1594 function _cleanClassName(str
) {
1595 return str
.replace(/ /g
, '-').toLowerCase();
1599 * @private _createIframe
1600 * @description Creates the DOM and YUI Element for the iFrame editor area.
1601 * @param {String} id The string ID to prefix the iframe with
1602 * @returns {Object} iFrame object
1604 function _createIframe(id
) {
1605 var ifrmID
= id
+ '_editor';
1606 var ifrmDom
= document
.createElement('iframe');
1607 ifrmDom
.id
= ifrmID
;
1615 allowTransparency
: 'true',
1617 src
: 'javascript:false'
1619 for (var i
in config
) {
1620 if (Lang
.hasOwnProperty(config
, i
)) {
1621 ifrmDom
.setAttribute(i
, config
[i
]);
1625 var ifrm
= new YAHOO
.util
.Element(ifrmDom
);
1626 ifrm
.setStyle('zIndex', '-1');
1630 YAHOO
.extend(YAHOO
.widget
.Editor
, YAHOO
.util
.Element
, {
1632 * @property DOMReady
1634 * @description Flag to determine if DOM is ready or not
1639 * @property _selection
1641 * @description Holder for caching iframe selections
1648 * @description DOM Element holder for the editor Mask when disabled
1653 * @property _showingHiddenElements
1655 * @description Status of the hidden elements button
1658 _showingHiddenElements
: null,
1660 * @property currentWindow
1661 * @description A reference to the currently open EditorWindow
1664 currentWindow
: null,
1666 * @property currentEvent
1667 * @description A reference to the current editor event
1672 * @property operaEvent
1674 * @description setTimeout holder for Opera and Image DoubleClick event..
1679 * @property currentFont
1680 * @description A reference to the last font selected from the Toolbar
1685 * @property currentElement
1686 * @description A reference to the current working element in the editor
1692 * @description A reference to the dompath container for writing the current working dom path to.
1697 * @property beforeElement
1698 * @description A reference to the H2 placed before the editor for Accessibilty.
1701 beforeElement
: null,
1703 * @property afterElement
1704 * @description A reference to the H2 placed after the editor for Accessibilty.
1709 * @property invalidHTML
1710 * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found.
1727 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
1728 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
1733 * @property _contentTimer
1734 * @description setTimeout holder for documentReady check
1736 _contentTimer
: null,
1739 * @property _contentTimerCounter
1740 * @description Counter to check the number of times the body is polled for before giving up
1743 _contentTimerCounter
: 0,
1746 * @property _disabled
1747 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
1750 _disabled
: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent', 'outdent' ],
1753 * @property _alwaysDisabled
1754 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
1757 _alwaysDisabled
: { },
1760 * @property _alwaysEnabled
1761 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
1764 _alwaysEnabled
: { hiddenelements
: true },
1767 * @property _semantic
1768 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
1771 _semantic
: { 'bold': true, 'italic' : true, 'underline' : true },
1774 * @property _tag2cmd
1775 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
1784 'blockquote': 'formatblock',
1785 'sup': 'superscript',
1787 'img': 'insertimage',
1789 'ul' : 'insertunorderedlist',
1790 'ol' : 'insertorderedlist',
1791 'indent' : 'indent',
1792 'outdent' : 'outdent'
1797 * @description Get the Document of the IFRAME
1800 _getDoc: function() {
1801 //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) {
1804 if (this.get('iframe')) {
1805 if (this.get('iframe').get) {
1806 if (this.get('iframe').get('element')) {
1808 if (this.get('iframe').get('element').contentWindow
) {
1809 if (this.get('iframe').get('element').contentWindow
.document
) {
1810 value
= this.get('iframe').get('element').contentWindow
.document
;
1822 * @method _getWindow
1823 * @description Get the Window of the IFRAME
1826 _getWindow: function() {
1827 return this.get('iframe').get('element').contentWindow
;
1831 * @method _focusWindow
1832 * @description Attempt to set the focus of the iframes window.
1833 * @param {Boolean} onLoad Safari needs some special care to set the cursor in the iframe
1835 _focusWindow: function(onLoad
) {
1836 if (this.browser
.webkit
) {
1839 * @knownissue Safari Cursor Position
1840 * @browser Safari 2.x
1841 * @description Can't get Safari to place the cursor at the beginning of the text..
1842 * This workaround at least set's the toolbar into the proper state.
1844 this._getSelection().setBaseAndExtent(this._getDoc().body
, 0, this._getDoc().body
, 1);
1845 this._getSelection().collapse(false);
1847 this._getSelection().setBaseAndExtent(this._getDoc().body
, 1, this._getDoc().body
, 1);
1848 this._getSelection().collapse(false);
1850 this._getWindow().focus();
1852 if (this._getDoc().queryCommandEnabled('insertimage')) {
1853 this.browser
.webkit3
= true;
1856 this._getWindow().focus();
1861 * @method _hasSelection
1862 * @description Determines if there is a selection in the editor document.
1863 * @returns {Boolean}
1865 _hasSelection: function() {
1866 var sel
= this._getSelection();
1867 var range
= this._getRange();
1871 if (this.browser
.ie
|| this.browser
.opera
) {
1879 if ((sel
!= '') && (sel
!= undefined)) {
1887 * @method _getSelection
1888 * @description Handles the different selection objects across the A-Grade list.
1889 * @returns {Object} Selection Object
1891 _getSelection: function() {
1893 if (this._getDoc() && this._getWindow()) {
1894 if (this._getDoc().selection
) {
1895 _sel
= this._getDoc().selection
;
1897 _sel
= this._getWindow().getSelection();
1899 //Handle Safari's lack of Selection Object
1900 if (this.browser
.webkit
) {
1901 if (_sel
.baseNode
) {
1902 this._selection
= new Object();
1903 this._selection
.baseNode
= _sel
.baseNode
;
1904 this._selection
.baseOffset
= _sel
.baseOffset
;
1905 this._selection
.extentNode
= _sel
.extentNode
;
1906 this._selection
.extentOffset
= _sel
.extentOffset
;
1907 } else if (this._selection
!= null) {
1908 _sel
= this._getWindow().getSelection();
1909 _sel
.setBaseAndExtent(
1910 this._selection
.baseNode
,
1911 this._selection
.baseOffset
,
1912 this._selection
.extentNode
,
1913 this._selection
.extentOffset
1915 this._selection
= null;
1924 * @description Handles the different range objects across the A-Grade list.
1925 * @returns {Object} Range Object
1927 _getRange: function(sel
) {
1928 var sel
= this._getSelection();
1934 if (this.browser
.webkit
&& !sel
.getRangeAt
) {
1935 var _range
= this._getDoc().createRange();
1937 _range
.setStart(sel
.anchorNode
, sel
.anchorOffset
);
1938 _range
.setEnd(sel
.focusNode
, sel
.focusOffset
);
1940 _range
= this._getWindow().getSelection()+'';
1945 if (this.browser
.ie
|| this.browser
.opera
) {
1946 return sel
.createRange();
1949 if (sel
.rangeCount
> 0) {
1950 return sel
.getRangeAt(0);
1956 * @method _setDesignMode
1957 * @description Sets the designMode of the iFrame document.
1958 * @param {String} state This should be either on or off
1960 _setDesignMode: function(state
) {
1962 this._getDoc().designMode
= state
;
1967 * @method _toggleDesignMode
1968 * @description Toggles the designMode of the iFrame document on and off.
1969 * @returns {String} The state that it was set to.
1971 _toggleDesignMode: function() {
1972 var _dMode
= this._getDoc().designMode
,
1974 if (_dMode
== 'on') {
1977 this._setDesignMode(_state
);
1982 * @method _initEditor
1983 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
1985 _initEditor: function() {
1986 YAHOO
.log('editorLoaded', 'info', 'Editor');
1987 if (this.browser
.ie
) {
1988 this._getDoc().body
.style
.margin
= '0';
1990 this._setDesignMode('on');
1992 this.toolbar
.on('buttonClick', this._handleToolbarClick
, this, true);
1993 //Setup Listeners on iFrame
1994 Event
.addListener(this._getDoc(), 'mouseup', this._handleMouseUp
, this, true);
1995 Event
.addListener(this._getDoc(), 'mousedown', this._handleMouseDown
, this, true);
1996 Event
.addListener(this._getDoc(), 'click', this._handleClick
, this, true);
1997 Event
.addListener(this._getDoc(), 'dblclick', this._handleDoubleClick
, this, true);
1998 Event
.addListener(this._getDoc(), 'keypress', this._handleKeyPress
, this, true);
1999 Event
.addListener(this._getDoc(), 'keyup', this._handleKeyUp
, this, true);
2000 Event
.addListener(this._getDoc(), 'keydown', this._handleKeyDown
, this, true);
2001 this.toolbar
.set('disabled', false);
2002 this.fireEvent('editorContentLoaded', { type
: 'editorLoaded', target
: this });
2003 if (this.get('dompath')) {
2005 window
.setTimeout(function() {
2006 self
._writeDomPath
.call(self
);
2012 * @method _checkLoaded
2013 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
2015 _checkLoaded: function() {
2016 this._contentTimerCounter
++;
2017 if (this._contentTimer
) {
2018 window
.clearTimeout(this._contentTimer
);
2020 if (this._contentTimerCounter
> 250) {
2021 alert('ERROR: Body Did Not load');
2024 if (this._getDoc() && this._getDoc().body
&& (this._getDoc().body
._rteLoaded
== true)) {
2025 //The onload event has fired, clean up after ourselves and fire the _initEditor method
2027 if (!this.browser.ie) {
2028 //IE Doesn't like this..
2029 delete this._getDoc().body._rteLoaded;
2030 this._getDoc().body.removeAttribute('onload');
2036 this._contentTimer
= window
.setTimeout(function() {
2037 self
._checkLoaded
.call(self
);
2043 * @method _setInitialContent
2044 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
2046 _setInitialContent: function() {
2047 YAHOO
.log('Body of editor populated with contents of the text area', 'info', 'Editor');
2048 var title
= this.STR_TITLE
;
2049 var html
= this.get('html');
2050 html
= html
.replace('{TITLE}', title
);
2051 html
= html
.replace('{CONTENT}', this.get('textarea').value
);
2052 html
= html
.replace('{CSS}', this.get('css'));
2053 html
= html
.replace('{HIDDEN_CSS}', this.get('hiddencss'));
2055 this._getDoc().open();
2056 this._getDoc().write(html
);
2057 this._getDoc().close();
2059 this._checkLoaded();
2063 * @method _setMarkupType
2064 * @param {String} action The action to take. Possible values are: css, default or semantic
2065 * @description This method will turn on/off the useCSS execCommand.
2067 _setMarkupType: function(action
) {
2068 switch (this.get('markup')) {
2070 this._setEditorStyle(true);
2073 this._setEditorStyle(false);
2076 if (this._semantic
[action
]) {
2077 this._setEditorStyle(false);
2079 this._setEditorStyle(true);
2085 * Set the editor to use CSS instead of HTML
2086 * @param {Booleen} stat True/False
2088 _setEditorStyle: function(stat
) {
2090 this._getDoc().execCommand('useCSS', false, !stat
);
2096 * @method _getSelectedElement
2097 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
2098 * @returns {HTMLElement} The currently selected element.
2100 _getSelectedElement: function() {
2101 var doc
= this._getDoc();
2102 if (this.browser
.ie
) {
2103 var range
= this._getRange(), elm
= null;
2105 elm
= range
.item
? range
.item(0) : range
.parentElement();
2106 if (elm
== doc
.body
) {
2111 var sel
= this._getSelection(),
2112 range
= this._getRange(),
2114 if (!sel
|| !range
) {
2118 if (sel
.anchorNode
&& (sel
.anchorNode
.nodeType
== 3)) {
2119 if (sel
.anchorNode
.parentNode
) { //next check parentNode
2120 elm
= sel
.anchorNode
.parentNode
;
2122 if (sel
.anchorNode
.nextSibling
!= sel
.focusNode
.nextSibling
) {
2123 elm
= sel
.anchorNode
.nextSibling
;
2127 if (elm
&& elm
.tagName
&& (elm
.tagName
.toLowerCase() == 'br')) {
2132 elm
= range
.commonAncestorContainer
;
2133 if (!range
.collapsed
) {
2134 if (range
.startContainer
== range
.endContainer
) {
2135 if (range
.startOffset
- range
.endOffset
< 2) {
2136 if (range
.startContainer
.hasChildNodes()) {
2137 elm
= range
.startContainer
.childNodes
[range
.startOffset
];
2145 if (this.currentEvent
) {
2146 //elm = Event.getTarget(this.currentEvent);
2151 if (!elm
&& (this.currentElement
[0] || this.currentEvent
)) {
2152 if (this.currentEvent
&& (this.currentEvent
.keyCode
== undefined) && Event
.getTarget(this.currentEvent
)) {
2153 elm
= Event
.getTarget(this.currentEvent
);
2154 } else if (this.currentEvent
&& (this.currentEvent
.keyCode
!= undefined) && Event
.getTarget(this.currentEvent
)) {
2156 elm
= this.currentElement
[0];
2158 } else if ((elm
== this._getDoc().body
) && this.currentElement
[0] && !this._hasSelection()) {
2159 elm
= this.currentElement
[0];
2162 if (this.browser
.opera
|| this.browser
.webkit
) {
2163 if (this.currentEvent
&& !elm
) {
2164 elm
= Event
.getTarget(this.currentEvent
);
2168 if (!elm
|| !elm
.tagName
) {
2171 if (elm
&& elm
.tagName
&& elm
.tagName
.toLowerCase() == 'html') {
2172 //Safari sometimes gives us the HTML node back..
2180 * @method _getDomPath
2181 * @description This method will attempt to build the DOM path from the currently selected element.
2182 * @returns {Array} An array of node references that will create the DOM Path.
2184 _getDomPath: function() {
2185 var el
= this._getSelectedElement();
2189 if (el
.ownerDocument
!= this._getDoc()) {
2192 //Check to see if we get el.nodeName and nodeType
2193 if (el
.nodeName
&& (el
.nodeType
== 1)) {
2194 domPath
[domPath
.length
] = el
;
2197 if (el
.nodeName
.toUpperCase() == "BODY") {
2203 if (domPath
.length
== 0) {
2204 if (this._getDoc() && this._getDoc().body
) {
2205 domPath
[0] = this._getDoc().body
;
2208 return domPath
.reverse();
2212 * @method _writeDomPath
2213 * @description Write the current DOM path out to the dompath container below the editor.
2215 _writeDomPath: function() {
2216 var path
= this._getDomPath(),
2218 for (var i
= 0; i
< path
.length
; i
++) {
2219 var tag
= path
[i
].tagName
.toLowerCase();
2220 if ((tag
== 'ol') && (path
[i
].type
)) {
2221 tag
+= ':' + path
[i
].type
;
2223 if (Dom
.hasClass(path
[i
], 'yui-tag')) {
2224 tag
= path
[i
].getAttribute('tag');
2226 if ((this.get('markup') == 'semantic')) {
2228 case 'b': tag
= 'strong'; break;
2229 case 'i': tag
= 'em'; break;
2232 if (!Dom
.hasClass(path
[i
], 'yui-non')) {
2233 if (Dom
.hasClass(path
[i
], 'yui-tag')) {
2236 if (path
[i
].getAttribute('href')) {
2237 pathStr
+= ':' + path
[i
].getAttribute('href').replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp
2241 var classPath
= ((path
[i
].className
!= '') ? '.' + path
[i
].className
.replace(/ /g
, '.') : '');
2242 if ((classPath
.indexOf('yui') != -1) || (classPath
.toLowerCase().indexOf('apple-style-span') != -1)) {
2245 var pathStr
= tag
+ ((path
[i
].id
) ? '#' + path
[i
].id
: '') + classPath
;
2247 if (pathStr
.length
> 10) {
2248 pathStr
= pathStr
.substring(0, 10) + '...';
2250 pathArr
[pathArr
.length
] = pathStr
;
2253 var str
= pathArr
.join(' ' + this.SEP_DOMPATH
+ ' ');
2254 //Prevent flickering
2255 if (this.dompath
.innerHTML
!= str
) {
2256 this.dompath
.innerHTML
= str
;
2262 * @description Fix href and imgs as well as remove invalid HTML.
2264 _fixNodes: function() {
2265 for (var i
in this.invalidHTML
) {
2266 if (Lang
.hasOwnProperty(this.invalidHTML
, i
)) {
2267 var tags
= this._getDoc().body
.getElementsByTagName(i
);
2268 for (var h
= 0; h
< tags
.length
; h
++) {
2269 if (tags
[h
].parentNode
) {
2270 tags
[h
].parentNode
.removeChild(tags
[h
]);
2275 var as
= this._getDoc().body
.getElementsByTagName('a');
2277 YAHOO
.log('Found an A tag in the document, converting to span holder', 'info', 'Editor');
2278 for (var i
= 0; i
< as
.length
; i
++) {
2279 var el
= this._getDoc().createElement('span');
2280 Dom
.addClass(el
, 'yui-tag-a');
2281 Dom
.addClass(el
, 'yui-tag');
2282 el
.innerHTML
= as
[i
].innerHTML
;
2283 el
.setAttribute('tag', 'a');
2284 el
.setAttribute('href', as
[i
].getAttribute('href'));
2285 if (as
[i
].getAttribute('target') != null) {
2286 el
.setAttribute('target', as
[i
].getAttribute('target'));
2288 as
[i
].parentNode
.replaceChild(el
, as
[i
]);
2292 var imgs
= this._getDoc().getElementsByTagName('img');
2293 Dom
.addClass(imgs
, 'yui-img');
2295 for (var i
= 0; i
< imgs
.length
; i
++) {
2296 if (imgs
[i
].getAttribute('href', 2)) {
2297 var url
= imgs
[i
].getAttribute('src', 2);
2298 if ((url
!= '') && ((url
.indexOf('file:/') != -1) || (url
.indexOf(':\\') != -1))) {
2299 Dom
.addClass(imgs
[i
], this.CLASS_LOCAL_FILE
);
2301 Dom
.removeClass(imgs
[i
], this.CLASS_LOCAL_FILE
);
2306 var fakeAs
= this._getDoc().body
.getElementsByTagName('span');
2307 for (var i
= 0; i
< fakeAs
.length
; i
++) {
2308 if (fakeAs
[i
].getAttribute('href', 2)) {
2309 var url
= fakeAs
[i
].getAttribute('href', 2);
2310 if ((url
!= '') && ((url
.indexOf('file:/') != -1) || (url
.indexOf(':\\') != -1))) {
2311 Dom
.addClass(fakeAs
[i
], this.CLASS_LOCAL_FILE
);
2313 Dom
.removeClass(fakeAs
[i
], this.CLASS_LOCAL_FILE
);
2320 * @method _showHidden
2321 * @description Toggle on/off the hidden.css file.
2323 _showHidden: function() {
2324 if (this._showingHiddenElements
) {
2325 YAHOO
.log('Enabling hidden CSS File', 'info', 'Editor');
2326 this._showingHiddenElements
= false;
2327 this.toolbar
.deselectButton(this.toolbar
.getButtonByValue('hiddenelements'));
2328 Dom
.removeClass(this._getDoc().body
, this.CLASS_HIDDEN
);
2330 YAHOO
.log('Disabling hidden CSS File', 'info', 'Editor');
2331 this._showingHiddenElements
= true;
2332 Dom
.addClass(this._getDoc().body
, this.CLASS_HIDDEN
);
2333 this.toolbar
.selectButton(this.toolbar
.getButtonByValue('hiddenelements'));
2338 * @method _setCurrentEvent
2339 * @param {Event} ev The event to cache
2340 * @description Sets the current event property
2342 _setCurrentEvent: function(ev
) {
2343 if (ev
&& ev
.type
) {
2344 YAHOO
.log('Event: ' + ev
.type
, 'info', 'Editor');
2346 this.currentEvent
= ev
;
2350 * @method _handleClick
2351 * @param {Event} ev The event we are working on.
2352 * @description Handles all click events inside the iFrame document.
2354 _handleClick: function(ev
) {
2355 this._setCurrentEvent(ev
);
2356 if (this.currentWindow
) {
2359 if (!this.browser
.webkit
) {
2365 * @method _handleMouseUp
2366 * @param {Event} ev The event we are working on.
2367 * @description Handles all mouseup events inside the iFrame document.
2369 _handleMouseUp: function(ev
) {
2370 this._setCurrentEvent(ev
);
2371 if (this.browser
.opera
) {
2373 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on..
2375 * @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.
2377 var sel
= Event
.getTarget(ev
);
2378 if (sel
&& sel
.tagName
&& (sel
.tagName
.toLowerCase() == 'img')) {
2381 if (this.operaEvent
) {
2382 clearTimeout(this.operaEvent
);
2383 this.operaEvent
= null;
2384 this._handleDoubleClick(ev
);
2386 this.operaEvent
= window
.setTimeout(function() {
2387 self
.operaEvent
= false;
2392 //This will stop Safari from selecting the entire document if you select all the text in the editor
2393 if (this.browser
.webkit
|| this.browser
.opera
) {
2394 if (this.browser
.webkit
) {
2395 Event
.stopEvent(ev
);
2399 this.fireEvent('editorMouseUp', { type
: 'editorMouseUp', target
: this, ev
: ev
});
2403 * @method _handleMouseDown
2404 * @param {Event} ev The event we are working on.
2405 * @description Handles all mousedown events inside the iFrame document.
2407 _handleMouseDown: function(ev
) {
2408 this._setCurrentEvent(ev
);
2409 var sel
= Event
.getTarget(ev
);
2410 if (sel
&& sel
.tagName
&& (sel
.tagName
.toLowerCase() == 'img')) {
2411 if (this.browser
.webkit
) {
2413 Event
.stopEvent(ev
);
2416 //this.nodeChange();
2417 this.fireEvent('editorMouseDown', { type
: 'editorMouseDown', target
: this, ev
: ev
});
2421 * @method _handleDoubleClick
2422 * @param {Event} ev The event we are working on.
2423 * @description Handles all doubleclick events inside the iFrame document.
2425 _handleDoubleClick: function(ev
) {
2426 this._setCurrentEvent(ev
);
2427 var sel
= Event
.getTarget(ev
);
2428 if (sel
&& sel
.tagName
&& (sel
.tagName
.toLowerCase() == 'img')) {
2429 this.currentElement
[0] = sel
;
2430 this.toolbar
.fireEvent('insertimageClick', { type
: 'insertimageClick', target
: this.toolbar
});
2431 this.fireEvent('afterExecCommand', { type
: 'afterExecCommand', target
: this });
2432 } else if (sel
&& sel
.getAttribute
&& sel
.getAttribute('tag') && (sel
.getAttribute('tag').toLowerCase() == 'a')) {
2433 this.currentElement
[0] = sel
;
2434 this.toolbar
.fireEvent('createlinkClick', { type
: 'createlinkClick', target
: this.toolbar
});
2435 this.fireEvent('afterExecCommand', { type
: 'afterExecCommand', target
: this });
2438 this.fireEvent('editorDoubleClick', { type
: 'editorDoubleClick', target
: this, ev
: ev
});
2442 * @method _handleKeyUp
2443 * @param {Event} ev The event we are working on.
2444 * @description Handles all keyup events inside the iFrame document.
2446 _handleKeyUp: function(ev
) {
2447 this._setCurrentEvent(ev
);
2448 switch (ev
.keyCode
) {
2449 case 37: //Left Arrow
2451 case 39: //Right Arrow
2452 case 40: //Down Arrow
2453 case 46: //Forward Delete
2455 case 65: //The letter a (for ctrl + a and cmd + a)
2456 case 27: //Escape key if window is open
2457 if ((ev
.keyCode
== 27) && this.currentWindow
) {
2463 this.fireEvent('editorKeyUp', { type
: 'editorKeyUp', target
: this, ev
: ev
});
2467 * @method _handleKeyPress
2468 * @param {Event} ev The event we are working on.
2469 * @description Handles all keypress events inside the iFrame document.
2471 _handleKeyPress: function(ev
) {
2472 this._setCurrentEvent(ev
);
2473 this.fireEvent('editorKeyPress', { type
: 'editorKeyPress', target
: this, ev
: ev
});
2477 * @method _handleKeyDown
2478 * @param {Event} ev The event we are working on.
2479 * @description Handles all keydown events inside the iFrame document.
2481 _handleKeyDown: function(ev
) {
2482 this._setCurrentEvent(ev
);
2483 if (this.currentWindow
) {
2489 if (ev
.shiftKey
&& ev
.ctrlKey
) {
2492 switch (ev
.keyCode
) {
2493 case 84: //Focus Toolbar Header -- Ctrl + Shift + T
2494 if (ev
.shiftKey
&& ev
.ctrlKey
) {
2495 this.toolbar
._titlebar
.firstChild
.focus();
2496 Event
.stopEvent(ev
);
2500 case 27: //Focus After Element - Ctrl + Shift + Esc
2502 this.afterElement
.focus();
2503 Event
.stopEvent(ev
);
2508 action
= 'justifyleft';
2511 action
= 'justifycenter';
2514 action
= 'justifyright';
2517 if (this._hasSelection()) {
2518 this.execCommand('createlink', '');
2519 this.toolbar
.fireEvent('createlinkClick', { type
: 'createlinkClick', target
: this.toolbar
});
2520 this.fireEvent('afterExecCommand', { type
: 'afterExecCommand', target
: this });
2531 action
= 'underline';
2534 if (this.browser
.safari
) {
2535 this._getDoc().execCommand('inserttext', false, '\t');
2536 Event
.stopEvent(ev
);
2540 if (this.browser
.ie
) {
2541 //Insert a <br> instead of a <p></p> in Internet Explorer
2542 var _range
= this._getRange();
2543 var tar
= this._getSelectedElement();
2544 if (tar
&& tar
.tagName
&& (tar
.tagName
.toLowerCase() != 'li')) {
2546 _range
.pasteHTML('<br>');
2547 _range
.collapse(false);
2550 Event
.stopEvent(ev
);
2554 if (doExec
&& action
) {
2555 this.execCommand(action
, null);
2556 Event
.stopEvent(ev
);
2559 this.fireEvent('editorKeyDown', { type
: 'editorKeyDown', target
: this, ev
: ev
});
2562 * @method nodeChange
2563 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes.
2565 nodeChange: function() {
2568 this.fireEvent('beforeNodeChange', { type
: 'beforeNodeChange', target
: this });
2569 if (this.get('dompath')) {
2570 this._writeDomPath();
2572 //Check to see if we are disabled before continuing
2573 if (!this.get('disabled')) {
2574 if (this.STOP_NODE_CHANGE
) {
2575 //Reset this var for next action
2576 this.STOP_NODE_CHANGE
= false;
2579 var sel
= this._getSelection();
2580 var range
= this._getRange();
2582 //Handle disabled buttons
2583 for (var i
= 0; i
< this._disabled
.length
; i
++) {
2584 var _button
= this.toolbar
.getButtonByValue(this._disabled
[i
]);
2585 if (_button
&& _button
.get) {
2586 if (!this._hasSelection()) {
2587 //No Selection - disable
2588 this.toolbar
.disableButton(_button
.get('id'));
2590 if (!this._alwaysDisabled
[this._disabled
[i
]]) {
2591 this.toolbar
.enableButton(_button
.get('id'));
2594 if (!this._alwaysEnabled
[this._disabled
[i
]]) {
2595 this.toolbar
.deselectButton(_button
);
2599 //Handle updating the toolbar with active buttons
2600 for (var i
= 0; i
< this.toolbar
._buttonList
.length
; i
++) {
2601 if (!this._alwaysEnabled
[this.toolbar
._buttonList
[i
].get('value')]) {
2602 this.toolbar
.deselectButton(this.toolbar
._buttonList
[i
]);
2605 var path
= this._getDomPath();
2607 for (var i
= 0; i
< path
.length
; i
++) {
2608 var tag
= path
[i
].tagName
.toLowerCase();
2609 if (path
[i
].getAttribute('tag')) {
2610 var tag
= path
[i
].getAttribute('tag').toLowerCase();
2612 var cmd
= this._tag2cmd
[tag
];
2614 //Bold and Italic styles
2615 if (path
[i
].style
.fontWeight
.toLowerCase() == 'bold') {
2618 if (path
[i
].style
.fontStyle
.toLowerCase() == 'italic') {
2621 if (path
[i
].style
.textDecoration
.toLowerCase() == 'underline') {
2626 olType
= path
[i
].type
;
2632 if (!Lang
.isArray(cmd
)) {
2635 for (var j
= 0; j
< cmd
.length
; j
++) {
2636 var button
= this.toolbar
.getButtonByValue(cmd
[j
]);
2637 this.toolbar
.selectButton(button
);
2638 this.toolbar
.enableButton(button
);
2642 switch (path
[i
].style
.textAlign
.toLowerCase()) {
2647 var alignType
= path
[i
].style
.textAlign
.toLowerCase();
2648 if (path
[i
].style
.textAlign
.toLowerCase() == 'justify') {
2651 var button
= this.toolbar
.getButtonByValue('justify' + alignType
);
2652 this.toolbar
.selectButton(button
);
2653 this.toolbar
.enableButton(button
);
2656 //Handle Ordered List Drop Down - it will reset if olType is null
2657 //this._updateMenuChecked('insertorderedlist', olType);
2661 //Reset Font Family and Size to the inital configs
2662 var fn_button
= this.toolbar
.getButtonByValue('fontname');
2664 var family
= fn_button
._configs
.label
._initialConfig
.value
;
2665 fn_button
.set('label', '<span class="yui-toolbar-fontname-' + _cleanClassName(family
) + '">' + family
+ '</span>');
2666 this._updateMenuChecked('fontname', family
);
2669 var fs_button
= this.toolbar
.getButtonByValue('fontsize');
2671 fs_button
.set('label', fs_button
._configs
.label
._initialConfig
.value
);
2674 var hd_button
= this.toolbar
.getButtonByValue('heading');
2676 hd_button
.set('label', hd_button
._configs
.label
._initialConfig
.value
);
2677 this._updateMenuChecked('heading', 'none');
2682 this.fireEvent('afterNodeChange', { type
: 'afterNodeChange', target
: this });
2686 * @method _updateMenuChecked
2687 * @param {Object} button The command identifier of the button you want to check
2688 * @param {String} value The value of the menu item you want to check
2689 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar)
2690 * @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.
2692 _updateMenuChecked: function(button
, value
, tbar
) {
2694 tbar
= this.toolbar
;
2696 var _button
= tbar
.getButtonByValue(button
);
2697 var _menuItems
= _button
.getMenu().getItems();
2698 if (_menuItems
.length
== 0) {
2699 _button
.getMenu()._onBeforeShow();
2700 _menuItems
= _button
.getMenu().getItems();
2702 for (var i
= 0; i
< _menuItems
.length
; i
++) {
2703 _menuItems
[i
].cfg
.setProperty('checked', false);
2704 if (_menuItems
[i
].value
== value
) {
2705 _menuItems
[i
].cfg
.setProperty('checked', true);
2711 * @method _handleToolbarClick
2712 * @param {Event} ev The event that triggered the button click
2713 * @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.
2715 _handleToolbarClick: function(ev
) {
2718 var cmd
= ev
.button
.value
;
2719 if (ev
.button
.menucmd
) {
2721 cmd
= ev
.button
.menucmd
;
2723 if (this.STOP_EXEC_COMMAND
) {
2724 YAHOO
.log('execCommand skipped because we found the STOP_EXEC_COMMAND flag set to true', 'warn', 'Editor');
2725 YAHOO
.log('NOEXEC::execCommand::(' + cmd
+ '), (' + value
+ ')', 'warn', 'Editor');
2726 this.STOP_EXEC_COMMAND
= false;
2729 this.execCommand(cmd
, value
);
2731 Event
.stopEvent(ev
);
2735 * @method _setupAfterElement
2736 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation.
2738 _setupAfterElement: function() {
2739 if (!this.afterElement
) {
2740 this.afterElement
= document
.createElement('h2');
2741 this.afterElement
.className
= 'yui-editor-skipheader';
2742 this.afterElement
.tabIndex
= '-1';
2743 this.afterElement
.innerHTML
= this.STR_LEAVE_EDITOR
;
2744 this.get('element_cont').get('firstChild').appendChild(this.afterElement
);
2748 * @property EDITOR_PANEL_ID
2749 * @description HTML id to give the properties window in the DOM.
2752 EDITOR_PANEL_ID
: 'yui-editor-panel',
2754 * @property SEP_DOMPATH
2755 * @description The value to place in between the Dom path items
2760 * @property STR_LEAVE_EDITOR
2761 * @description The accessibility string for the element after the iFrame
2764 STR_LEAVE_EDITOR
: 'You have left the Rich Text Editor.',
2766 * @property STR_BEFORE_EDITOR
2767 * @description The accessibility string for the element before the iFrame
2770 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>',
2772 * @property STR_CLOSE_WINDOW
2773 * @description The Title of the close button in the Editor Window
2776 STR_CLOSE_WINDOW
: 'Close Window',
2778 * @property STR_CLOSE_WINDOW_NOTE
2779 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window
2782 STR_CLOSE_WINDOW_NOTE
: 'To close this window use the Escape key',
2784 * @property STR_TITLE
2785 * @description The Title of the HTML document that is created in the iFrame
2788 STR_TITLE
: 'Rich Text Area.',
2790 * @property STR_IMAGE_HERE
2791 * @description The text to place in the URL textbox when using the blankimage.
2794 STR_IMAGE_HERE
: 'Image Url Here',
2796 * @property STR_IMAGE_PROP_TITLE
2797 * @description The title for the Image Property Editor Window
2800 STR_IMAGE_PROP_TITLE
: 'Image Options',
2802 * @property STR_IMAGE_URL
2803 * @description The label string for Image URL
2806 STR_IMAGE_URL
: 'Image Url',
2808 * @property STR_IMAGE_TITLE
2809 * @description The label string for Image Description
2812 STR_IMAGE_TITLE
: 'Description',
2814 * @property STR_IMAGE_SIZE
2815 * @description The label string for Image Size
2818 STR_IMAGE_SIZE
: 'Size',
2820 * @property STR_IMAGE_ORIG_SIZE
2821 * @description The label string for Original Image Size
2824 STR_IMAGE_ORIG_SIZE
: 'Original Size',
2826 * @property STR_IMAGE_COPY
2827 * @description The label string for the image copy and paste message for Opera and Safari
2830 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>',
2832 * @property STR_IMAGE_PADDING
2833 * @description The label string for the image padding.
2836 STR_IMAGE_PADDING
: 'Padding',
2838 * @property STR_IMAGE_BORDER
2839 * @description The label string for the image border.
2842 STR_IMAGE_BORDER
: 'Border',
2844 * @property STR_IMAGE_TEXTFLOW
2845 * @description The label string for the image text flow.
2848 STR_IMAGE_TEXTFLOW
: 'Text Flow',
2850 * @property STR_LOCAL_FILE_WARNING
2851 * @description The label string for the local file warning.
2854 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>',
2856 * @property STR_LINK_PROP_TITLE
2857 * @description The label string for the Link Property Editor Window.
2860 STR_LINK_PROP_TITLE
: 'Link Options',
2862 * @property STR_LINK_PROP_REMOVE
2863 * @description The label string for the Remove link from text link inside the property editor.
2866 STR_LINK_PROP_REMOVE
: 'Remove link from text',
2868 * @property STR_LINK_URL
2869 * @description The label string for the Link URL.
2872 STR_LINK_URL
: 'Link URL',
2874 * @property STR_LINK_NEW_WINDOW
2875 * @description The string for the open in a new window label.
2878 STR_LINK_NEW_WINDOW
: 'Open in a new window.',
2880 * @property STR_LINK_TITLE
2881 * @description The string for the link description.
2884 STR_LINK_TITLE
: 'Description',
2887 * @property STOP_EXEC_COMMAND
2888 * @description Set to true when you want the default execCommand function to not process anything
2891 STOP_EXEC_COMMAND
: false,
2894 * @property STOP_NODE_CHANGE
2895 * @description Set to true when you want the default nodeChange function to not process anything
2898 STOP_NODE_CHANGE
: false,
2901 * @property CLASS_HIDDEN
2902 * @description CSS class applied to the body when the hiddenelements button is pressed.
2905 CLASS_HIDDEN
: 'hidden',
2908 * @property CLASS_LOCAL_FILE
2909 * @description CSS class applied to an element when it's found to have a local url.
2912 CLASS_LOCAL_FILE
: 'warning-localfile',
2915 * @property CLASS_CONTAINER
2916 * @description Default CSS class to apply to the editors container element
2919 CLASS_CONTAINER
: 'yui-editor-container',
2922 * @property CLASS_EDITABLE
2923 * @description Default CSS class to apply to the editors iframe element
2926 CLASS_EDITABLE
: 'yui-editor-editable',
2929 * @property CLASS_EDITABLE_CONT
2930 * @description Default CSS class to apply to the editors iframe's parent element
2933 CLASS_EDITABLE_CONT
: 'yui-editor-editable-container',
2936 * @property CLASS_PREFIX
2937 * @description Default prefix for dynamically created class names
2940 CLASS_PREFIX
: 'yui-editor',
2943 * @description Standard browser detection
2946 browser
: YAHOO
.env
.ua
,
2949 * @description The Editor class' initialization method
2951 init: function(p_oElement
, p_oAttributes
) {
2952 YAHOO
.widget
.Editor
.superclass
.init
.call(this, p_oElement
, p_oAttributes
);
2953 this.get('element_cont').addClass(this.CLASS_CONTAINER
);
2954 Dom
.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT
);
2955 this.get('iframe').addClass(this.CLASS_EDITABLE
);
2958 * @method initAttributes
2959 * @description Initializes all of the configuration attributes used to create
2961 * @param {Object} attr Object literal specifying a set of
2962 * configuration attributes used to create the editor.
2964 initAttributes: function(attr
) {
2965 YAHOO
.widget
.Editor
.superclass
.initAttributes
.call(this, attr
);
2971 * @description A reference to the textarea element that we are replacing
2975 this.setAttributeConfig('textarea', {
2976 value
: attr
.textarea
,
2981 * @description The height of the editor iframe container, not including the toolbar..
2982 * @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
2985 this.setAttributeConfig('height', {
2986 value
: attr
.height
|| Dom
.getStyle(self
.get('textarea'), 'height'),
2991 * @description The width of the editor container.
2992 * @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
2995 this.setAttributeConfig('width', {
2996 value
: attr
.width
|| Dom
.getStyle(this.get('textarea'), 'width'),
3001 * @config blankimage
3002 * @description The CSS used to show/hide hidden elements on the page
3003 * @default 'assets/blankimage.png'
3006 this.setAttributeConfig('blankimage', {
3007 value
: attr
.blankimage
|| this._getBlankImage()
3011 * @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>
3012 * @default <code><pre>
3013 .hidden div, .hidden p, .hidden span, .hidden img {
3014 border: 1px dotted #ccc;
3024 this.setAttributeConfig('hiddencss', {
3025 value
: attr
.hiddencss
|| '.hidden div,.hidden p,.hidden span,.hidden img { border: 1px dotted #ccc; } .hidden .yui-non { border: none; } .hidden img { padding: 2px; }',
3030 * @description The Base CSS used to format the content of the editor
3031 * @default <code><pre>body {
3032 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;
3035 color: blue; text-decoration: underline;
3037 span.yui-tag-blockquote {
3038 margin: 1em; display: block;
3040 span.yui-tag-indent {
3041 margin-left: 1em; display: block;
3043 .warning-localfile {
3044 border-bottom: 1px dashed red !important;
3048 this.setAttributeConfig('css', {
3049 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; }',
3054 * @description The default HTML to be written to the iframe document before the contents are loaded
3055 * @default This HTML requires a few things if you are to override:
3056 <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>
3057 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p>
3060 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
3063 <title>{TITLE}</title>
3064 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
3072 <body onload="document.body._rteLoaded = true;">
3080 this.setAttributeConfig('html', {
3081 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>',
3086 * @config handleSubmit
3087 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler.
3088 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form.
3089 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted.
3093 this.setAttributeConfig('handleSubmit', {
3096 method: function(exec
) {
3098 var ta
= this.get('textarea');
3100 Event
.addListener(ta
.form
, 'submit', function() {
3110 * @description Internal config for holding the iframe element.
3114 this.setAttributeConfig('iframe', {
3120 * @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.
3121 All Toolbar buttons are also disabled so they cannot be used.
3126 this.setAttributeConfig('disabled', {
3128 method: function(disabled
) {
3131 this._setDesignMode('off');
3132 this.toolbar
.set('disabled', true);
3133 this._mask
= document
.createElement('DIV');
3134 Dom
.setStyle(this._mask
, 'height', '100%');
3135 Dom
.setStyle(this._mask
, 'width', '100%');
3136 Dom
.setStyle(this._mask
, 'position', 'absolute');
3137 Dom
.setStyle(this._mask
, 'top', '0');
3138 Dom
.setStyle(this._mask
, 'left', '0');
3139 Dom
.setStyle(this._mask
, 'opacity', '.5');
3140 Dom
.addClass(this._mask
, 'yui-editor-masked');
3141 this.get('iframe').get('parentNode').appendChild(this._mask
);
3145 this._mask
.parentNode
.removeChild(this._mask
);
3147 this.toolbar
.set('disabled', false);
3148 this._setDesignMode('on');
3149 this._focusWindow();
3155 * @config element_cont
3156 * @description Internal config for the editors container
3160 this.setAttributeConfig('element_cont', {
3165 * @config toolbar_cont
3166 * @description Internal config for the toolbars container
3170 this.setAttributeConfig('toolbar_cont', {
3176 * @description The default toolbar config.
3177 * @default This config is too large to display here, view the code to see it: <a href="editor.js.html"></a>
3180 this.setAttributeConfig('toolbar', {
3181 value
: attr
.toolbar
|| {
3182 /* {{{ Defaut Toolbar Config */
3184 titlebar
: 'Text Editing Tools',
3187 { group
: 'fontstyle', label
: 'Font Name and Size',
3189 { type
: 'select', label
: 'Arial', value
: 'fontname', disabled
: true,
3191 { text
: 'Arial', checked
: true },
3192 { text
: 'Arial Black' },
3193 { text
: 'Comic Sans MS' },
3194 { text
: 'Courier New' },
3195 { text
: 'Lucida Console' },
3197 { text
: 'Times New Roman' },
3198 { text
: 'Trebuchet MS' },
3202 { type
: 'spin', label
: '13', value
: 'fontsize', range
: [ 9, 75 ], disabled
: true }
3205 { type
: 'separator' },
3206 { group
: 'textstyle', label
: 'Font Style',
3208 { type
: 'push', label
: 'Bold CTRL + SHIFT + B', value
: 'bold' },
3209 { type
: 'push', label
: 'Italic CTRL + SHIFT + I', value
: 'italic' },
3210 { type
: 'push', label
: 'Underline CTRL + SHIFT + U', value
: 'underline' },
3211 { type
: 'separator' },
3212 { type
: 'push', label
: 'Subscript', value
: 'subscript', disabled
: true },
3213 { type
: 'push', label
: 'Superscript', value
: 'superscript', disabled
: true },
3214 { type
: 'separator' },
3215 { type
: 'color', label
: 'Font Color', value
: 'forecolor', disabled
: true },
3216 { type
: 'color', label
: 'Background Color', value
: 'backcolor', disabled
: true },
3217 { type
: 'separator' },
3218 { type
: 'push', label
: 'Remove Formatting', value
: 'removeformat', disabled
: true },
3219 { type
: 'push', label
: 'Hidden Elements', value
: 'hiddenelements' }
3222 { type
: 'separator' },
3223 { group
: 'alignment', label
: 'Alignment',
3225 { type
: 'push', label
: 'Align Left CTRL + SHIFT + [', value
: 'justifyleft' },
3226 { type
: 'push', label
: 'Align Center CTRL + SHIFT + |', value
: 'justifycenter' },
3227 { type
: 'push', label
: 'Align Right CTRL + SHIFT + ]', value
: 'justifyright' },
3228 { type
: 'push', label
: 'Justify', value
: 'justifyfull' }
3231 { type
: 'separator' },
3232 { group
: 'parastyle', label
: 'Paragraph Style',
3234 { type
: 'select', label
: 'Normal', value
: 'heading', disabled
: true,
3236 { text
: 'Normal', value
: 'none', checked
: true },
3237 { text
: 'Header 1', value
: 'h1' },
3238 { text
: 'Header 2', value
: 'h2' },
3239 { text
: 'Header 3', value
: 'h3' },
3240 { text
: 'Header 4', value
: 'h4' },
3241 { text
: 'Header 5', value
: 'h5' },
3242 { text
: 'Header 6', value
: 'h6' }
3247 { type
: 'separator' },
3248 { group
: 'indentlist', label
: 'Indenting and Lists',
3250 { type
: 'push', label
: 'Indent', value
: 'indent', disabled
: true },
3251 { type
: 'push', label
: 'Outdent', value
: 'outdent', disabled
: true },
3252 { type
: 'push', label
: 'Create an Unordered List', value
: 'insertunorderedlist' },
3253 { type
: 'push', label
: 'Create an Ordered List', value
: 'insertorderedlist' }
3255 { type: 'menu', label: 'Create an Ordered List', value: 'insertorderedlist',
3257 { text: '1,2,3,4', value: '1', checked: true },
3258 { text: 'A,B,C,D', value: 'A' },
3259 { text: 'a,b,c,d', value: 'a' },
3260 { text: 'I,II,III,IV', value: 'I' },
3261 { text: 'i,ii,iii,iv', value: 'i' }
3267 { type
: 'separator' },
3268 { group
: 'insertitem', label
: 'Insert Item',
3270 { type
: 'push', label
: 'HTML Link CTRL + SHIFT + L', value
: 'createlink', disabled
: true },
3271 { type
: 'push', label
: 'Insert Image', value
: 'insertimage' }
3278 method: function(toolbar
) {
3283 * @description Should the editor animate window movements
3284 * @default false unless Animation is found, then true
3287 this.setAttributeConfig('animate', {
3289 validator: function(value
) {
3291 if (!YAHOO
.util
.Anim
) {
3299 * @description A reference to the panel we are using for windows.
3303 this.setAttributeConfig('panel', {
3306 validator: function(value
) {
3308 if (!YAHOO
.widget
.Panel
) {
3315 * @config localFileWarning
3316 * @description Should we throw the warning if we detect a file that is local to their machine?
3320 this.setAttributeConfig('localFileWarning', {
3325 * @description Toggle the display of the current Dom path below the editor
3329 this.setAttributeConfig('dompath', {
3331 method: function(dompath
) {
3332 if (dompath
&& !this.dompath
) {
3333 this.dompath
= document
.createElement('DIV');
3334 this.dompath
.id
= this.get('id') + '_dompath';
3335 Dom
.addClass(this.dompath
, 'dompath');
3336 this.get('element_cont').get('firstChild').appendChild(this.dompath
);
3337 if (this.get('iframe')) {
3338 this._writeDomPath();
3340 } else if (!dompath
&& this.dompath
) {
3341 this.dompath
.parentNode
.removeChild(this.dompath
);
3342 this.dompath
= null;
3344 this._setupAfterElement();
3349 * @description Should we try to adjust the markup for the following types: semantic, css or default
3350 * @default "semantic"
3353 this.setAttributeConfig('markup', {
3355 validator: function(markup
) {
3356 switch (markup
.toLowerCase()) {
3367 this.on('afterRender', function() {
3368 this._renderPanel();
3373 * @method _getBlankImage
3374 * @description Retrieves the full url of the image to use as the blank image.
3375 * @returns {String} The URL to the blank image
3377 _getBlankImage: function() {
3378 if (!this.DOMReady
) {
3379 this._queue
[this._queue
.length
] = ['_getBlankImage', arguments
];
3382 var div
= document
.createElement('div');
3383 div
.style
.position
= 'absolute';
3384 div
.style
.top
= '-9999px';
3385 div
.style
.left
= '-9999px';
3386 div
.className
= this.CLASS_PREFIX
+ '-blankimage';
3387 document
.body
.appendChild(div
);
3388 var img
= YAHOO
.util
.Dom
.getStyle(div
, 'background-image');
3389 img
= img
.replace('url(', '').replace(')', '').replace(/"/g, '');
3390 this.set('blankimage', img);
3395 * @method _handleFontSize
3396 * @description Handles the font size button in the toolbar.
3397 * @param {Object} o Object returned from Toolbar's buttonClick Event
3399 _handleFontSize: function(o) {
3400 var button = this.toolbar.getButtonById(o.button.id);
3401 var value = button.get('label') + 'px';
3402 this.execCommand('fontsize', value);
3403 this.STOP_EXEC_COMMAND = true;
3407 * @method _handleColorPicker
3408 * @description Handles the colorpicker buttons in the toolbar.
3409 * @param {Object} o Object returned from Toolbar's buttonClick Event
3411 _handleColorPicker: function(o) {
3413 var value = '#' + o.color;
3414 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
3415 this.execCommand(cmd, value);
3420 * @method _handleAlign
3421 * @description Handles the alignment buttons in the toolbar.
3422 * @param {Object} o Object returned from Toolbar's buttonClick Event
3424 _handleAlign: function(o) {
3425 var button = this.toolbar.getButtonById(o.button.id);
3427 for (var i = 0; i < o.button.menu.length; i++) {
3428 if (o.button.menu[i].value == o.button.value) {
3429 cmd = o.button.menu[i].value;
3432 var value = this._getSelection();
3434 this.execCommand(cmd, value);
3435 this.STOP_EXEC_COMMAND = true;
3439 * @method _handleAfterNodeChange
3440 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state).
3442 _handleAfterNodeChange: function() {
3443 var path = this._getDomPath();
3444 for (var i = 0; i < path.length; i++) {
3446 tag = elm.tagName.toLowerCase(),
3451 if (elm.getAttribute('tag')) {
3452 tag = elm.getAttribute('tag');
3455 family = elm.getAttribute('face');
3456 if (Dom.getStyle(elm, 'font-family')) {
3457 family = Dom.getStyle(elm, 'font-family');
3459 var fn_button = this.toolbar.getButtonByValue('fontname');
3461 for (var b = 0; b < fn_button._configs.menu.value.length; b++) {
3462 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) {
3464 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
3468 family = fn_button._configs.label._initialConfig.value;
3470 fn_button.set('label', '<span class="yui
-toolbar
-fontname
-' + _cleanClassName(family) + '">' + family + '</span>');
3471 this._updateMenuChecked('fontname', family);
3474 var fs_button = this.toolbar.getButtonByValue('fontsize');
3476 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'));
3477 if ((fontsize == null) || isNaN(fontsize)) {
3478 fontsize = fs_button._configs.label._initialConfig.value;
3480 fs_button.set('label', ''+fontsize);
3483 if (tag.substring(0, 1) == 'h') {
3484 var hd_button = this.toolbar.getButtonByValue('heading');
3486 for (var b = 0; b < hd_button._configs.menu.value.length; b++) {
3487 if (hd_button._configs.menu.value[b].value.toLowerCase() == tag) {
3488 hd_button.set('label', hd_button._configs.menu.value[b].text);
3491 this._updateMenuChecked('heading', tag);
3496 if (elm && elm.tagName && (elm.tagName.toLowerCase() != 'body')) {
3497 this.toolbar.enableButton(fn_button);
3498 this.toolbar.enableButton(fs_button);
3504 * @method _handleInsertImageClick
3505 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked.
3507 _handleInsertImageClick: function() {
3508 //this.toolbar.disableButton(this.toolbar.getButtonByValue('insertimage'));
3509 this.on('afterExecCommand', function() {
3510 var el = this.currentElement[0],
3518 win = new YAHOO.widget.EditorWindow('insertimage', {
3523 el = this._getSelectedElement();
3526 if (el.getAttribute('src')) {
3527 src = el.getAttribute('src', 2);
3528 if (src.indexOf(this.get('blankimage')) != -1) {
3529 src = this.STR_IMAGE_HERE;
3533 if (el.getAttribute('alt', 2)) {
3534 title = el.getAttribute('alt', 2);
3536 if (el.getAttribute('title', 2)) {
3537 title = el.getAttribute('title', 2);
3539 height = parseInt(el.height);
3540 width = parseInt(el.width);
3541 if (el.style.height) {
3542 height = parseInt(el.style.height);
3544 if (el.style.width) {
3545 width = parseInt(el.style.width);
3547 if (el.style.margin) {
3548 padding = parseInt(el.style.margin);
3551 el._height = height;
3556 var oheight = el._height;
3557 var owidth = el._width;
3560 var str = '<label for="insertimage_url
"><strong>' + this.STR_IMAGE_URL + ':</strong> <input type="text
" id="insertimage_url
" value="' + src + '" size="40"></label>';
3561 var body = document.createElement('div');
3562 body.innerHTML = str;
3564 var tbarCont = document.createElement('div');
3565 tbarCont.id = 'img_toolbar';
3566 body.appendChild(tbarCont);
3568 var str2 = '<label for="insertimage_title
"><strong>' + this.STR_IMAGE_TITLE + ':</strong> <input type="text
" id="insertimage_title
" value="' + title + '" size="40"></label>';
3569 var div = document.createElement('div');
3570 div.innerHTML = str2;
3571 body.appendChild(div);
3577 var tbar = new YAHOO.widget.Toolbar(tbarCont, {
3580 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
3582 { type: 'spin', label: ''+padding, value: 'padding', range: [0, 50] }
3585 { type: 'separator' },
3586 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
3588 { type: 'select', label: 'Border Size', value: 'bordersize',
3590 { text: 'none', value: '0', checked: true },
3591 { text: '----', value: '1' },
3592 { text: '----', value: '2' },
3593 { text: '----', value: '3' },
3594 { text: '----', value: '4' },
3595 { text: '----', value: '5' }
3598 { type: 'select', label: 'Border Type', value: 'bordertype', disabled: true,
3600 { text: '----', value: 'solid', checked: true },
3601 { text: '----', value: 'dashed' },
3602 { text: '----', value: 'dotted' }
3605 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true }
3608 { type: 'separator' },
3609 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':',
3611 { type: 'push', label: 'Left', value: 'left' },
3612 { type: 'push', label: 'Inline', value: 'inline' },
3613 { type: 'push', label: 'Block', value: 'block' },
3614 { type: 'push', label: 'Right', value: 'right' }
3622 var btype = 'solid';
3623 if (el.style.borderLeftWidth) {
3624 bsize = parseInt(el.style.borderLeftWidth);
3626 if (el.style.borderLeftStyle) {
3627 btype = el.style.borderLeftStyle;
3629 var bs_button = tbar.getButtonByValue('bordersize');
3630 var bSizeStr = ((parseInt(bsize) > 0) ? '----' : 'none');
3631 bs_button.set('label', '<span class="yui
-toolbar
-bordersize
-' + bsize + '">'+bSizeStr+'</span>');
3632 this._updateMenuChecked('bordersize', bsize, tbar);
3634 var bs_button = tbar.getButtonByValue('bordertype');
3635 bs_button.set('label', '<span class="yui
-toolbar
-bordertype
-' + btype + '">----</span>');
3636 this._updateMenuChecked('bordertype', btype, tbar);
3637 if (parseInt(bsize) > 0) {
3638 tbar.enableButton(tbar.getButtonByValue('bordertype'));
3639 tbar.enableButton(tbar.getButtonByValue('bordercolor'));
3642 var cont = tbar.get('cont');
3643 var hw = document.createElement('div');
3644 hw.className = 'yui-toolbar-group yui-toolbar-group-padding height-width';
3645 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>';
3647 if ((height != oheight) || (width != owidth)) {
3648 orgSize = '<span class="info
">' + this.STR_IMAGE_ORIG_SIZE + '<br>'+ owidth +' x ' + oheight + '</span>';
3650 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;
3651 cont.insertBefore(hw, cont.firstChild);
3653 Event.onAvailable('insertimage_width', function() {
3654 Event.on('insertimage_width', 'blur', function() {
3655 var value = parseInt(Dom.get('insertimage_width').value);
3657 el.style.width = value + 'px';
3662 Event.onAvailable('insertimage_height', function() {
3663 Event.on('insertimage_height', 'blur', function() {
3664 var value = parseInt(Dom.get('insertimage_height').value);
3666 el.style.height = value + 'px';
3672 if (el.align == 'right') {
3673 tbar.selectButton(tbar.getButtonByValue('right'));
3674 } else if (el.align == 'left') {
3675 tbar.selectButton(tbar.getButtonByValue('left'));
3676 } else if (el.style.display == 'block') {
3677 tbar.selectButton(tbar.getButtonByValue('block'));
3679 tbar.selectButton(tbar.getButtonByValue('inline'));
3681 if (parseInt(el.style.marginLeft) > 0) {
3682 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft));
3684 if (el.style.borderSize) {
3685 tbar.selectButton(tbar.getButtonByValue('bordersize'));
3686 tbar.selectButton(tbar.getButtonByValue(parseInt(el.style.borderSize)));
3689 tbar.on('colorPickerClicked', function(o) {
3690 var size = '1', type = 'solid', color = 'black';
3692 if (el.style.borderLeftWidth) {
3693 size = parseInt(el.style.borderLeftWidth);
3695 if (el.style.borderLeftStyle) {
3696 type = el.style.borderLeftStyle;
3698 if (el.style.borderLeftColor) {
3699 color = el.style.borderLeftColor;
3701 var borderString = size + 'px ' + type + ' #' + o.color;
3702 el.style.border = borderString;
3703 }, this.toolbar, true);
3705 tbar.on('buttonClick', function(o) {
3706 var value = o.button.value;
3707 if (o.button.menucmd) {
3708 value = o.button.menucmd
3710 var size = '1', type = 'solid', color = 'black';
3712 /* All border calcs are done on the left border
3713 since our default interface only supports
3714 one border size/type and color */
3715 if (el.style.borderLeftWidth) {
3716 size = parseInt(el.style.borderLeftWidth);
3718 if (el.style.borderLeftStyle) {
3719 type = el.style.borderLeftStyle;
3721 if (el.style.borderLeftColor) {
3722 color = el.style.borderLeftColor;
3726 var borderString = parseInt(o.button.value) + 'px ' + type + ' ' + color;
3727 el.style.border = borderString;
3728 if (parseInt(o.button.value) > 0) {
3729 tbar.enableButton(tbar.getButtonByValue('bordertype'));
3730 tbar.enableButton(tbar.getButtonByValue('bordercolor'));
3732 tbar.disableButton(tbar.getButtonByValue('bordertype'));
3733 tbar.disableButton(tbar.getButtonByValue('bordercolor'));
3737 var borderString = size + 'px ' + o.button.value + ' ' + color;
3738 el.style.border = borderString;
3742 tbar.deselectAllButtons();
3743 el.style.display = '';
3744 el.align = o.button.value;
3747 tbar.deselectAllButtons();
3748 el.style.display = '';
3752 tbar.deselectAllButtons();
3753 el.style.display = 'block';
3754 el.align = 'center';
3757 var _button = tbar.getButtonById(o.button.id);
3758 el.style.margin = _button.get('label') + 'px';
3761 tbar.selectButton(tbar.getButtonByValue(o.button.value));
3765 win.setHeader(this.STR_IMAGE_PROP_TITLE);
3767 if ((this.browser.webkit && !this.browser.webkit3) || this.browser.opera) {
3768 var str = this.STR_IMAGE_COPY;
3771 this.openWindow(win);
3774 //Set event after openWindow..
3775 Event.onAvailable('insertimage_url', function() {
3776 window.setTimeout(function() {
3777 YAHOO.util.Dom.get('insertimage_url').focus();
3779 YAHOO.util.Dom.get('insertimage_url').select();
3783 if (this.get('localFileWarning')) {
3784 Event.on('insertimage_url', 'blur', function() {
3785 var url = Dom.get('insertimage_url');
3786 if ((url.value != '') && ((url.value.indexOf('file:/') != -1) || (url.value.indexOf(':\\') != -1))) {
3787 //Local File throw Warning
3788 Dom.addClass(url, 'warning');
3789 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
3790 var str = this.STR_LOCAL_FILE_WARNING;
3791 this.get('panel').setFooter(str);
3793 Dom.removeClass(url, 'warning');
3794 this.get('panel').setFooter(' ');
3795 if ((this.browser.webkit && !this.browser.webkit3) || this.browser.opera) {
3796 var str = this.STR_IMAGE_COPY;
3797 this.get('panel').setFooter(str);
3800 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
3801 this.currentElement[0].setAttribute('src', url.value);
3802 var img = new Image();
3804 window.setTimeout(function() {
3805 YAHOO.util.Dom.get('insertimage_height').value = img.height;
3806 YAHOO.util.Dom.get('insertimage_width').value = img.width;
3807 if (!self.currentElement[0]._height) {
3808 self.currentElement[0]._height = img.height;
3810 if (!self.currentElement[0]._width) {
3811 self.currentElement[0]._width = img.width;
3816 img.src = url.value;
3826 * @method _handleInsertImageWindowClose
3827 * @description Handles the closing of the Image Properties Window.
3829 _handleInsertImageWindowClose: function() {
3830 var url = Dom.get('insertimage_url');
3831 var title = Dom.get('insertimage_title');
3832 var el = this.currentElement[0];
3833 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) {
3834 el.setAttribute('src', url.value);
3835 el.setAttribute('title', title.value);
3836 el.setAttribute('alt', title.value);
3837 //this.toolbar.enableButton(this.toolbar.getButtonByValue('insertimage'));
3839 //No url/src given, remove the node from the document
3840 el.parentNode.removeChild(el);
3845 * @method _handleCreateLinkClick
3846 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked.
3848 _handleCreateLinkClick: function() {
3849 this.on('afterExecCommand', function() {
3851 var win = new YAHOO.widget.EditorWindow('createlink', {
3855 var el = this.currentElement[0],
3861 if (el.getAttribute('href') != null) {
3862 url = el.getAttribute('href');
3863 if ((url != '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) {
3864 //Local File throw Warning
3865 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
3866 var str = this.STR_LOCAL_FILE_WARNING;
3873 if (el.getAttribute('title') != null) {
3874 title = el.getAttribute('title');
3876 if (el.getAttribute('target') != null) {
3877 target = el.getAttribute('target');
3880 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>';
3881 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>';
3882 str += '<label for="createlink_title
"><strong>' + this.STR_LINK_TITLE + ':</strong> <input type="text
" name="createlink_title
" id="createlink_title
" value="' + title + '"></label>';
3884 var body = document.createElement('div');
3885 body.innerHTML = str;
3887 var unlinkCont = document.createElement('div');
3888 unlinkCont.className = 'removeLink';
3889 var unlink = document.createElement('a');
3891 unlink.innerHTML = this.STR_LINK_PROP_REMOVE;
3892 unlink.title = this.STR_LINK_PROP_REMOVE;
3893 Event.on(unlink, 'click', function(ev) {
3894 Event.stopEvent(ev);
3895 this.execCommand('unlink');
3898 unlinkCont.appendChild(unlink);
3899 body.appendChild(unlinkCont);
3901 win.setHeader(this.STR_LINK_PROP_TITLE);
3904 Event.onAvailable('createlink_url', function() {
3905 window.setTimeout(function() {
3907 YAHOO.util.Dom.get('createlink_url').focus();
3910 Event.on('createlink_url', 'blur', function() {
3911 var url = Dom.get('createlink_url');
3912 if ((url.value != '') && ((url.value.indexOf('file:/') != -1) || (url.value.indexOf(':\\') != -1))) {
3913 //Local File throw Warning
3914 Dom.addClass(url, 'warning');
3915 YAHOO.log('Local file reference found, show local warning', 'warn', 'Editor');
3916 var str = this.STR_LOCAL_FILE_WARNING;
3917 this.get('panel').setFooter(str);
3919 Dom.removeClass(url, 'warning');
3920 this.get('panel').setFooter(' ');
3925 this.openWindow(win);
3930 * @method _handleCreateLinkWindowClose
3931 * @description Handles the closing of the Link Properties Window.
3933 _handleCreateLinkWindowClose: function() {
3934 var url = Dom.get('createlink_url');
3935 var target = Dom.get('createlink_target');
3936 var title = Dom.get('createlink_title');
3937 var el = this.currentElement[0];
3938 if (url && url.value) {
3939 var urlValue = url.value;
3940 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
3941 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) {
3942 //Found an @ sign, prefix with mailto:
3943 urlValue = 'mailto:' + urlValue;
3945 /* :// not found adding */
3946 urlValue = 'http:/'+'/' + urlValue;
3949 el.setAttribute('href', urlValue);
3950 if (target.checked) {
3951 el.setAttribute('target', target.value);
3953 el.setAttribute('target', '');
3955 el.setAttribute('title', ((title.value) ? title.value : ''));
3958 el.removeAttribute('tag');
3959 Dom.removeClass(el, 'yui-tag-a');
3960 Dom.removeClass(el, 'yui-tag');
3961 Dom.addClass(el, 'yui-non');
3967 * @description Causes the toolbar and the editor to render and replace the textarea.
3969 render: function() {
3970 if (!this.DOMReady) {
3971 this._queue[this._queue.length] = ['render', arguments];
3975 var tbarConf = this.get('toolbar');
3976 //Set the toolbar to disabled until content is loaded
3977 tbarConf.disabled = true;
3978 //Create Toolbar instance
3979 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf);
3980 YAHOO.log('fireEvent::toolbarLoaded', 'info', 'Editor');
3981 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar });
3984 this.toolbar.on('toolbarCollapsed', function() {
3985 if (this.currentWindow) {
3989 this.toolbar.on('toolbarExpanded', function() {
3990 if (this.currentWindow) {
3994 this.toolbar.on('fontsizeClick', function(o) {
3995 this._handleFontSize(o);
3998 this.toolbar.on('colorPickerClicked', function(o) {
3999 this._handleColorPicker(o);
4002 this.toolbar.on('alignClick', function(o) {
4003 this._handleAlign(o);
4005 this.on('afterNodeChange', function() {
4006 this._handleAfterNodeChange();
4008 this.toolbar.on('insertimageClick', function() {
4009 this._handleInsertImageClick();
4011 this.on('windowinsertimageClose', function() {
4012 this._handleInsertImageWindowClose();
4014 this.toolbar.on('createlinkClick', function() {
4015 this._handleCreateLinkClick();
4017 this.on('windowcreatelinkClose', function() {
4018 this._handleCreateLinkWindowClose();
4022 //Replace Textarea with editable area
4024 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element'));
4027 if (!this.beforeElement) {
4028 this.beforeElement = document.createElement('h2');
4029 this.beforeElement.className = 'yui-editor-skipheader';
4030 this.beforeElement.tabIndex = '-1';
4031 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR;
4032 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling'));
4035 Dom.setStyle(this.get('textarea'), 'display', 'none');
4036 this.get('element_cont').appendChild(this.get('element'));
4037 this.get('element_cont').setStyle('display', 'block');
4040 //Set height and width of editor container
4041 this.get('element_cont').setStyle('width', this.get('width'));
4042 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height'));
4044 this.get('iframe').setStyle('width', '100%'); //WIDTH
4045 //this.get('iframe').setStyle('_width', '99%'); //WIDTH
4046 this.get('iframe').setStyle('height', '100%');
4050 window.setTimeout(function() {
4051 self._setInitialContent.call(self);
4054 this.fireEvent('afterRender', { type: 'afterRender', target: this });
4057 * @method execCommand
4058 * @param {String} action The "execCommand
" action to try to execute (Example: bold, insertimage, inserthtml)
4059 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana'
4060 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions
4062 execCommand: function(action, value) {
4063 this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments });
4064 if (this.STOP_EXEC_COMMAND) {
4065 this.STOP_EXEC_COMMAND = false;
4068 this._setMarkupType(action);
4069 if (this.browser.ie) {
4070 this._getWindow().focus();
4073 var _sel = this._getSelection();
4074 var _range = this._getRange();
4075 var _selEl = this._getSelectedElement();
4079 switch (action.toLowerCase()) {
4081 if (this.browser.ie) {
4082 action = 'formatblock';
4084 if (value == 'none') {
4085 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'))) {
4086 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') {
4087 _sel = _sel.parentNode;
4089 var _span = this._getDoc().createElement('span');
4090 _span.className = 'yui-non';
4091 _span.innerHTML = _sel.innerHTML;
4092 _sel.parentNode.replaceChild(_span, _sel);
4096 if (this.browser.ie || this.browser.webkit || this.browser.opera) {
4097 this._createCurrentElement(value);
4103 if (this.browser.gecko || this.browser.opera) {
4104 this._setEditorStyle(true);
4105 action = 'hilitecolor';
4108 case 'hiddenelements':
4113 //var el = this._getSelectedElement();
4114 var el = this.currentElement[0];
4115 el.removeAttribute('title');
4116 el.removeAttribute('tag');
4117 el.removeAttribute('target');
4118 el.removeAttribute('href');
4119 YAHOO.util.Dom.addClass(el, 'yui-non');
4120 YAHOO.util.Dom.removeClass(el, 'yui-tag-a');
4121 YAHOO.util.Dom.removeClass(el, 'yui-tag');
4125 var el = this._getSelectedElement();
4126 if (!el || (el.getAttribute('tag') != 'a')) {
4127 this._createCurrentElement('a');
4129 this.currentElement[0] = el;
4135 value = this.get('blankimage');
4139 * @browser Safari 2.x
4140 * @description The issue here is that we have no way of knowing where the cursor position is
4141 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4144 var el = this._getSelectedElement();
4146 if (el && el.tagName && (el.tagName.toLowerCase() == 'img')) {
4147 this.currentElement[0] = el;
4150 if (this._getDoc().queryCommandEnabled(action)) {
4151 this._getDoc().execCommand('insertimage', false, value);
4152 var imgs = this._getDoc().getElementsByTagName('img');
4153 for (var i = 0; i < imgs.length; i++) {
4154 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) {
4155 YAHOO.util.Dom.addClass(imgs[i], 'yui-img');
4156 this.currentElement[0] = imgs[i];
4161 this._createCurrentElement('img');
4162 var _img = this._getDoc().createElement('img');
4163 _img.setAttribute('src', value);
4164 YAHOO.util.Dom.addClass(_img, 'yui-img');
4165 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]);
4166 this.currentElement[0] = _img;
4175 * @browser Safari 2.x
4176 * @description The issue here is that we have no way of knowing where the cursor position is
4177 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4179 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
4180 YAHOO.log('More Safari DOM tricks (inserthtml)', 'info', 'EditorSafari');
4181 this._createCurrentElement('img');
4182 var _span = this._getDoc().createElement('span');
4183 _span.innerHTML = value;
4184 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]);
4186 } else if (this.browser.ie) {
4187 var _range = this._getRange();
4189 _range.item(0).outerHTML = value;
4191 _range.pasteHTML(value);
4196 case 'removeformat':
4198 * @knownissue Remove Format issue
4199 * @browser Safari 2.x
4200 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected.
4201 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds.
4202 * So here we are making the best possible guess and acting on it.
4204 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) {
4205 this._createCurrentElement('span');
4206 YAHOO.util.Dom.addClass(this.currentElement[0], 'yui-non');
4207 var re= /<\S[^><]*>/g;
4208 var str = this.currentElement[0].innerHTML.replace(re, '');
4209 var _txt = this._getDoc().createTextNode(str);
4210 this.currentElement[0].parentNode.parentNode.replaceChild(_txt, this.currentElement[0].parentNode);
4217 if (this.browser.webkit) {
4218 YAHOO.log('Safari dom fun again (' + action + ')..', 'info', 'EditorSafari');
4219 var tag = action.toLowerCase().substring(0, 3);
4220 this._createCurrentElement(tag);
4221 if (this.currentElement[0].parentNode.tagName.toLowerCase() == tag) {
4222 YAHOO.log('we are a child of tag (' + tag + '), reverse process', 'info', 'EditorSafari');
4223 var span = this._getDoc().createElement('span');
4224 span.innerHTML = this.currentElement[0].innerHTML;
4225 YAHOO.util.Dom.addClass(span, 'yui-non');
4226 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4229 var _sub = this._getDoc().createElement(tag);
4230 _sub.innerHTML = this.currentElement[0].innerHTML;
4231 this.currentElement[0].parentNode.replaceChild(_sub, this.currentElement[0]);
4237 value = 'blockquote';
4238 if (this.browser.webkit) {
4239 this._createCurrentElement('blockquote');
4240 if (YAHOO.util.Dom.hasClass(this.currentElement[0].parentNode, 'yui-tag-blockquote')) {
4241 var span = this._getDoc().createElement('span');
4242 span.innerHTML = this.currentElement[0].innerHTML;
4243 YAHOO.util.Dom.addClass(span, 'yui-non');
4244 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4248 var tar = Event.getTarget(this.currentEvent);
4249 if (tar && tar.tagName && (tar.tagName.toLowerCase() == 'blockquote')) {
4250 var span = this._getDoc().createElement('span');
4251 span.innerHTML = tar.innerHTML;
4252 YAHOO.util.Dom.addClass(span, 'yui-non');
4253 tar.parentNode.replaceChild(span, tar);
4260 this._createCurrentElement(action.toLowerCase());
4261 if (this.currentElement[0].parentNode) {
4262 if (action.toLowerCase() == 'outdent') {
4263 if (YAHOO.util.Dom.hasClass(this.currentElement[0].parentNode, 'yui-tag-indent')) {
4264 var span = this._getDoc().createElement('span');
4265 span.innerHTML = this.currentElement[0].innerHTML;
4266 YAHOO.util.Dom.addClass(span, 'yui-non');
4267 this.currentElement[0].parentNode.parentNode.replaceChild(span, this.currentElement[0].parentNode);
4273 case 'insertorderedlist':
4274 case 'insertunorderedlist':
4276 * @knownissue Safari 2.+ doesn't support ordered and unordered lists
4277 * @browser Safari 2.x
4278 * The issue with this workaround is that when applied to a set of text
4279 * that has BR's in it, Safari may or may not pick up the individual items as
4280 * list items. This is fixed in WebKit (Safari 3)
4282 var tag = ((action.toLowerCase() == 'insertorderedlist') ? 'ol' : 'ul');
4283 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) || this.browser.opera) {
4284 if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) {
4285 var selEl = this._getSelectedElement();
4286 if ((selEl.tagName.toLowerCase() == 'li') && (selEl.parentNode.tagName.toLowerCase() == tag)) {
4287 YAHOO.log('We already have a list, undo it', 'info', 'Editor');
4288 var el = selEl.parentNode;
4289 var list = this._getDoc().createElement('span');
4290 YAHOO.util.Dom.addClass(list, 'yui-non');
4292 var lis = el.getElementsByTagName('li');
4293 for (var i = 0; i < lis.length; i++) {
4294 str += lis[i].innerHTML + '<br>';
4296 list.innerHTML = str;
4299 YAHOO.log('Create list item', 'info', 'Editor');
4300 this._createCurrentElement(tag.toLowerCase());
4301 var el = this.currentElement[0];
4302 var list = this._getDoc().createElement(tag);
4306 var li = this._getDoc().createElement('li');
4307 li.innerHTML = el.innerHTML + ' ';
4308 list.appendChild(li);
4310 el.parentNode.replaceChild(list, el);
4313 var el = this._getSelectedElement();
4314 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..
4315 YAHOO.log('We already have a list, undo it', 'info', 'Editor');
4316 if (this.browser.ie) {
4317 YAHOO.log('Undo IE', 'info', 'Editor');
4320 var lis = el.parentNode.getElementsByTagName('li');
4321 for (var i = 0; i < lis.length; i++) {
4322 str += lis[i].innerHTML + '<br>';
4324 var newEl = this._getDoc().createElement('span');
4325 newEl.innerHTML = str;
4326 el.parentNode.parentNode.replaceChild(newEl, el.parentNode);
4329 this._getDoc().execCommand(action, '', el.parentNode);
4333 if (this.browser.opera) {
4335 window.setTimeout(function() {
4336 var lis = self._getDoc().getElementsByTagName('li');
4337 for (var i = 0; i < lis.length; i++) {
4338 if (lis[i].innerHTML.toLowerCase() == '<br>') {
4339 lis[i].parentNode.parentNode.removeChild(lis[i].parentNode);
4344 if (this.browser.ie && exec) {
4346 if (this._getRange().html) {
4347 html = '<li>' + this._getRange().html+ '</li>';
4349 html = '<li>' + this._getRange().text + '</li>';
4352 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
4358 var selEl = this._getSelectedElement();
4359 this.currentFont = value;
4360 if (selEl && selEl.tagName && !this._hasSelection()) {
4361 YAHOO.util.Dom.setStyle(selEl, 'font-family', value);
4366 if ((this.currentElement.length > 0) && (!this._hasSelection())) {
4367 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
4369 this._createCurrentElement('span', {'fontSize': value });
4375 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'Editor');
4377 this._getDoc().execCommand(action, false, value);
4379 YAHOO.log('execCommand Failed', 'error', 'Editor');
4382 YAHOO.log('OVERRIDE::execCommand skipped', 'warn', 'Editor');
4384 this.on('afterExecCommand', function() {
4385 this.unsubscribeAll('afterExecCommand');
4388 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
4393 * @method _createCurrentElement
4394 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create
4395 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element.
4396 * @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.
4397 * 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
4398 * <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.
4400 _createCurrentElement: function(tagName, tagStyle) {
4401 var tagName = ((tagName) ? tagName : 'a'),
4402 sel = this._getSelection(),
4405 _doc = this._getDoc();
4407 if (this.currentFont) {
4411 tagStyle.fontFamily = this.currentFont;
4412 this.currentFont = null;
4414 this.currentElement = [];
4416 var _elCreate = function() {
4424 var el = _doc.createElement(tagName);
4427 var el = _doc.createElement('span');
4428 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName);
4429 YAHOO.util.Dom.addClass(el, 'yui-tag');
4430 el.setAttribute('tag', tagName);
4433 for (var i in tagStyle) {
4434 if (YAHOO.util.Lang.hasOwnProperty(tagStyle, i)) {
4435 el.style[i] = tagStyle[i];
4443 if (!this._hasSelection()) {
4444 if (this._getDoc().queryCommandEnabled('insertimage')) {
4445 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img');
4446 var imgs = this._getDoc().getElementsByTagName('img');
4447 for (var i = 0; i < imgs.length; i++) {
4448 if (imgs[i].getAttribute('src', 2) == 'yui-tmp-img') {
4450 imgs[i].parentNode.replaceChild(el, imgs[i]);
4451 this.currentElement[this.currentElement.length] = el;
4452 //this.currentElement = el;
4456 if (this.currentEvent) {
4457 tar = YAHOO.util.Event.getTarget(this.currentEvent);
4460 tar = this._getDoc().body;
4466 * @browser Safari 2.x
4467 * @description The issue here is that we have no way of knowing where the cursor position is
4468 * inside of the iframe, so we have to place the newly inserted data in the best place that we can.
4471 if (tar.tagName.toLowerCase() == 'body') {
4472 tar.appendChild(el);
4473 } else if (tar.nextSibling) {
4474 tar.parentNode.insertBefore(el, tar.nextSibling);
4476 tar.parentNode.appendChild(el);
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);
4488 //Force CSS Styling for this action...
4489 this._setEditorStyle(true);
4490 this._getDoc().execCommand('fontname', false, 'yui-tmp');
4492 /* TODO: This needs to be cleaned up.. */
4493 var _tmp1 = this._getDoc().getElementsByTagName('font');
4494 var _tmp2 = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName);
4495 var _tmp3 = this._getDoc().getElementsByTagName('span');
4496 var _tmp4 = this._getDoc().getElementsByTagName('i');
4497 var _tmp5 = this._getDoc().getElementsByTagName('b');
4498 for (var e = 0; e < _tmp1.length; e++) {
4499 _tmp[_tmp.length] = _tmp1[e];
4501 for (var e = 0; e < _tmp2.length; e++) {
4502 _tmp[_tmp.length] = _tmp2[e];
4504 for (var e = 0; e < _tmp3.length; e++) {
4505 _tmp[_tmp.length] = _tmp3[e];
4507 for (var e = 0; e < _tmp4.length; e++) {
4508 _tmp[_tmp.length] = _tmp4[e];
4510 for (var e = 0; e < _tmp5.length; e++) {
4511 _tmp[_tmp.length] = _tmp5[e];
4513 for (var i = 0; i < _tmp.length; i++) {
4514 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) {
4515 var el = _elCreate();
4516 el.innerHTML = _tmp[i].innerHTML;
4517 if (_tmp[i].parentNode) {
4518 _tmp[i].parentNode.replaceChild(el, _tmp[i]);
4519 //this.currentElement = el;
4520 this.currentElement[this.currentElement.length] = el;
4521 this.currentEvent = null;
4522 if (this.browser.webkit) {
4523 //Force Safari to focus the new element
4524 this._getSelection().setBaseAndExtent(el, 0, el, 0);
4525 this._getSelection().collapse(true);
4527 if (this.browser.ie && tagStyle && tagStyle.fontSize) {
4528 this._getSelection().empty();
4530 if (this.browser.gecko) {
4531 this._getSelection().collapseToStart();
4536 var len = this.currentElement.length;
4537 for (var i = 0; i < len; i++) {
4538 if ((i + 1) != len) { //Skip the last one in the list
4539 if (this.currentElement[i] && this.currentElement[i].nextSibling) {
4540 if (this.currentElement[i].tagName && (this.currentElement[i].tagName.toLowerCase() != 'br')) {
4541 this.currentElement[this.currentElement.length] = this.currentElement[i].nextSibling;
4550 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea.
4552 saveHTML: function() {
4553 var html = this.cleanHTML();
4554 this.get('textarea').value = html;
4558 * @method setEditorHTML
4559 * @param {String} html The html content to load into the editor
4560 * @description Loads HTML into the editors body
4562 setEditorHTML: function(html) {
4563 this._getDoc().body.innerHTML = html;
4567 * @method getEditorHTML
4568 * @description Gets the unprocessed/unfiltered HTML from the editor
4570 getEditorHTML: function() {
4571 return this._getDoc().body.innerHTML;
4575 * @param {String} html The unfiltered HTML
4576 * @description Process the HTML with a few regexes to clean it up and stabilize the output
4577 * @returns {String} The filtered HTML
4579 cleanHTML: function(html) {
4580 //Start Filtering Output
4583 var html = this.getEditorHTML();
4585 //Make some backups...
4586 html = html.replace(/<div><br><\/div>/gi, '<YUI_BR>');
4587 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>');
4588 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>');
4589 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>');
4590 html = html.replace(/<br class="khtml
-block
-placeholder
">/gi, '<YUI_BR>');
4591 html = html.replace(/<br>/gi, '<YUI_BR>');
4592 html = html.replace(/<br\/>/gi, '<YUI_BR>');
4593 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>');
4594 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>');
4595 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>');
4597 //Convert b and i tags to strong and em tags
4598 html = html.replace(/<i([^>]*)>/gi, '<em$1>');
4599 html = html.replace(/<\/i>/gi, '</em>');
4600 html = html.replace(/<b([^>]*)>/gi, '<strong$1>');
4601 html = html.replace(/<\/b>/gi, '</strong>');
4603 html = html.replace(/<font/gi, '<font');
4604 html = html.replace(/<\/font>/gi, '</font>');
4605 html = html.replace(/<span/gi, '<span');
4606 html = html.replace(/<\/span>/gi, '</span>');
4607 html = html.replace(/<u/gi, '<u');
4608 html = html.replace(/\/u>/gi, '/u>');
4610 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>');
4611 html = html.replace(/\/ol>/gi, '/ol>');
4612 html = html.replace(/<li/gi, '<li');
4613 html = html.replace(/\/li>/gi, '/li>');
4615 //Handle the sudo A tags
4616 html = html.replace(new RegExp('<span ([^>]*) tag="a
" ([^>]*)>([^>]*)<\/span>', 'gi'), '<a $1 $2>$3</a>');
4618 //Safari only regexes
4619 if (this.browser.webkit) {
4620 //<DIV><SPAN class="Apple
-style
-span
" style="line
-height
: normal
;">Test THis</SPAN></DIV>
4621 html = html.replace(/Apple-style-span/gi, '');
4622 html = html.replace(/style="line
-height
: normal
;"/gi, '');
4625 //yui-tag-a yui-tag yui-non yui-img
4626 html = html.replace(/yui-tag-a/gi, '');
4627 html = html.replace(/yui-tag-span/gi, '');
4628 html = html.replace(/yui-tag/gi, '');
4629 html = html.replace(/yui-non/gi, '');
4630 html = html.replace(/yui-img/gi, '');
4631 html = html.replace(/ tag="span
"/gi, '');
4632 html = html.replace(/ class=""/gi, '');
4633 html = html.replace(/ class=" "/gi, '');
4634 html = html.replace(/ class=" "/gi, '');
4635 html = html.replace(/ target=""/gi, '');
4636 html = html.replace(/ title=""/gi, '');
4638 //Other string cleanup
4639 html = html.replace(/<br><li/gi, '<li');
4641 //html = html.replace(/<span >([^>]*)<\/span>/gi, '$1');
4642 //html = html.replace(/<div>([^>]*)<\/div>/gi, '$1');
4645 //Replace our backups with the real thing
4646 html = html.replace(/<YUI_BR>/g, '<br>');
4647 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img$1>');
4648 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>');
4649 html = html.replace(/<\/YUI_UL>/g, '<\/ul>');
4654 * @method clearEditorDoc
4655 * @description Clear the doc of the Editor
4657 clearEditorDoc: function() {
4658 this._getDoc().body.innerHTML = ' ';
4662 * @method _renderPanel
4663 * @description Renders the panel used for Editor Windows to the document so we can start using it..
4664 * @returns {<a href="YAHOO
.widget
.Panel
.html
">YAHOO.widget.Panel</a>}
4666 _renderPanel: function() {
4667 if (!YAHOO.widget.EditorInfo.panel) {
4668 var panel = new YAHOO.widget.Panel(this.EDITOR_PANEL_ID, {
4676 YAHOO.widget.EditorInfo.panel = panel;
4678 var panel = YAHOO.widget.EditorInfo.panel;
4680 this.set('panel', panel);
4682 this.get('panel').setBody('---');
4683 this.get('panel').setHeader(' ');
4684 this.get('panel').setFooter(' ');
4685 if (this.DOMReady) {
4686 this.get('panel').render(document.body);
4687 Dom.addClass(this.get('panel').element, 'yui-editor-panel');
4689 Event.onDOMReady(function() {
4690 this.get('panel').render(document.body);
4691 Dom.addClass(this.get('panel').element, 'yui-editor-panel');
4694 this.get('panel').showEvent.subscribe(function() {
4695 YAHOO.util.Dom.setStyle(this.element, 'display', 'block');
4697 return this.get('panel');
4700 * @method openWindow
4701 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO
.widget
.EditorWindow
.html
">YAHOO.widget.EditorWindow</a> instance
4702 * @description Opens a new "window
/panel
"
4704 openWindow: function(win) {
4705 this.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open..
4706 Event.addListener(document, 'keypress', this._closeWindow, this, true);
4707 if (YAHOO.widget.EditorInfo.window.win && YAHOO.widget.EditorInfo.window.scope) {
4708 YAHOO.widget.EditorInfo.window.scope.closeWindow.call(YAHOO.widget.EditorInfo.window.scope);
4710 YAHOO.widget.EditorInfo.window.win = win;
4711 YAHOO.widget.EditorInfo.window.scope = this;
4714 xy = Dom.getXY(this.currentElement[0]),
4715 elXY = Dom.getXY(this.get('iframe').get('element')),
4716 panel = this.get('panel'),
4717 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
4718 wWidth = (parseInt(win.attrs.width) / 2),
4721 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel });
4723 body = document.createElement('div');
4724 body.className = this.CLASS_PREFIX + '-body-cont';
4726 var _note = document.createElement('h3');
4727 _note.className = 'yui-editor-skipheader';
4728 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE;
4729 body.appendChild(_note);
4730 form = document.createElement('form');
4731 form.setAttribute('method', 'GET');
4732 var windowName = win.name;
4733 Event.addListener(form, 'submit', function(ev) {
4734 var evName = 'window' + windowName + 'Submit';
4735 self.fireEvent(evName, { type: evName, target: this });
4736 Event.stopEvent(ev);
4738 body.appendChild(form);
4740 Dom.setStyle(panel.element.firstChild, 'width', win.attrs.width);
4741 if (Lang.isObject(win.body)) { //Assume it's a reference
4742 form.appendChild(win.body);
4743 } else { //Assume it's a string
4744 var _tmp = document.createElement('div');
4745 _tmp.innerHTML = win.body;
4746 form.appendChild(_tmp);
4748 var _close = document.createElement('span');
4749 _close.innerHTML = 'X';
4750 _close.title = this.STR_CLOSE_WINDOW;
4751 _close.className = 'close';
4752 Event.addListener(_close, 'click', function() {
4755 var _knob = document.createElement('span');
4756 _knob.innerHTML = '^';
4757 _knob.className = 'knob';
4760 var _header = document.createElement('h3');
4761 _header.innerHTML = win.header;
4763 panel.cfg.setProperty('width', win.attrs.width);
4764 panel.setHeader(' '); //Clear the current header
4765 panel.appendToHeader(_header);
4766 _header.appendChild(_close);
4767 _header.appendChild(_knob);
4768 panel.setBody(' '); //Clear the current body
4769 panel.setFooter(' '); //Clear the current footer
4770 if (win.footer != null) {
4771 panel.setFooter(win.footer);
4773 panel.appendToBody(body); //Append the new DOM node to it
4774 panel.showEvent.subscribe(function() {
4775 Event.addListener(panel.element, 'click', function(ev) {
4776 Event.stopPropagation(ev);
4779 panel.hideEvent.subscribe(function() {
4780 this.currentWindow = null;
4781 var evName = 'window' + windowName + 'Close';
4782 this.fireEvent(evName, { type: evName, target: this });
4785 this.currentWindow = win;
4786 this.moveWindow(true);
4788 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel });
4791 * @method moveWindow
4792 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.)
4793 * @description Realign the window with the currentElement and reposition the knob above the panel.
4795 moveWindow: function(force) {
4796 if (!this.currentWindow) {
4799 var win = this.currentWindow,
4800 xy = Dom.getXY(this.currentElement[0]),
4801 elXY = Dom.getXY(this.get('iframe').get('element')),
4802 panel = this.get('panel'),
4803 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)],
4804 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])],
4805 wWidth = (parseInt(win.attrs.width) / 2),
4807 orgXY = panel.cfg.getProperty('xy'),
4810 newXY[0] = ((newXY[0] - wWidth) + 20);
4811 //Account for the Scroll bars in a scrolled editor window.
4812 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc());
4813 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc());
4817 if (this.currentElement[0].tagName && (this.currentElement[0].tagName.toLowerCase() == 'img')) {
4818 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) {
4819 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size
4820 newXY[1] = (newXY[1] + 75); //Placeholder sizea
4822 var w = parseInt(this.currentElement[0].width);
4823 var h = parseInt(this.currentElement[0].height);
4824 newXY[0] = (newXY[0] + (w / 2));
4825 newXY[1] = (newXY[1] + h);
4827 newXY[1] = newXY[1] + 15;
4829 if (Dom.getStyle(this.currentElement[0], 'fontSize').indexOf('px') != -1) {
4830 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize')) + 5;
4832 newXY[1] = newXY[1] + 20;
4835 if (newXY[0] < elXY[0]) {
4836 newXY[0] = elXY[0] + 5;
4840 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth))) {
4841 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth)) - (wWidth * 2) - 5);
4846 var xDiff = (newXY[0] - orgXY[0]);
4847 var yDiff = (newXY[1] - orgXY[1]);
4853 //Convert negative numbers to positive so we can get the difference in distance
4854 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff);
4855 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff);
4857 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)
4861 if (this.currentElement[0].width) {
4862 elW = (parseInt(this.currentElement[0].width) / 2);
4865 var leftOffset = xy[0] + elXY[0] + elW;
4866 _knobLeft = leftOffset - newXY[0];
4867 //Check to see if the knob will go off either side & reposition it
4868 if (_knobLeft > (parseInt(win.attrs.width) - 40)) {
4869 _knobLeft = parseInt(win.attrs.width) - 40;
4870 } else if (_knobLeft < 40) {
4873 if (isNaN(_knobLeft)) {
4878 _knob.style.left = _knobLeft + 'px';
4880 if (this.get('animate')) {
4881 Dom.setStyle(panel.element, 'opacity', '0');
4882 var anim = new YAHOO.util.Anim(panel.element, {
4887 }, .1, YAHOO.util.Easing.easeOut);
4888 panel.cfg.setProperty('xy', newXY);
4889 anim.onComplete.subscribe(function() {
4890 if (this.browser.ie) {
4891 panel.element.style.filter = 'none';
4896 panel.cfg.setProperty('xy', newXY);
4899 if (this.get('animate')) {
4900 var anim = new YAHOO.util.Anim(panel.element, {}, .5, YAHOO.util.Easing.easeOut);
4909 anim.onComplete.subscribe(function() {
4910 panel.cfg.setProperty('xy', newXY);
4912 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed ..
4913 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, .5, YAHOO.util.Easing.easeOut)
4915 var _knobAnim = new YAHOO.util.Anim(_knob, {
4919 }, .75, YAHOO.util.Easing.easeOut);
4921 iframeAnim.animate();
4922 _knobAnim.animate();
4924 _knob.style.left = _knobLeft + 'px';
4925 panel.cfg.setProperty('xy', newXY);
4932 * @method _closeWindow
4933 * @description Close the currently open EditorWindow with the Escape key.
4934 * @param {Event} ev The keypress Event that we are trapping
4936 _closeWindow: function(ev) {
4937 if (ev.keyCode == 27) {
4938 if (this.currentWindow) {
4944 * @method closeWindow
4945 * @description Close the currently open EditorWindow.
4947 closeWindow: function() {
4948 YAHOO.widget.EditorInfo.window = {};
4949 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow });
4950 this.currentWindow = null;
4951 this.get('panel').hide();
4952 this.get('panel').cfg.setProperty('xy', [-900,-900]);
4953 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel
4954 this.unsubscribeAll('afterExecCommand');
4955 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed
4956 this._focusWindow();
4957 Event.removeListener(document, 'keypress', this._closeWindow);
4961 * @description Destroys the editor, all of it's elements and objects.
4964 destroy: function() {
4966 this.toolbar.destroy();
4967 Dom.setStyle(this.get('textarea'), 'display', 'block');
4968 var textArea = this.get('textarea');
4969 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element'));
4970 this.get('element_cont').get('element').innerHTML = '';
4971 //Brutal Object Destroy
4972 for (var i in this) {
4973 if (Lang.hasOwnProperty(this, i)) {
4981 * @description Returns a string representing the editor.
4984 toString: function() {
4986 if (this.get && this.get('element_cont')) {
4987 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
4994 * @event toolbarLoaded
4995 * @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.
4996 * @type YAHOO.util.CustomEvent
4999 * @event afterRender
5000 * @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.
5001 * @type YAHOO.util.CustomEvent
5004 * @event editorContentLoaded
5005 * @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.
5006 * @type YAHOO.util.CustomEvent
5009 * @event editorMouseUp
5010 * @param {Event} ev The DOM Event that occured
5011 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5012 * @type YAHOO.util.CustomEvent
5015 * @event editorMouseDown
5016 * @param {Event} ev The DOM Event that occured
5017 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5018 * @type YAHOO.util.CustomEvent
5021 * @event editorDoubleClick
5022 * @param {Event} ev The DOM Event that occured
5023 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5024 * @type YAHOO.util.CustomEvent
5027 * @event editorKeyUp
5028 * @param {Event} ev The DOM Event that occured
5029 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5030 * @type YAHOO.util.CustomEvent
5033 * @event editorKeyPress
5034 * @param {Event} ev The DOM Event that occured
5035 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5036 * @type YAHOO.util.CustomEvent
5039 * @event editorKeyDown
5040 * @param {Event} ev The DOM Event that occured
5041 * @description Passed through HTML Event. See <a href="YAHOO
.util
.Element
.html
#addListener
">Element.addListener</a> for more information on listening for this event.
5042 * @type YAHOO.util.CustomEvent
5045 * @event beforeNodeChange
5046 * @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.
5047 * @type YAHOO.util.CustomEvent
5050 * @event afterNodeChange
5051 * @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.
5052 * @type YAHOO.util.CustomEvent
5055 * @event beforeExecCommand
5056 * @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.
5057 * @type YAHOO.util.CustomEvent
5060 * @event afterExecCommand
5061 * @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.
5062 * @type YAHOO.util.CustomEvent
5065 * @event beforeOpenWindow
5066 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">EditorWindow</a>} win The EditorWindow object
5067 * @param {Overlay} panel The Overlay object that is used to create the window.
5068 * @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.
5069 * @type YAHOO.util.CustomEvent
5072 * @event afterOpenWindow
5073 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">EditorWindow</a>} win The EditorWindow object
5074 * @param {Overlay} panel The Overlay object that is used to create the window.
5075 * @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.
5076 * @type YAHOO.util.CustomEvent
5079 * @event closeWindow
5080 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">EditorWindow</a>} win The EditorWindow object
5081 * @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.
5082 * @type YAHOO.util.CustomEvent
5085 * @event windowCMDOpen
5086 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">EditorWindow</a>} win The EditorWindow object
5087 * @param {Overlay} panel The Overlay object that is used to create the window.
5088 * @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.
5089 * @type YAHOO.util.CustomEvent
5092 * @event windowCMDClose
5093 * @param {<a href="YAHOO
.widget
.EditorWindow
.html
">EditorWindow</a>} win The EditorWindow object
5094 * @param {Overlay} panel The Overlay object that is used to create the window.
5095 * @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.
5096 * @type YAHOO.util.CustomEvent
5100 * @description Singleton object used to track the open window objects and panels across the various open editors
5104 YAHOO.widget.EditorInfo = {
5108 * @description A reference to the currently open window object in any editor on the page.
5109 * @type Object <a href="YAHOO
.widget
.EditorWindow
.html
">YAHOO.widget.EditorWindow</a>
5115 * @description A reference to the currently open panel in any editor on the page.
5116 * @type Object <a href="YAHOO
.widget
.Panel
.html
">YAHOO.widget.Panel</a>
5122 * @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.
5123 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor.
5124 * @class EditorWindow
5125 * @param {String} name The name of the window.
5126 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width
5128 YAHOO.widget.EditorWindow = function(name, attrs) {
5132 * @description A unique name for the window
5134 this.name = name.replace(' ', '_');
5138 * @description The window attributes
5143 YAHOO.widget.EditorWindow.prototype = {
5147 * @description Holds a cache of the DOM for the window so we only have to build it once..
5153 * @description Holder for the header of the window, used in Editor.openWindow
5159 * @description Holder for the body of the window, used in Editor.openWindow
5165 * @description Holder for the footer of the window, used in Editor.openWindow
5170 * @description Sets the header for the window.
5171 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header.
5173 setHeader: function(str) {
5178 * @description Sets the body for the window.
5179 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body.
5181 setBody: function(str) {
5186 * @description Sets the footer for the window.
5187 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer.
5189 setFooter: function(str) {
5194 * @description Returns a string representing the EditorWindow.
5197 toString: function() {
5198 return 'Editor Window (' + this.name + ')';
5205 YAHOO.register("editor
", YAHOO.widget.Editor, {version: "2.3.0", build: "442"});