MDL-11517 reserved word MOD used in table alias in questions backup code
[moodle-pu.git] / lib / yui / editor / editor-beta-debug.js
blob266453f804a8de7b5953fd44b7b351b350e19293
1 /*
2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.3.0
6 */
7 /*
8 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
9 Code licensed under the BSD License:
10 http://developer.yahoo.net/yui/license.txt
12 /**
13 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p>
14 * @class Toolbar
15 * @namespace YAHOO.widget
16 * @requires yahoo, dom, element, event
17 * @optional container, menu, button, dragdrop
18 * @beta
20 (function() {
21 /**
22 * @private
23 **/
24 var Dom = YAHOO.util.Dom,
25 Event = YAHOO.util.Event,
26 Lang = YAHOO.lang;
28 /**
29 * Provides a rich toolbar widget based on the button and menu widgets
30 * @constructor
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) {
39 var attrs = el;
41 var local_attrs = (attrs || {});
43 var oConfig = {
44 element: null,
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);
87 /**
88 * @method _addMenuClasses
89 * @private
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, '-')));
106 this._setWidth();
109 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, {
110 /**
111 * @property dd
112 * @description The DragDrop instance associated with the Toolbar
113 * @type Object
115 dd: null,
116 /**
117 * @property _colorData
118 * @description Object reference containing colors hex and text values.
119 * @type Object
121 _colorData: {
122 /* {{{ _colorData */
123 '#111111': 'Obsidian',
124 '#2D2D2D': 'Dark Gray',
125 '#434343': 'Shale',
126 '#5B5B5B': 'Flint',
127 '#737373': 'Gray',
128 '#8B8B8B': 'Concrete',
129 '#A2A2A2': 'Gray',
130 '#B9B9B9': 'Titanium',
131 '#000000': 'Black',
132 '#D0D0D0': 'Light Gray',
133 '#E6E6E6': 'Silver',
134 '#FFFFFF': 'White',
135 '#BFBF00': 'Pumpkin',
136 '#FFFF00': 'Yellow',
137 '#FFFF40': 'Banana',
138 '#FFFF80': 'Pale Yellow',
139 '#FFFFBF': 'Butter',
140 '#525330': 'Raw Siena',
141 '#898A49': 'Mildew',
142 '#AEA945': 'Olive',
143 '#7F7F00': 'Paprika',
144 '#C3BE71': 'Earth',
145 '#E0DCAA': 'Khaki',
146 '#FCFAE1': 'Cream',
147 '#60BF00': 'Cactus',
148 '#80FF00': 'Chartreuse',
149 '#A0FF40': 'Green',
150 '#C0FF80': 'Pale Lime',
151 '#DFFFBF': 'Light Mint',
152 '#3B5738': 'Green',
153 '#668F5A': 'Lime Gray',
154 '#7F9757': 'Yellow',
155 '#407F00': 'Clover',
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',
165 '#438059': 'Moss',
166 '#7FA37C': 'Medium Green',
167 '#007F40': 'Pine',
168 '#8DAE94': 'Yellow Gray Green',
169 '#ACC6B5': 'Aqua Lung',
170 '#DDEBE2': 'Sea Vapor',
171 '#00BFBF': 'Fog',
172 '#00FFFF': 'Cyan',
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',
188 '#1B2C48': 'Navy',
189 '#385376': 'Biscay',
190 '#57708F': 'Dusty Blue',
191 '#00407F': 'Sea Blue',
192 '#7792AC': 'Sky Blue Gray',
193 '#A8BED1': 'Morning Sky',
194 '#DEEBF6': 'Vapor',
195 '#0000BF': 'Deep Blue',
196 '#0000FF': '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',
215 '#40007F': 'Violet',
216 '#726284': 'Smoky Violet',
217 '#9E8FA9': 'Slate Gray',
218 '#DCD1DF': 'Violet White',
219 '#BF00BF': 'Royal Violet',
220 '#FF00FF': 'Fuchsia',
221 '#FF40FF': 'Magenta',
222 '#FF80FF': 'Orchid',
223 '#FFBFFF': 'Pale Magenta',
224 '#4A234A': 'Dark Purple',
225 '#794A72': 'Medium Purple',
226 '#936386': 'Cool Granite',
227 '#7F007F': 'Purple',
228 '#9D7292': 'Purple Moon',
229 '#C0A0B6': 'Pale Purple',
230 '#ECDAE5': 'Pink Cloud',
231 '#BF005F': 'Hot Pink',
232 '#FF007F': 'Deep Pink',
233 '#FF409F': 'Grape',
234 '#FF80BF': 'Electric Pink',
235 '#FFBFDF': 'Pink',
236 '#451528': 'Purple Red',
237 '#823857': 'Purple Dino',
238 '#A94A76': 'Purple Gray',
239 '#7F003F': 'Rose',
240 '#BC6F95': 'Antique Mauve',
241 '#D8A5BB': 'Cool Marble',
242 '#F7DDE9': 'Pink Granite',
243 '#C00000': 'Apple',
244 '#FF0000': 'Fire Truck',
245 '#FF4040': 'Pale Red',
246 '#FF8080': 'Salmon',
247 '#FFC0C0': 'Warm Pink',
248 '#441415': 'Sepia',
249 '#82393C': 'Rust',
250 '#AA4D4E': 'Brick',
251 '#800000': 'Brick Red',
252 '#BC6E6E': 'Mauve',
253 '#D8A3A4': 'Shrimp Pink',
254 '#F8DDDD': 'Shell Pink',
255 '#BF5F00': 'Dark Orange',
256 '#FF7F00': 'Orange',
257 '#FF9F40': 'Grapefruit',
258 '#FFBF80': 'Canteloupe',
259 '#FFDFBF': 'Wax',
260 '#482C1B': 'Dark Brick',
261 '#855A40': 'Dirt',
262 '#B27C51': 'Tan',
263 '#7F3F00': 'Nutmeg',
264 '#C49B71': 'Mustard',
265 '#E1C4A8': 'Pale Tan',
266 '#FDEEE0': 'Marble'
267 /* }}} */
269 /**
270 * @property _colorPicker
271 * @description The HTML Element containing the colorPicker
272 * @type HTMLElement
274 _colorPicker: null,
275 /**
276 * @property STR_COLLAPSE
277 * @description String for Toolbar Collapse Button
278 * @type String
280 STR_COLLAPSE: 'Collapse Toolbar',
281 /**
282 * @property STR_SPIN_LABEL
283 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute
284 * @type String
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.',
287 /**
288 * @property STR_SPIN_UP
289 * @description String for spinbutton up
290 * @type String
292 STR_SPIN_UP: 'Click to increase the value of this input',
293 /**
294 * @property STR_SPIN_DOWN
295 * @description String for spinbutton down
296 * @type String
298 STR_SPIN_DOWN: 'Click to decrease the value of this input',
299 /**
300 * @property _titlebar
301 * @description Object reference to the titlebar
302 * @type HTMLElement
304 _titlebar: null,
305 /**
306 * @property _disabled
307 * @description Object to track button status when enabling/disabling the toolbar
308 * @type Object
310 _disabled: null,
311 /**
312 * @property browser
313 * @description Standard browser detection
314 * @type Object
316 browser: YAHOO.env.ua,
318 * @protected
319 * @property _buttonList
320 * @description Internal property list of current buttons in the toolbar
321 * @type Array
323 _buttonList: null,
325 * @protected
326 * @property _buttonGroupList
327 * @description Internal property list of current button groups in the toolbar
328 * @type Array
330 _buttonGroupList: null,
332 * @protected
333 * @property _sep
334 * @description Internal reference to the separator HTML Element for cloning
335 * @type HTMLElement
337 _sep: null,
339 * @protected
340 * @property _sepCount
341 * @description Internal refernce for counting separators, so we can give them a useful class name for styling
342 * @type Number
344 _sepCount: null,
346 * @protected
347 * @property draghandle
348 * @type HTMLElement
350 _dragHandle: null,
352 * @protected
353 * @property _toolbarConfigs
354 * @type Object
356 _toolbarConfigs: {
357 renderer: true
360 * @protected
361 * @property CLASS_CONTAINER
362 * @description Default CSS class to apply to the toolbar container element
363 * @type String
365 CLASS_CONTAINER: 'yui-toolbar-container',
367 * @protected
368 * @property CLASS_DRAGHANDLE
369 * @description Default CSS class to apply to the toolbar's drag handle element
370 * @type String
372 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle',
374 * @protected
375 * @property CLASS_SEPARATOR
376 * @description Default CSS class to apply to all separators in the toolbar
377 * @type String
379 CLASS_SEPARATOR: 'yui-toolbar-separator',
381 * @protected
382 * @property CLASS_DISABLED
383 * @description Default CSS class to apply when the toolbar is disabled
384 * @type String
386 CLASS_DISABLED: 'yui-toolbar-disabled',
388 * @protected
389 * @property CLASS_PREFIX
390 * @description Default prefix for dynamically created class names
391 * @type String
393 CLASS_PREFIX: 'yui-toolbar',
394 /**
395 * @method init
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
404 * the toolbar.
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);
415 * @config buttons
416 * @description Object specifying the buttons to include in the toolbar
417 * Example:
418 * <code><pre>
420 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' },
421 * { type: 'separator' },
422 * { id: 'b4', type: 'menu', label: 'Align', value: 'align',
423 * menu: [
424 * { text: "Left", value: 'alignleft' },
425 * { text: "Center", value: 'aligncenter' },
426 * { text: "Right", value: 'alignright' }
430 * </pre></code>
431 * @type Object
434 this.setAttributeConfig('buttons', {
435 value: [],
436 writeOnce: true,
437 method: function(data) {
438 for (var i in data) {
439 if (Lang.hasOwnProperty(data, i)) {
440 if (data[i].type == 'separator') {
441 this.addSeparator();
442 } else if (data[i].group != undefined) {
443 this.addButtonGroup(data[i]);
444 } else {
445 this.addButton(data[i]);
453 * @config disabled
454 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on.
455 * @default false
456 * @type Boolean
458 this.setAttributeConfig('disabled', {
459 value: false,
460 method: function(disabled) {
461 if (!Lang.isObject(this._disabled)) {
462 this._disabled = {};
464 if (disabled) {
465 this.addClass(this.CLASS_DISABLED);
466 this.set('draggable', false);
467 } else {
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++) {
476 if (disabled) {
477 //If it's already disabled, flag it
478 if (this._buttonList[i].get('disabled')) {
479 this._disabled[i] = true;
480 } else {
481 this._disabled[i] = null;
483 this.disableButton(this._buttonList[i].get('id'));
484 } else {
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]) {
489 _check = true;
491 if (!_check) {
492 this.enableButton(_button.get('id'));
500 * @config grouplabels
501 * @description Boolean indicating if the toolbar should show the group label's text string.
502 * @default true
503 * @type Boolean
505 this.setAttributeConfig('grouplabels', {
506 value: true,
507 writeOnce: true
511 * @config cont
512 * @description Boolean indicating if the toolbar should show the group label's text string.
513 * @default true
514 * @type Boolean
516 this.setAttributeConfig('cont', {
517 value: attr.cont,
518 readOnly: true
522 * @config collapse
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
525 * @default false
526 * @type Boolean
528 this.setAttributeConfig('collapse', {
529 value: false
532 * @config titlebar
533 * @description Boolean indicating if the toolbar should have a titlebar. If
534 * passed a string, it will use that as the titlebar text
535 * @default false
536 * @type Boolean or String
538 this.setAttributeConfig('titlebar', {
539 value: false,
540 method: function(titlebar) {
541 if (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');
549 h2.tabIndex = '-1';
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 });
565 } else {
566 Dom.setStyle(this.get('cont'), 'display', 'none');
567 Dom.addClass(collapse, 'collapsed');
568 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this });
570 }, this, true);
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'));
579 } else {
580 this.appendChild(this._titlebar);
582 } else if (this._titlebar) {
583 if (this._titlebar && this._titlebar.parentNode) {
584 this._titlebar.parentNode.removeChild(this._titlebar);
592 * @config draggable
593 * @description Boolean indicating if the toolbar should be draggable.
594 * @default false
595 * @type Boolean
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);
613 } else {
614 this.get('cont').appendChild(this._dragHandle);
617 * @property dd
618 * @description The DragDrop instance associated with the Toolbar
619 * @type Object
621 this.dd = new YAHOO.util.DD(this.get('id'));
622 this.dd.setHandleElId(this._dragHandle.id);
625 } else {
626 YAHOO.log('Dragging disabled', 'info', 'Toolbar');
627 if (this._dragHandle) {
628 this._dragHandle.parentNode.removeChild(this._dragHandle);
629 this._dragHandle = null;
630 this.dd = null;
633 if (this._titlebar) {
634 if (draggable) {
635 this.dd = new YAHOO.util.DD(this.get('id'));
636 this.dd.setHandleElId(this._titlebar);
637 Dom.addClass(this._titlebar, 'draggable');
638 } else {
639 Dom.removeClass(this._titlebar, 'draggable');
640 if (this.dd) {
641 this.dd.unreg();
642 this.dd = null;
647 validator: function(value) {
648 var ret = true;
649 if (!YAHOO.util.DD) {
650 ret = false;
652 return ret;
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];
665 return false;
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');
684 div.appendChild(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');
694 ul.appendChild(li);
695 if ((oGroup.buttons[i].type != undefined) && oGroup.buttons[i].type == 'separator') {
696 this.addSeparator(li);
697 } else {
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);
719 * @method addButton
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];
728 return false;
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)) {
744 var funcObject = {
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);
754 scope: this
756 oButton.menu[i].onclick = funcObject;
761 var _oButton = {};
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);
778 if (_oButton.menu) {
779 if (oButton.menu instanceof YAHOO.widget.Overlay) {
780 oButton.menu.showEvent.subscribe(function() {
781 this._button = _oButton;
783 } else {
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);
799 if (!oButton.id) {
800 oButton.id = tmp.get('id');
802 YAHOO.log('Button created (' + oButton.type + ')', 'info', 'Toolbar');
804 if (after) {
805 var el = tmp.get('element');
806 var nextSib = null;
807 if (after.get) {
808 nextSib = after.get('element').nextSibling;
809 } else if (after.nextSibling) {
810 nextSib = after.nextSibling;
812 if (nextSib) {
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;
823 a.href = '#';
824 Event.on(a, 'click', function(ev) {
825 Event.stopEvent(ev);
827 tmp._button.parentNode.replaceChild(a, tmp._button);
828 tmp._button = a;
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) {
845 var exec = true;
846 if (ev.keyCode && (ev.keyCode == 9)) {
847 exec = false;
849 if (exec) {
850 this._colorPicker._button = oButton.value;
851 var menuEL = tmp.getMenu().element;
852 if (menuEL.style.visibility == 'hidden') {
853 tmp.getMenu().show();
854 } else {
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);
868 }, oButton, this);
869 } else {
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);
877 var self = this;
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');
881 var oMenu = args[1];
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);
889 tmp._hideMenu();
890 return false;
892 tmp.getMenu().clickEvent.subscribe(function(ev, args) {
893 YAHOO.log('clickEvent', 'warn', 'Toolbar');
894 YAHOO.util.Event.stopEvent(args[0]);
897 } else {
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);
914 }, oButton, this);
916 tmp.on('focusout', function(ev) {
917 YAHOO.util.Event.stopEvent(ev);
918 }, oButton, this);
919 tmp.on('click', function(ev) {
920 YAHOO.util.Event.stopEvent(ev);
921 }, oButton, this);
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() {
927 return true;
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);
941 return oButton;
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];
952 return false;
954 var sepCont = ((cont) ? cont : this.get('cont'));
955 if (!this.get('element')) {
956 this._queue[this._queue.length] = ['addSeparator', arguments];
957 return false;
959 if (this._sepCount == null) {
960 this._sepCount = 0;
962 if (!this._sep) {
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);
970 this._sepCount++;
971 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount);
972 if (after) {
973 var nextSib = null;
974 if (after.get) {
975 nextSib = after.get('element').nextSibling;
976 } else if (after.nextSibling) {
977 nextSib = after.nextSibling;
978 } else {
979 nextSib = after;
981 if (nextSib) {
982 if (nextSib == after) {
983 nextSib.parentNode.appendChild(_sep);
984 } else {
985 nextSib.parentNode.insertBefore(_sep, nextSib);
988 } else {
989 sepCont.appendChild(_sep);
991 return _sep;
994 * @method _createColorPicker
995 * @private
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);
1009 }, this, true);
1011 this._colorPicker = picker;
1013 var html = '';
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;
1030 }, this, true);
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();
1044 }, this, true);
1047 * @method _resetColorPicker
1048 * @private
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
1059 * @private
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';
1085 }, this, true);
1086 return _oButton;
1089 * @private
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');
1097 var self = this,
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');
1102 _b1.href = '#';
1103 _b2.href = '#';
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);
1123 return value;
1126 var br = this.browser;
1127 var tbar = false;
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'));
1137 value++;
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
1144 //_button.focus();
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'));
1154 value--;
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
1162 //_button.focus();
1164 self._buttonClick(ev, oButton);
1168 var _intKeyUp = function(ev) {
1169 if (ev.keyCode == 38) {
1170 _intUp(ev);
1171 } else if (ev.keyCode == 40) {
1172 _intDown(ev);
1173 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key
1174 _intUp(ev);
1175 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key
1176 _intDown(ev);
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);
1187 }, this, true);
1188 Event.on(_b2, 'mousedown', function(ev) {
1189 Event.stopEvent(ev);
1190 }, this, true);
1191 Event.on(_b1, 'click', _intUp, this, true);
1192 Event.on(_b2, 'click', _intDown, this, true);
1195 * @protected
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) {
1202 var doEvent = true;
1204 if (ev && ev.type == 'keypress') {
1205 if (ev.keyCode == 9) {
1206 doEvent = false;
1207 } else if ((ev.keyCode == 13) || (ev.keyCode == 0) || (ev.keyCode == 32)) {
1208 } else {
1209 doEvent = false;
1213 if (doEvent) {
1214 if (info.value) {
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 });
1219 if (info.menucmd) {
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;
1233 break;
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);
1241 } else {
1242 _items[m].cfg.setProperty('checked', false);
1247 if (ev) {
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];
1264 return false;
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);
1289 } else {
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);
1302 return false;
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];
1313 } else {
1314 return false;
1318 * @method getButtons
1319 * @description Returns an array of buttons in the current toolbar
1320 * @return {Array}
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.
1329 * @return {Boolean}
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);
1340 } else {
1341 return false;
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.
1348 * @return {Boolean}
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);
1359 } else {
1360 return 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.
1367 * @return {Boolean}
1369 selectButton: function(button, value) {
1370 if (button) {
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');
1380 if (value) {
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>');
1386 } else {
1387 _items[m].cfg.setProperty('checked', false);
1391 } else {
1392 return 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.
1400 * @return {Boolean}
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');
1413 } else {
1414 return false;
1418 * @method deselectAllButtons
1419 * @description Deselects all buttons in the toolbar.
1420 * @return {Boolean}
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.
1432 * @return {Boolean}
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');
1443 button.destroy();
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;
1451 } else {
1452 return false;
1457 * @method destroy
1458 * @description Destroys the toolbar, all of it's elements and objects.
1459 * @return {Boolean}
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)) {
1467 this[i] = null;
1470 return true;
1473 * @method toString
1474 * @description Returns a string representing the toolbar.
1475 * @return {String}
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
1488 * @event valueClick
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.
1492 * Example:
1493 * <code><pre>
1494 * buttons : [
1495 * { type: 'button', value: 'test', value: 'testButton' }
1496 * ]</pre>
1497 * </code>
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
1512 })();
1514 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
1515 Code licensed under the BSD License:
1516 http://developer.yahoo.net/yui/license.txt
1519 * @module editor
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
1524 * @beta
1527 (function() {
1528 var Dom = YAHOO.util.Dom,
1529 Event = YAHOO.util.Event,
1530 Lang = YAHOO.lang,
1531 DD = YAHOO.util.DD,
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.
1537 * @constructor
1538 * @class Editor
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');
1547 var oConfig = {
1548 element: null,
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;
1582 this.fireQueue();
1583 }, this, 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
1592 * @returns {String}
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;
1608 var config = {
1609 border: '0',
1610 frameBorder: '0',
1611 marginWidth: '0',
1612 marginHeight: '0',
1613 leftMargin: '0',
1614 topMargin: '0',
1615 allowTransparency: 'true',
1616 width: '100%',
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');
1627 return ifrm;
1630 YAHOO.extend(YAHOO.widget.Editor, YAHOO.util.Element, {
1632 * @property DOMReady
1633 * @private
1634 * @description Flag to determine if DOM is ready or not
1635 * @type Boolean
1637 DOMReady: null,
1639 * @property _selection
1640 * @private
1641 * @description Holder for caching iframe selections
1642 * @type Object
1644 _selection: null,
1646 * @property _mask
1647 * @private
1648 * @description DOM Element holder for the editor Mask when disabled
1649 * @type Object
1651 _mask: null,
1653 * @property _showingHiddenElements
1654 * @private
1655 * @description Status of the hidden elements button
1656 * @type Boolean
1658 _showingHiddenElements: null,
1660 * @property currentWindow
1661 * @description A reference to the currently open EditorWindow
1662 * @type Object
1664 currentWindow: null,
1666 * @property currentEvent
1667 * @description A reference to the current editor event
1668 * @type Event
1670 currentEvent: null,
1672 * @property operaEvent
1673 * @private
1674 * @description setTimeout holder for Opera and Image DoubleClick event..
1675 * @type Object
1677 operaEvent: null,
1679 * @property currentFont
1680 * @description A reference to the last font selected from the Toolbar
1681 * @type HTMLElement
1683 currentFont: null,
1685 * @property currentElement
1686 * @description A reference to the current working element in the editor
1687 * @type Array
1689 currentElement: [],
1691 * @property dompath
1692 * @description A reference to the dompath container for writing the current working dom path to.
1693 * @type HTMLElement
1695 dompath: null,
1697 * @property beforeElement
1698 * @description A reference to the H2 placed before the editor for Accessibilty.
1699 * @type HTMLElement
1701 beforeElement: null,
1703 * @property afterElement
1704 * @description A reference to the H2 placed after the editor for Accessibilty.
1705 * @type HTMLElement
1707 afterElement: null,
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.
1711 * @type Object
1713 invalidHTML: {
1714 form: true,
1715 input: true,
1716 button: true,
1717 select: true,
1718 link: true,
1719 html: true,
1720 body: true,
1721 script: true,
1722 style: true,
1723 textarea: true
1726 * @property toolbar
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>
1730 toolbar: null,
1732 * @private
1733 * @property _contentTimer
1734 * @description setTimeout holder for documentReady check
1736 _contentTimer: null,
1738 * @private
1739 * @property _contentTimerCounter
1740 * @description Counter to check the number of times the body is polled for before giving up
1741 * @type Number
1743 _contentTimerCounter: 0,
1745 * @private
1746 * @property _disabled
1747 * @description The Toolbar items that should be disabled if there is no selection present in the editor.
1748 * @type Array
1750 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent', 'outdent' ],
1752 * @private
1753 * @property _alwaysDisabled
1754 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
1755 * @type Object
1757 _alwaysDisabled: { },
1759 * @private
1760 * @property _alwaysEnabled
1761 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
1762 * @type Object
1764 _alwaysEnabled: { hiddenelements: true },
1766 * @private
1767 * @property _semantic
1768 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
1769 * @type Object
1771 _semantic: { 'bold': true, 'italic' : true, 'underline' : true },
1773 * @private
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.
1776 * @type Object
1778 _tag2cmd: {
1779 'b': 'bold',
1780 'strong': 'bold',
1781 'i': 'italic',
1782 'em': 'italic',
1783 'u': 'underline',
1784 'blockquote': 'formatblock',
1785 'sup': 'superscript',
1786 'sub': 'subscript',
1787 'img': 'insertimage',
1788 'a' : 'createlink',
1789 'ul' : 'insertunorderedlist',
1790 'ol' : 'insertorderedlist',
1791 'indent' : 'indent',
1792 'outdent' : 'outdent'
1795 * @private
1796 * @method _getDoc
1797 * @description Get the Document of the IFRAME
1798 * @return {Object}
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) {
1802 var value = false;
1803 if (this.get) {
1804 if (this.get('iframe')) {
1805 if (this.get('iframe').get) {
1806 if (this.get('iframe').get('element')) {
1807 try {
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;
1813 } catch (e) {}
1818 return value;
1821 * @private
1822 * @method _getWindow
1823 * @description Get the Window of the IFRAME
1824 * @return {Object}
1826 _getWindow: function() {
1827 return this.get('iframe').get('element').contentWindow;
1830 * @private
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) {
1837 if (onLoad) {
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);
1846 } else {
1847 this._getSelection().setBaseAndExtent(this._getDoc().body, 1, this._getDoc().body, 1);
1848 this._getSelection().collapse(false);
1850 this._getWindow().focus();
1851 //Check for.webkit3
1852 if (this._getDoc().queryCommandEnabled('insertimage')) {
1853 this.browser.webkit3 = true;
1855 } else {
1856 this._getWindow().focus();
1860 * @private
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();
1868 var hasSel = false;
1870 //Internet Explorer
1871 if (this.browser.ie || this.browser.opera) {
1872 if (range.text) {
1873 hasSel = true;
1875 if (range.html) {
1876 hasSel = true;
1878 } else {
1879 if ((sel != '') && (sel != undefined)) {
1880 hasSel = true;
1883 return hasSel;
1886 * @private
1887 * @method _getSelection
1888 * @description Handles the different selection objects across the A-Grade list.
1889 * @returns {Object} Selection Object
1891 _getSelection: function() {
1892 var _sel = null;
1893 if (this._getDoc() && this._getWindow()) {
1894 if (this._getDoc().selection) {
1895 _sel = this._getDoc().selection;
1896 } else {
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;
1919 return _sel;
1922 * @private
1923 * @method _getRange
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();
1930 if (sel == null) {
1931 return null;
1934 if (this.browser.webkit && !sel.getRangeAt) {
1935 var _range = this._getDoc().createRange();
1936 try {
1937 _range.setStart(sel.anchorNode, sel.anchorOffset);
1938 _range.setEnd(sel.focusNode, sel.focusOffset);
1939 } catch (e) {
1940 _range = this._getWindow().getSelection()+'';
1942 return _range;
1945 if (this.browser.ie || this.browser.opera) {
1946 return sel.createRange();
1949 if (sel.rangeCount > 0) {
1950 return sel.getRangeAt(0);
1952 return null;
1955 * @private
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) {
1961 try {
1962 this._getDoc().designMode = state;
1963 } catch(e) { }
1966 * @private
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,
1973 _state = 'on';
1974 if (_dMode == 'on') {
1975 _state = 'off';
1977 this._setDesignMode(_state);
1978 return _state;
1981 * @private
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')) {
2004 var self = this;
2005 window.setTimeout(function() {
2006 self._writeDomPath.call(self);
2007 }, 150);
2011 * @private
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');
2022 return false;
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');
2033 this._initEditor();
2034 } else {
2035 var self = this;
2036 this._contentTimer = window.setTimeout(function() {
2037 self._checkLoaded.call(self);
2038 }, 20);
2042 * @private
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();
2062 * @private
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')) {
2069 case 'css':
2070 this._setEditorStyle(true);
2071 break;
2072 case 'default':
2073 this._setEditorStyle(false);
2074 break;
2075 case 'semantic':
2076 if (this._semantic[action]) {
2077 this._setEditorStyle(false);
2078 } else {
2079 this._setEditorStyle(true);
2081 break;
2085 * Set the editor to use CSS instead of HTML
2086 * @param {Booleen} stat True/False
2088 _setEditorStyle: function(stat) {
2089 try {
2090 this._getDoc().execCommand('useCSS', false, !stat);
2091 } catch (ex) {
2095 * @private
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;
2104 if (range) {
2105 elm = range.item ? range.item(0) : range.parentElement();
2106 if (elm == doc.body) {
2107 elm = null;
2110 } else {
2111 var sel = this._getSelection(),
2112 range = this._getRange(),
2113 elm = null;
2114 if (!sel || !range) {
2115 return null;
2117 if (sel != '') {
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')) {
2128 elm = null;
2131 if (!elm) {
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];
2143 //Safari Fix
2144 if (!elm) {
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)) {
2155 } else {
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) {
2169 elm = doc.body;
2171 if (elm && elm.tagName && elm.tagName.toLowerCase() == 'html') {
2172 //Safari sometimes gives us the HTML node back..
2173 elm = doc.body;
2176 return elm;
2179 * @private
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();
2186 var domPath = [];
2188 while (el!= null) {
2189 if (el.ownerDocument != this._getDoc()) {
2190 return false;
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") {
2198 break;
2201 el = el.parentNode;
2203 if (domPath.length == 0) {
2204 if (this._getDoc() && this._getDoc().body) {
2205 domPath[0] = this._getDoc().body;
2208 return domPath.reverse();
2211 * @private
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(),
2217 pathArr = [];
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')) {
2227 switch (tag) {
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')) {
2234 var pathStr = tag;
2235 if (tag == 'a') {
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
2240 } else {
2241 var classPath = ((path[i].className != '') ? '.' + path[i].className.replace(/ /g, '.') : '');
2242 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) {
2243 classPath = '';
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;
2260 * @private
2261 * @method _fixNodes
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');
2276 if (as.length) {
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]);
2289 as[i] = null;
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);
2300 } else {
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);
2312 } else {
2313 Dom.removeClass(fakeAs[i], this.CLASS_LOCAL_FILE);
2319 * @private
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);
2329 } else {
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'));
2337 * @private
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;
2349 * @private
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) {
2357 this.closeWindow();
2359 if (!this.browser.webkit) {
2360 this.nodeChange();
2364 * @private
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..
2374 * @browser Opera
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')) {
2379 this.nodeChange();
2380 var self = this;
2381 if (this.operaEvent) {
2382 clearTimeout(this.operaEvent);
2383 this.operaEvent = null;
2384 this._handleDoubleClick(ev);
2385 } else {
2386 this.operaEvent = window.setTimeout(function() {
2387 self.operaEvent = false;
2388 }, 200);
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);
2398 this.nodeChange();
2399 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev });
2402 * @private
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) {
2412 this.nodeChange();
2413 Event.stopEvent(ev);
2416 //this.nodeChange();
2417 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev });
2420 * @private
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 });
2437 this.nodeChange();
2438 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev });
2441 * @private
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
2450 case 38: //Up Arrow
2451 case 39: //Right Arrow
2452 case 40: //Down Arrow
2453 case 46: //Forward Delete
2454 case 8: //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) {
2458 this.closeWindow();
2460 this.nodeChange();
2461 break;
2463 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev });
2466 * @private
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 });
2476 * @private
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) {
2484 this.closeWindow();
2486 var doExec = false;
2487 var action = null;
2488 //if (ev.ctrlKey) {
2489 if (ev.shiftKey && ev.ctrlKey) {
2490 doExec = true;
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);
2497 doExec = false;
2499 break;
2500 case 27: //Focus After Element - Ctrl + Shift + Esc
2501 if (ev.shiftKey) {
2502 this.afterElement.focus();
2503 Event.stopEvent(ev);
2504 exec = false;
2506 break;
2507 case 219: //Left
2508 action = 'justifyleft';
2509 break;
2510 case 220: //Center
2511 action = 'justifycenter';
2512 break;
2513 case 221: //Right
2514 action = 'justifyright';
2515 break;
2516 case 76: //L
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 });
2521 doExec = false;
2523 break;
2524 case 66: //B
2525 action = 'bold';
2526 break;
2527 case 73: //I
2528 action = 'italic';
2529 break;
2530 case 85: //U
2531 action = 'underline';
2532 break;
2533 case 9: //Tab Key
2534 if (this.browser.safari) {
2535 this._getDoc().execCommand('inserttext', false, '\t');
2536 Event.stopEvent(ev);
2538 break;
2539 case 13:
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')) {
2545 if (_range) {
2546 _range.pasteHTML('<br>');
2547 _range.collapse(false);
2548 _range.select();
2550 Event.stopEvent(ev);
2554 if (doExec && action) {
2555 this.execCommand(action, null);
2556 Event.stopEvent(ev);
2557 this.nodeChange();
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() {
2566 this._fixNodes();
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;
2577 return false;
2578 } else {
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'));
2589 } else {
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();
2606 var olType = null;
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') {
2616 cmd = 'bold';
2618 if (path[i].style.fontStyle.toLowerCase() == 'italic') {
2619 cmd = 'italic';
2621 if (path[i].style.textDecoration.toLowerCase() == 'underline') {
2622 cmd = 'underline';
2624 if (tag == 'ol') {
2625 if (path[i].type) {
2626 olType = path[i].type;
2627 } else {
2628 olType = 'A';
2631 if (cmd) {
2632 if (!Lang.isArray(cmd)) {
2633 cmd = [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);
2641 //Handle Alignment
2642 switch (path[i].style.textAlign.toLowerCase()) {
2643 case 'left':
2644 case 'right':
2645 case 'center':
2646 case 'justify':
2647 var alignType = path[i].style.textAlign.toLowerCase();
2648 if (path[i].style.textAlign.toLowerCase() == 'justify') {
2649 alignType = 'full';
2651 var button = this.toolbar.getButtonByValue('justify' + alignType);
2652 this.toolbar.selectButton(button);
2653 this.toolbar.enableButton(button);
2654 break;
2656 //Handle Ordered List Drop Down - it will reset if olType is null
2657 //this._updateMenuChecked('insertorderedlist', olType);
2659 //After for loop
2661 //Reset Font Family and Size to the inital configs
2662 var fn_button = this.toolbar.getButtonByValue('fontname');
2663 if (fn_button) {
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');
2670 if (fs_button) {
2671 fs_button.set('label', fs_button._configs.label._initialConfig.value);
2674 var hd_button = this.toolbar.getButtonByValue('heading');
2675 if (hd_button) {
2676 hd_button.set('label', hd_button._configs.label._initialConfig.value);
2677 this._updateMenuChecked('heading', 'none');
2682 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this });
2685 * @private
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) {
2693 if (!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);
2710 * @private
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) {
2716 var value = '';
2717 var str = '';
2718 var cmd = ev.button.value;
2719 if (ev.button.menucmd) {
2720 value = cmd;
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;
2727 return false;
2728 } else {
2729 this.execCommand(cmd, value);
2731 Event.stopEvent(ev);
2734 * @private
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.
2750 * @type String
2752 EDITOR_PANEL_ID: 'yui-editor-panel',
2754 * @property SEP_DOMPATH
2755 * @description The value to place in between the Dom path items
2756 * @type String
2758 SEP_DOMPATH: '<',
2760 * @property STR_LEAVE_EDITOR
2761 * @description The accessibility string for the element after the iFrame
2762 * @type String
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
2768 * @type String
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
2774 * @type String
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
2780 * @type String
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
2786 * @type String
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.
2792 * @type String
2794 STR_IMAGE_HERE: 'Image Url Here',
2796 * @property STR_IMAGE_PROP_TITLE
2797 * @description The title for the Image Property Editor Window
2798 * @type String
2800 STR_IMAGE_PROP_TITLE: 'Image Options',
2802 * @property STR_IMAGE_URL
2803 * @description The label string for Image URL
2804 * @type String
2806 STR_IMAGE_URL: 'Image Url',
2808 * @property STR_IMAGE_TITLE
2809 * @description The label string for Image Description
2810 * @type String
2812 STR_IMAGE_TITLE: 'Description',
2814 * @property STR_IMAGE_SIZE
2815 * @description The label string for Image Size
2816 * @type String
2818 STR_IMAGE_SIZE: 'Size',
2820 * @property STR_IMAGE_ORIG_SIZE
2821 * @description The label string for Original Image Size
2822 * @type String
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
2828 * @type String
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.
2834 * @type String
2836 STR_IMAGE_PADDING: 'Padding',
2838 * @property STR_IMAGE_BORDER
2839 * @description The label string for the image border.
2840 * @type String
2842 STR_IMAGE_BORDER: 'Border',
2844 * @property STR_IMAGE_TEXTFLOW
2845 * @description The label string for the image text flow.
2846 * @type String
2848 STR_IMAGE_TEXTFLOW: 'Text Flow',
2850 * @property STR_LOCAL_FILE_WARNING
2851 * @description The label string for the local file warning.
2852 * @type String
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.
2858 * @type String
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.
2864 * @type String
2866 STR_LINK_PROP_REMOVE: 'Remove link from text',
2868 * @property STR_LINK_URL
2869 * @description The label string for the Link URL.
2870 * @type String
2872 STR_LINK_URL: 'Link URL',
2874 * @property STR_LINK_NEW_WINDOW
2875 * @description The string for the open in a new window label.
2876 * @type String
2878 STR_LINK_NEW_WINDOW: 'Open in a new window.',
2880 * @property STR_LINK_TITLE
2881 * @description The string for the link description.
2882 * @type String
2884 STR_LINK_TITLE: 'Description',
2886 * @protected
2887 * @property STOP_EXEC_COMMAND
2888 * @description Set to true when you want the default execCommand function to not process anything
2889 * @type Boolean
2891 STOP_EXEC_COMMAND: false,
2893 * @protected
2894 * @property STOP_NODE_CHANGE
2895 * @description Set to true when you want the default nodeChange function to not process anything
2896 * @type Boolean
2898 STOP_NODE_CHANGE: false,
2900 * @protected
2901 * @property CLASS_HIDDEN
2902 * @description CSS class applied to the body when the hiddenelements button is pressed.
2903 * @type String
2905 CLASS_HIDDEN: 'hidden',
2907 * @protected
2908 * @property CLASS_LOCAL_FILE
2909 * @description CSS class applied to an element when it's found to have a local url.
2910 * @type String
2912 CLASS_LOCAL_FILE: 'warning-localfile',
2914 * @protected
2915 * @property CLASS_CONTAINER
2916 * @description Default CSS class to apply to the editors container element
2917 * @type String
2919 CLASS_CONTAINER: 'yui-editor-container',
2921 * @protected
2922 * @property CLASS_EDITABLE
2923 * @description Default CSS class to apply to the editors iframe element
2924 * @type String
2926 CLASS_EDITABLE: 'yui-editor-editable',
2928 * @protected
2929 * @property CLASS_EDITABLE_CONT
2930 * @description Default CSS class to apply to the editors iframe's parent element
2931 * @type String
2933 CLASS_EDITABLE_CONT: 'yui-editor-editable-container',
2935 * @protected
2936 * @property CLASS_PREFIX
2937 * @description Default prefix for dynamically created class names
2938 * @type String
2940 CLASS_PREFIX: 'yui-editor',
2941 /**
2942 * @property browser
2943 * @description Standard browser detection
2944 * @type Object
2946 browser: YAHOO.env.ua,
2947 /**
2948 * @method init
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
2960 * the editor.
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);
2966 var self = this;
2969 * @private
2970 * @config textarea
2971 * @description A reference to the textarea element that we are replacing
2972 * @default null
2973 * @type Boolean
2975 this.setAttributeConfig('textarea', {
2976 value: attr.textarea,
2977 writeOnce: true
2980 * @config height
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
2983 * @type String
2985 this.setAttributeConfig('height', {
2986 value: attr.height || Dom.getStyle(self.get('textarea'), 'height'),
2987 writeOnce: true
2990 * @config width
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
2993 * @type String
2995 this.setAttributeConfig('width', {
2996 value: attr.width || Dom.getStyle(this.get('textarea'), 'width'),
2997 writeOnce: true
3001 * @config blankimage
3002 * @description The CSS used to show/hide hidden elements on the page
3003 * @default 'assets/blankimage.png'
3004 * @type String
3006 this.setAttributeConfig('blankimage', {
3007 value: attr.blankimage || this._getBlankImage()
3010 * @config hiddencss
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;
3016 .hidden .yui-non {
3017 border: none;
3019 .hidden img {
3020 padding: 2px;
3021 }</pre></code>
3022 * @type String
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; }',
3026 writeOnce: true
3029 * @config css
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;
3034 span.yui-tag-a {
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;
3045 }</pre></code>
3046 * @type String
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; }',
3050 writeOnce: true
3053 * @config html
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>
3058 <code>
3059 <pre>
3060 &lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&gt;
3061 &lt;html&gt;
3062 &lt;head&gt;
3063 &lt;title&gt;{TITLE}&lt;/title&gt;
3064 &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
3065 &lt;style&gt;
3066 {CSS}
3067 &lt;/style&gt;
3068 &lt;style&gt;
3069 {HIDDEN_CSS}
3070 &lt;/style&gt;
3071 &lt;/head&gt;
3072 &lt;body onload="document.body._rteLoaded = true;"&gt;
3073 {CONTENT}
3074 &lt;/body&gt;
3075 &lt;/html&gt;
3076 </pre>
3077 </code>
3078 * @type String
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>',
3082 writeOnce: true
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.
3090 * @default false
3091 * @type Boolean
3093 this.setAttributeConfig('handleSubmit', {
3094 value: false,
3095 writeOnce: true,
3096 method: function(exec) {
3097 if (exec) {
3098 var ta = this.get('textarea');
3099 if (ta.form) {
3100 Event.addListener(ta.form, 'submit', function() {
3101 this.saveHTML();
3102 }, this, true);
3108 * @private
3109 * @config iframe
3110 * @description Internal config for holding the iframe element.
3111 * @default null
3112 * @type Boolean
3114 this.setAttributeConfig('iframe', {
3115 value: null,
3116 writeOnce: true
3119 * @config disabled
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.
3122 * @default false
3123 * @type Boolean
3126 this.setAttributeConfig('disabled', {
3127 value: false,
3128 method: function(disabled) {
3129 if (disabled) {
3130 if (!this._mask) {
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);
3143 } else {
3144 if (this._mask) {
3145 this._mask.parentNode.removeChild(this._mask);
3146 this._mask = null;
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
3157 * @default false
3158 * @type Boolean
3160 this.setAttributeConfig('element_cont', {
3161 value: null,
3162 writeOnce: true
3165 * @config toolbar_cont
3166 * @description Internal config for the toolbars container
3167 * @default false
3168 * @type Boolean
3170 this.setAttributeConfig('toolbar_cont', {
3171 value: null,
3172 writeOnce: true
3175 * @config toolbar
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>
3178 * @type Object
3180 this.setAttributeConfig('toolbar', {
3181 value: attr.toolbar || {
3182 /* {{{ Defaut Toolbar Config */
3183 collapse: true,
3184 titlebar: 'Text Editing Tools',
3185 draggable: false,
3186 buttons: [
3187 { group: 'fontstyle', label: 'Font Name and Size',
3188 buttons: [
3189 { type: 'select', label: 'Arial', value: 'fontname', disabled: true,
3190 menu: [
3191 { text: 'Arial', checked: true },
3192 { text: 'Arial Black' },
3193 { text: 'Comic Sans MS' },
3194 { text: 'Courier New' },
3195 { text: 'Lucida Console' },
3196 { text: 'Tahoma' },
3197 { text: 'Times New Roman' },
3198 { text: 'Trebuchet MS' },
3199 { text: 'Verdana' }
3202 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true }
3205 { type: 'separator' },
3206 { group: 'textstyle', label: 'Font Style',
3207 buttons: [
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',
3224 buttons: [
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',
3233 buttons: [
3234 { type: 'select', label: 'Normal', value: 'heading', disabled: true,
3235 menu: [
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',
3249 buttons: [
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',
3256 menu: [
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',
3269 buttons: [
3270 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true },
3271 { type: 'push', label: 'Insert Image', value: 'insertimage' }
3275 /* }}} */
3277 writeOnce: true,
3278 method: function(toolbar) {
3282 * @config animate
3283 * @description Should the editor animate window movements
3284 * @default false unless Animation is found, then true
3285 * @type Boolean
3287 this.setAttributeConfig('animate', {
3288 value: false,
3289 validator: function(value) {
3290 var ret = true;
3291 if (!YAHOO.util.Anim) {
3292 ret = false;
3294 return ret;
3298 * @config panel
3299 * @description A reference to the panel we are using for windows.
3300 * @default false
3301 * @type Boolean
3303 this.setAttributeConfig('panel', {
3304 value: null,
3305 writeOnce: true,
3306 validator: function(value) {
3307 var ret = true;
3308 if (!YAHOO.widget.Panel) {
3309 ret = false;
3311 return ret;
3315 * @config localFileWarning
3316 * @description Should we throw the warning if we detect a file that is local to their machine?
3317 * @default true
3318 * @type Boolean
3320 this.setAttributeConfig('localFileWarning', {
3321 value: true
3324 * @config dompath
3325 * @description Toggle the display of the current Dom path below the editor
3326 * @default false
3327 * @type Boolean
3329 this.setAttributeConfig('dompath', {
3330 value: false,
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();
3348 * @config markup
3349 * @description Should we try to adjust the markup for the following types: semantic, css or default
3350 * @default "semantic"
3351 * @type Boolean
3353 this.setAttributeConfig('markup', {
3354 value: 'semantic',
3355 validator: function(markup) {
3356 switch (markup.toLowerCase()) {
3357 case 'semantic':
3358 case 'css':
3359 case 'default':
3360 return true;
3361 break;
3363 return false;
3367 this.on('afterRender', function() {
3368 this._renderPanel();
3372 * @private
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];
3380 return '';
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);
3391 return img;
3394 * @private
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;
3406 * @private
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) {
3412 var cmd = o.button;
3413 var value = '#' + o.color;
3414 if ((cmd == 'forecolor') || (cmd == 'backcolor')) {
3415 this.execCommand(cmd, value);
3419 * @private
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);
3426 var cmd = null;
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;
3438 * @private
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++) {
3445 var elm = path[i],
3446 tag = elm.tagName.toLowerCase(),
3447 family = null,
3448 fontsize = null,
3449 validFont = false;
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');
3460 if (fn_button) {
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()) {
3463 validFont = true;
3464 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button
3467 if (!validFont) {
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');
3475 if (fs_button) {
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');
3485 if (hd_button) {
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);
3503 * @private
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],
3511 title = '',
3512 src = '',
3513 align = '',
3514 height = 75,
3515 width = 75,
3516 padding = 0,
3517 blankimage = false,
3518 win = new YAHOO.widget.EditorWindow('insertimage', {
3519 width: '415px'
3522 if (!el) {
3523 el = this._getSelectedElement();
3525 if (el) {
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;
3530 blankimage = true;
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);
3550 if (!el._height) {
3551 el._height = height;
3553 if (!el._width) {
3554 el._width = width;
3556 var oheight = el._height;
3557 var owidth = el._width;
3559 if (!win.cache) {
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);
3572 win.cache = body;
3573 } else {
3574 body = win.cache;
3577 var tbar = new YAHOO.widget.Toolbar(tbarCont, {
3578 /* {{{ */
3579 buttons: [
3580 { group: 'padding', label: this.STR_IMAGE_PADDING + ':',
3581 buttons: [
3582 { type: 'spin', label: ''+padding, value: 'padding', range: [0, 50] }
3585 { type: 'separator' },
3586 { group: 'border', label: this.STR_IMAGE_BORDER + ':',
3587 buttons: [
3588 { type: 'select', label: 'Border Size', value: 'bordersize',
3589 menu: [
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,
3599 menu: [
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 + ':',
3610 buttons: [
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' }
3618 /* }}} */
3621 var bsize = '0';
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>';
3646 var orgSize = '';
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);
3656 if (value > 5) {
3657 el.style.width = value + 'px';
3658 this.moveWindow();
3660 }, this, true);
3661 }, this, true);
3662 Event.onAvailable('insertimage_height', function() {
3663 Event.on('insertimage_height', 'blur', function() {
3664 var value = parseInt(Dom.get('insertimage_height').value);
3665 if (value > 5) {
3666 el.style.height = value + 'px';
3667 this.moveWindow();
3669 }, this, true);
3670 }, this, true);
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'));
3678 } else {
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;
3724 switch(value) {
3725 case 'bordersize':
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'));
3731 } else {
3732 tbar.disableButton(tbar.getButtonByValue('bordertype'));
3733 tbar.disableButton(tbar.getButtonByValue('bordercolor'));
3735 break;
3736 case 'bordertype':
3737 var borderString = size + 'px ' + o.button.value + ' ' + color;
3738 el.style.border = borderString;
3739 break;
3740 case 'right':
3741 case 'left':
3742 tbar.deselectAllButtons();
3743 el.style.display = '';
3744 el.align = o.button.value;
3745 break;
3746 case 'inline':
3747 tbar.deselectAllButtons();
3748 el.style.display = '';
3749 el.align = '';
3750 break;
3751 case 'block':
3752 tbar.deselectAllButtons();
3753 el.style.display = 'block';
3754 el.align = 'center';
3755 break;
3756 case 'padding':
3757 var _button = tbar.getButtonById(o.button.id);
3758 el.style.margin = _button.get('label') + 'px';
3759 break;
3761 tbar.selectButton(tbar.getButtonByValue(o.button.value));
3762 this.moveWindow();
3763 }, this, true);
3765 win.setHeader(this.STR_IMAGE_PROP_TITLE);
3766 win.setBody(body);
3767 if ((this.browser.webkit && !this.browser.webkit3) || this.browser.opera) {
3768 var str = this.STR_IMAGE_COPY;
3769 win.setFooter(str);
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();
3778 if (blankimage) {
3779 YAHOO.util.Dom.get('insertimage_url').select();
3781 }, 50);
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);
3792 } else {
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();
3803 var self = this;
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;
3813 self.moveWindow();
3814 }, 200);
3816 img.src = url.value;
3819 }, this, true);
3821 }, this, true);
3825 * @private
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'));
3838 } else {
3839 //No url/src given, remove the node from the document
3840 el.parentNode.removeChild(el);
3844 * @private
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', {
3852 width: '300px'
3855 var el = this.currentElement[0],
3856 url = '',
3857 title = '',
3858 target = '',
3859 localFile = false;
3860 if (el) {
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;
3867 win.setFooter(str);
3868 localFile = true;
3869 } else {
3870 win.setFooter(' ');
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>&nbsp;</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');
3890 unlink.href = '#';
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');
3896 this.closeWindow();
3897 }, this, true);
3898 unlinkCont.appendChild(unlink);
3899 body.appendChild(unlinkCont);
3901 win.setHeader(this.STR_LINK_PROP_TITLE);
3902 win.setBody(body);
3904 Event.onAvailable('createlink_url', function() {
3905 window.setTimeout(function() {
3906 try {
3907 YAHOO.util.Dom.get('createlink_url').focus();
3908 } catch (e) {}
3909 }, 50);
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);
3918 } else {
3919 Dom.removeClass(url, 'warning');
3920 this.get('panel').setFooter(' ');
3922 }, this, true);
3923 }, this, true);
3925 this.openWindow(win);
3929 * @private
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;
3944 } else {
3945 /* :// not found adding */
3946 urlValue = 'http:/'+'/' + urlValue;
3949 el.setAttribute('href', urlValue);
3950 if (target.checked) {
3951 el.setAttribute('target', target.value);
3952 } else {
3953 el.setAttribute('target', '');
3955 el.setAttribute('title', ((title.value) ? title.value : ''));
3957 } else {
3958 el.removeAttribute('tag');
3959 Dom.removeClass(el, 'yui-tag-a');
3960 Dom.removeClass(el, 'yui-tag');
3961 Dom.addClass(el, 'yui-non');
3963 this.nodeChange();
3966 * @method render
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];
3972 return false;
3974 var self = this;
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) {
3986 this.moveWindow();
3988 }, this, true);
3989 this.toolbar.on('toolbarExpanded', function() {
3990 if (this.currentWindow) {
3991 this.moveWindow();
3993 }, this, true);
3994 this.toolbar.on('fontsizeClick', function(o) {
3995 this._handleFontSize(o);
3996 }, this, true);
3998 this.toolbar.on('colorPickerClicked', function(o) {
3999 this._handleColorPicker(o);
4000 }, this, true);
4002 this.toolbar.on('alignClick', function(o) {
4003 this._handleAlign(o);
4004 }, this, true);
4005 this.on('afterNodeChange', function() {
4006 this._handleAfterNodeChange();
4007 }, this, true);
4008 this.toolbar.on('insertimageClick', function() {
4009 this._handleInsertImageClick();
4010 }, this, true);
4011 this.on('windowinsertimageClose', function() {
4012 this._handleInsertImageWindowClose();
4013 }, this, true);
4014 this.toolbar.on('createlinkClick', function() {
4015 this._handleCreateLinkClick();
4016 }, this, true);
4017 this.on('windowcreatelinkClose', function() {
4018 this._handleCreateLinkWindowClose();
4019 }, this, true);
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%');
4049 var self = this;
4050 window.setTimeout(function() {
4051 self._setInitialContent.call(self);
4052 }, 10);
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;
4066 return false;
4068 this._setMarkupType(action);
4069 if (this.browser.ie) {
4070 this._getWindow().focus();
4072 var exec = true;
4073 var _sel = this._getSelection();
4074 var _range = this._getRange();
4075 var _selEl = this._getSelectedElement();
4076 if (_selEl) {
4077 _sel = _selEl;
4079 switch (action.toLowerCase()) {
4080 case 'heading':
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);
4094 exec = false;
4095 } else {
4096 if (this.browser.ie || this.browser.webkit || this.browser.opera) {
4097 this._createCurrentElement(value);
4098 exec = false;
4101 break;
4102 case 'backcolor':
4103 if (this.browser.gecko || this.browser.opera) {
4104 this._setEditorStyle(true);
4105 action = 'hilitecolor';
4107 break;
4108 case 'hiddenelements':
4109 this._showHidden();
4110 exec = false;
4111 break;
4112 case 'unlink':
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');
4122 exec = false;
4123 break;
4124 case 'createlink':
4125 var el = this._getSelectedElement();
4126 if (!el || (el.getAttribute('tag') != 'a')) {
4127 this._createCurrentElement('a');
4128 } else {
4129 this.currentElement[0] = el;
4131 exec = false;
4132 break;
4133 case 'insertimage':
4134 if (value == '') {
4135 value = this.get('blankimage');
4138 * @knownissue
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;
4148 exec = false;
4149 } else {
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];
4159 exec = false;
4160 } else {
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;
4167 exec = false;
4171 break;
4172 case 'inserthtml':
4174 * @knownissue
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]);
4185 exec = false;
4186 } else if (this.browser.ie) {
4187 var _range = this._getRange();
4188 if (_range.item) {
4189 _range.item(0).outerHTML = value;
4190 } else {
4191 _range.pasteHTML(value);
4193 exec = false;
4195 break;
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);
4212 exec = false;
4214 break;
4215 case 'superscript':
4216 case 'subscript':
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);
4228 } else {
4229 var _sub = this._getDoc().createElement(tag);
4230 _sub.innerHTML = this.currentElement[0].innerHTML;
4231 this.currentElement[0].parentNode.replaceChild(_sub, this.currentElement[0]);
4233 exec = false;
4235 break;
4236 case 'formatblock':
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);
4246 exec = false;
4247 } else {
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);
4254 exec = false;
4257 break;
4258 case 'indent':
4259 case 'outdent':
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);
4271 exec = false;
4272 break;
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');
4291 var str = '';
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;
4298 } else {
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);
4303 if (tag == 'ol') {
4304 list.type = value;
4306 var li = this._getDoc().createElement('li');
4307 li.innerHTML = el.innerHTML + '&nbsp;';
4308 list.appendChild(li);
4310 el.parentNode.replaceChild(list, el);
4311 exec = false;
4312 } else {
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');
4318 exec = false;
4319 var str = '';
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);
4327 } else {
4328 this.nodeChange();
4329 this._getDoc().execCommand(action, '', el.parentNode);
4330 this.nodeChange();
4333 if (this.browser.opera) {
4334 var self = this;
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);
4342 },30);
4344 if (this.browser.ie && exec) {
4345 var html = '';
4346 if (this._getRange().html) {
4347 html = '<li>' + this._getRange().html+ '</li>';
4348 } else {
4349 html = '<li>' + this._getRange().text + '</li>';
4352 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>');
4353 exec = false;
4356 break;
4357 case 'fontname':
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);
4362 exec = false;
4364 break;
4365 case 'fontsize':
4366 if ((this.currentElement.length > 0) && (!this._hasSelection())) {
4367 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value);
4368 } else {
4369 this._createCurrentElement('span', {'fontSize': value });
4371 exec = false;
4372 break;
4374 if (exec) {
4375 YAHOO.log('execCommand::(' + action + '), (' + value + ')', 'info', 'Editor');
4376 try {
4377 this._getDoc().execCommand(action, false, value);
4378 } catch(e) {
4379 YAHOO.log('execCommand Failed', 'error', 'Editor');
4381 } else {
4382 YAHOO.log('OVERRIDE::execCommand skipped', 'warn', 'Editor');
4384 this.on('afterExecCommand', function() {
4385 this.unsubscribeAll('afterExecCommand');
4386 this.nodeChange();
4388 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this });
4392 * @private
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(),
4403 tar = null,
4404 el = [],
4405 _doc = this._getDoc();
4407 if (this.currentFont) {
4408 if (!tagStyle) {
4409 tagStyle = {};
4411 tagStyle.fontFamily = this.currentFont;
4412 this.currentFont = null;
4414 this.currentElement = [];
4416 var _elCreate = function() {
4417 switch (tagName) {
4418 case 'h1':
4419 case 'h2':
4420 case 'h3':
4421 case 'h4':
4422 case 'h5':
4423 case 'h6':
4424 var el = _doc.createElement(tagName);
4425 break;
4426 default:
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);
4431 el.tabIndex = 1;
4433 for (var i in tagStyle) {
4434 if (YAHOO.util.Lang.hasOwnProperty(tagStyle, i)) {
4435 el.style[i] = tagStyle[i];
4438 break;
4440 return el;
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') {
4449 el = _elCreate();
4450 imgs[i].parentNode.replaceChild(el, imgs[i]);
4451 this.currentElement[this.currentElement.length] = el;
4452 //this.currentElement = el;
4455 } else {
4456 if (this.currentEvent) {
4457 tar = YAHOO.util.Event.getTarget(this.currentEvent);
4458 } else {
4459 //For Safari..
4460 tar = this._getDoc().body;
4463 if (tar) {
4465 * @knownissue
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.
4470 el = _elCreate();
4471 if (tar.tagName.toLowerCase() == 'body') {
4472 tar.appendChild(el);
4473 } else if (tar.nextSibling) {
4474 tar.parentNode.insertBefore(el, tar.nextSibling);
4475 } else {
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);
4487 } else {
4488 //Force CSS Styling for this action...
4489 this._setEditorStyle(true);
4490 this._getDoc().execCommand('fontname', false, 'yui-tmp');
4491 var _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;
4549 * @method saveHTML
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;
4555 return 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;
4564 this.nodeChange();
4567 * @method getEditorHTML
4568 * @description Gets the unprocessed/unfiltered HTML from the editor
4570 getEditorHTML: function() {
4571 return this._getDoc().body.innerHTML;
4574 * @method cleanHTML
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
4581 //Begin RegExs..
4582 if (!html) {
4583 var html = this.getEditorHTML();
4585 //Make some backups...
4586 html = html.replace(/<div><br><\/div>/gi, '<YUI_BR>');
4587 html = html.replace(/<p>(&nbsp;|&#160;)<\/p>/g, '<YUI_BR>');
4588 html = html.replace(/<p><br>&nbsp;<\/p>/gi, '<YUI_BR>');
4589 html = html.replace(/<p>&nbsp;<\/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');
4640 //<P><br>&nbsp;</P>
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>');
4651 return html;
4654 * @method clearEditorDoc
4655 * @description Clear the doc of the Editor
4657 clearEditorDoc: function() {
4658 this._getDoc().body.innerHTML = '&nbsp;';
4661 * @private
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, {
4669 width: '300px',
4670 iframe: true,
4671 visible: false,
4672 underlay: 'none',
4673 draggable: false,
4674 close: false
4676 YAHOO.widget.EditorInfo.panel = panel;
4677 } else {
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');
4688 } else {
4689 Event.onDOMReady(function() {
4690 this.get('panel').render(document.body);
4691 Dom.addClass(this.get('panel').element, 'yui-editor-panel');
4692 }, this, true);
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;
4713 var self = 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),
4719 align = 'center';
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);
4737 }, this, true);
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() {
4753 this.closeWindow();
4754 }, this, true);
4755 var _knob = document.createElement('span');
4756 _knob.innerHTML = '^';
4757 _knob.className = 'knob';
4758 win._knob = _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);
4778 }, this, true);
4779 panel.hideEvent.subscribe(function() {
4780 this.currentWindow = null;
4781 var evName = 'window' + windowName + 'Close';
4782 this.fireEvent(evName, { type: evName, target: this });
4784 }, this, true);
4785 this.currentWindow = win;
4786 this.moveWindow(true);
4787 panel.show();
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) {
4797 return false;
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),
4806 align = 'center',
4807 orgXY = panel.cfg.getProperty('xy'),
4808 _knob = win._knob;
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
4821 } else {
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;
4828 } else {
4829 if (Dom.getStyle(this.currentElement[0], 'fontSize').indexOf('px') != -1) {
4830 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize')) + 5;
4831 } else {
4832 newXY[1] = newXY[1] + 20;
4835 if (newXY[0] < elXY[0]) {
4836 newXY[0] = elXY[0] + 5;
4837 align = 'left';
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);
4842 align = 'right';
4845 try {
4846 var xDiff = (newXY[0] - orgXY[0]);
4847 var yDiff = (newXY[1] - orgXY[1]);
4848 } catch (e) {
4849 var xDiff = 0;
4850 var yDiff = 0;
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)
4858 var _knobLeft = 0,
4859 elW = 0;
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) {
4871 _knobLeft = 40;
4873 if (isNaN(_knobLeft)) {
4874 _knobLeft = 40;
4876 if (force) {
4877 if (_knob) {
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, {
4883 opacity: {
4884 from: 0,
4885 to: 1
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';
4893 }, this, true);
4894 anim.animate();
4895 } else {
4896 panel.cfg.setProperty('xy', newXY);
4898 } else {
4899 if (this.get('animate')) {
4900 var anim = new YAHOO.util.Anim(panel.element, {}, .5, YAHOO.util.Easing.easeOut);
4901 anim.attributes = {
4902 top: {
4903 to: newXY[1]
4905 left: {
4906 to: newXY[0]
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, {
4916 left: {
4917 to: _knobLeft
4919 }, .75, YAHOO.util.Easing.easeOut);
4920 anim.animate();
4921 iframeAnim.animate();
4922 _knobAnim.animate();
4923 } else {
4924 _knob.style.left = _knobLeft + 'px';
4925 panel.cfg.setProperty('xy', newXY);
4931 * @private
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) {
4939 this.closeWindow();
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);
4960 * @method destroy
4961 * @description Destroys the editor, all of it's elements and objects.
4962 * @return {Boolean}
4964 destroy: function() {
4965 this.saveHTML();
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)) {
4974 this[i] = null;
4977 return true;
4980 * @method toString
4981 * @description Returns a string representing the editor.
4982 * @return {String}
4984 toString: function() {
4985 var str = 'Editor';
4986 if (this.get && this.get('element_cont')) {
4987 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : ''));
4989 return str;
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
5101 * @class EditorInfo
5102 * @static
5104 YAHOO.widget.EditorInfo = {
5106 * @private
5107 * @property window
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>
5111 window: {},
5113 * @private
5114 * @property panel
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>
5118 panel: null
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) {
5130 * @private
5131 * @property name
5132 * @description A unique name for the window
5134 this.name = name.replace(' ', '_');
5136 * @private
5137 * @property attrs
5138 * @description The window attributes
5140 this.attrs = attrs;
5143 YAHOO.widget.EditorWindow.prototype = {
5145 * @private
5146 * @property _cache
5147 * @description Holds a cache of the DOM for the window so we only have to build it once..
5149 _cache: null,
5151 * @private
5152 * @property header
5153 * @description Holder for the header of the window, used in Editor.openWindow
5155 header: null,
5157 * @private
5158 * @property body
5159 * @description Holder for the body of the window, used in Editor.openWindow
5161 body: null,
5163 * @private
5164 * @property footer
5165 * @description Holder for the footer of the window, used in Editor.openWindow
5167 footer: null,
5169 * @method setHeader
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) {
5174 this.header = str;
5177 * @method setBody
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) {
5182 this.body = str;
5185 * @method setFooter
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) {
5190 this.footer = str;
5193 * @method toString
5194 * @description Returns a string representing the EditorWindow.
5195 * @return {String}
5197 toString: function() {
5198 return 'Editor Window (' + this.name + ')';
5204 })();
5205 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.3.0", build: "442"});