[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('yui2-simpleeditor', function(Y) { 2 var YAHOO = Y.YUI2; 3 /* 4 Copyright (c) 2011, Yahoo! Inc. All rights reserved. 5 Code licensed under the BSD License: 6 http://developer.yahoo.com/yui/license.html 7 version: 2.9.0 8 */ 9 (function() { 10 var Dom = YAHOO.util.Dom, 11 Event = YAHOO.util.Event, 12 Lang = YAHOO.lang; 13 /** 14 * @module editor 15 * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p> 16 * @class ToolbarButtonAdvanced 17 * @namespace YAHOO.widget 18 * @requires yahoo, dom, element, event, container_core, menu, button 19 * 20 * Provides a toolbar button based on the button and menu widgets. 21 * @constructor 22 * @class ToolbarButtonAdvanced 23 * @param {String/HTMLElement} el The element to turn into a button. 24 * @param {Object} attrs Object liternal containing configuration parameters. 25 */ 26 if (YAHOO.widget.Button) { 27 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button; 28 /** 29 * @property buttonType 30 * @private 31 * @description Tells if the Button is a Rich Button or a Simple Button 32 */ 33 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich'; 34 /** 35 * @method checkValue 36 * @param {String} value The value of the option that we want to mark as selected 37 * @description Select an option by value 38 */ 39 YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) { 40 var _menuItems = this.getMenu().getItems(); 41 if (_menuItems.length === 0) { 42 this.getMenu()._onBeforeShow(); 43 _menuItems = this.getMenu().getItems(); 44 } 45 for (var i = 0; i < _menuItems.length; i++) { 46 _menuItems[i].cfg.setProperty('checked', false); 47 if (_menuItems[i].value == value) { 48 _menuItems[i].cfg.setProperty('checked', true); 49 } 50 } 51 }; 52 } else { 53 YAHOO.widget.ToolbarButtonAdvanced = function() {}; 54 } 55 56 57 /** 58 * @description <p>Creates a basic custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p><p>Provides a toolbar button based on the button and menu widgets, <select> elements are used in place of menu's.</p> 59 * @class ToolbarButton 60 * @namespace YAHOO.widget 61 * @requires yahoo, dom, element, event 62 * @extends YAHOO.util.Element 63 * 64 * 65 * @constructor 66 * @param {String/HTMLElement} el The element to turn into a button. 67 * @param {Object} attrs Object liternal containing configuration parameters. 68 */ 69 70 YAHOO.widget.ToolbarButton = function(el, attrs) { 71 72 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) { 73 attrs = el; 74 } 75 var local_attrs = (attrs || {}); 76 77 var oConfig = { 78 element: null, 79 attributes: local_attrs 80 }; 81 82 if (!oConfig.attributes.type) { 83 oConfig.attributes.type = 'push'; 84 } 85 86 oConfig.element = document.createElement('span'); 87 oConfig.element.setAttribute('unselectable', 'on'); 88 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button'; 89 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>'; 90 oConfig.element.firstChild.firstChild.tabIndex = '-1'; 91 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId()); 92 oConfig.element.id = oConfig.attributes.id; 93 94 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 95 }; 96 97 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, { 98 /** 99 * @property buttonType 100 * @private 101 * @description Tells if the Button is a Rich Button or a Simple Button 102 */ 103 buttonType: 'normal', 104 /** 105 * @method _handleMouseOver 106 * @private 107 * @description Adds classes to the button elements on mouseover (hover) 108 */ 109 _handleMouseOver: function() { 110 if (!this.get('disabled')) { 111 this.addClass('yui-button-hover'); 112 this.addClass('yui-' + this.get('type') + '-button-hover'); 113 } 114 }, 115 /** 116 * @method _handleMouseOut 117 * @private 118 * @description Removes classes from the button elements on mouseout (hover) 119 */ 120 _handleMouseOut: function() { 121 this.removeClass('yui-button-hover'); 122 this.removeClass('yui-' + this.get('type') + '-button-hover'); 123 }, 124 /** 125 * @method checkValue 126 * @param {String} value The value of the option that we want to mark as selected 127 * @description Select an option by value 128 */ 129 checkValue: function(value) { 130 if (this.get('type') == 'menu') { 131 var opts = this._button.options; 132 if (opts) { 133 for (var i = 0; i < opts.length; i++) { 134 if (opts[i].value == value) { 135 opts.selectedIndex = i; 136 } 137 } 138 } 139 } 140 }, 141 /** 142 * @method init 143 * @description The ToolbarButton class's initialization method 144 */ 145 init: function(p_oElement, p_oAttributes) { 146 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes); 147 148 this.on('mouseover', this._handleMouseOver, this, true); 149 this.on('mouseout', this._handleMouseOut, this, true); 150 this.on('click', function(ev) { 151 Event.stopEvent(ev); 152 return false; 153 }, this, true); 154 }, 155 /** 156 * @method initAttributes 157 * @description Initializes all of the configuration attributes used to create 158 * the toolbar. 159 * @param {Object} attr Object literal specifying a set of 160 * configuration attributes used to create the toolbar. 161 */ 162 initAttributes: function(attr) { 163 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr); 164 /** 165 * @attribute value 166 * @description The value of the button 167 * @type String 168 */ 169 this.setAttributeConfig('value', { 170 value: attr.value 171 }); 172 /** 173 * @attribute menu 174 * @description The menu attribute, see YAHOO.widget.Button 175 * @type Object 176 */ 177 this.setAttributeConfig('menu', { 178 value: attr.menu || false 179 }); 180 /** 181 * @attribute type 182 * @description The type of button to create: push, menu, color, select, spin 183 * @type String 184 */ 185 this.setAttributeConfig('type', { 186 value: attr.type, 187 writeOnce: true, 188 method: function(type) { 189 var el, opt; 190 if (!this._button) { 191 this._button = this.get('element').getElementsByTagName('a')[0]; 192 } 193 switch (type) { 194 case 'select': 195 case 'menu': 196 el = document.createElement('select'); 197 el.id = this.get('id'); 198 var menu = this.get('menu'); 199 for (var i = 0; i < menu.length; i++) { 200 opt = document.createElement('option'); 201 opt.innerHTML = menu[i].text; 202 opt.value = menu[i].value; 203 if (menu[i].checked) { 204 opt.selected = true; 205 } 206 el.appendChild(opt); 207 } 208 this._button.parentNode.replaceChild(el, this._button); 209 Event.on(el, 'change', this._handleSelect, this, true); 210 this._button = el; 211 break; 212 } 213 } 214 }); 215 216 /** 217 * @attribute disabled 218 * @description Set the button into a disabled state 219 * @type String 220 */ 221 this.setAttributeConfig('disabled', { 222 value: attr.disabled || false, 223 method: function(disabled) { 224 if (disabled) { 225 this.addClass('yui-button-disabled'); 226 this.addClass('yui-' + this.get('type') + '-button-disabled'); 227 } else { 228 this.removeClass('yui-button-disabled'); 229 this.removeClass('yui-' + this.get('type') + '-button-disabled'); 230 } 231 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) { 232 this._button.disabled = disabled; 233 } 234 } 235 }); 236 237 /** 238 * @attribute label 239 * @description The text label for the button 240 * @type String 241 */ 242 this.setAttributeConfig('label', { 243 value: attr.label, 244 method: function(label) { 245 if (!this._button) { 246 this._button = this.get('element').getElementsByTagName('a')[0]; 247 } 248 if (this.get('type') == 'push') { 249 this._button.innerHTML = label; 250 } 251 } 252 }); 253 254 /** 255 * @attribute title 256 * @description The title of the button 257 * @type String 258 */ 259 this.setAttributeConfig('title', { 260 value: attr.title 261 }); 262 263 /** 264 * @config container 265 * @description The container that the button is rendered to, handled by Toolbar 266 * @type String 267 */ 268 this.setAttributeConfig('container', { 269 value: null, 270 writeOnce: true, 271 method: function(cont) { 272 this.appendTo(cont); 273 } 274 }); 275 276 }, 277 /** 278 * @private 279 * @method _handleSelect 280 * @description The event fired when a change event gets fired on a select element 281 * @param {Event} ev The change event. 282 */ 283 _handleSelect: function(ev) { 284 var tar = Event.getTarget(ev); 285 var value = tar.options[tar.selectedIndex].value; 286 this.fireEvent('change', {type: 'change', value: value }); 287 }, 288 /** 289 * @method getMenu 290 * @description A stub function to mimic YAHOO.widget.Button's getMenu method 291 */ 292 getMenu: function() { 293 return this.get('menu'); 294 }, 295 /** 296 * @method destroy 297 * @description Destroy the button 298 */ 299 destroy: function() { 300 Event.purgeElement(this.get('element'), true); 301 this.get('element').parentNode.removeChild(this.get('element')); 302 //Brutal Object Destroy 303 for (var i in this) { 304 if (Lang.hasOwnProperty(this, i)) { 305 this[i] = null; 306 } 307 } 308 }, 309 /** 310 * @method fireEvent 311 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled. 312 */ 313 fireEvent: function(p_sType, p_aArgs) { 314 // Disabled buttons should not respond to DOM events 315 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) { 316 Event.stopEvent(p_aArgs); 317 return; 318 } 319 320 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs); 321 }, 322 /** 323 * @method toString 324 * @description Returns a string representing the toolbar. 325 * @return {String} 326 */ 327 toString: function() { 328 return 'ToolbarButton (' + this.get('id') + ')'; 329 } 330 331 }); 332 })(); 333 /** 334 * @module editor 335 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p> 336 * @namespace YAHOO.widget 337 * @requires yahoo, dom, element, event, toolbarbutton 338 * @optional container_core, dragdrop 339 */ 340 (function() { 341 var Dom = YAHOO.util.Dom, 342 Event = YAHOO.util.Event, 343 Lang = YAHOO.lang; 344 345 var getButton = function(id) { 346 var button = id; 347 if (Lang.isString(id)) { 348 button = this.getButtonById(id); 349 } 350 if (Lang.isNumber(id)) { 351 button = this.getButtonByIndex(id); 352 } 353 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) { 354 button = this.getButtonByValue(id); 355 } 356 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) { 357 return button; 358 } 359 return false; 360 }; 361 362 /** 363 * Provides a rich toolbar widget based on the button and menu widgets 364 * @constructor 365 * @class Toolbar 366 * @extends YAHOO.util.Element 367 * @param {String/HTMLElement} el The element to turn into a toolbar. 368 * @param {Object} attrs Object liternal containing configuration parameters. 369 */ 370 YAHOO.widget.Toolbar = function(el, attrs) { 371 372 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) { 373 attrs = el; 374 } 375 var local_attrs = {}; 376 if (attrs) { 377 Lang.augmentObject(local_attrs, attrs); //Break the config reference 378 } 379 380 381 var oConfig = { 382 element: null, 383 attributes: local_attrs 384 }; 385 386 387 if (Lang.isString(el) && Dom.get(el)) { 388 oConfig.element = Dom.get(el); 389 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) { 390 oConfig.element = Dom.get(el); 391 } 392 393 394 if (!oConfig.element) { 395 oConfig.element = document.createElement('DIV'); 396 oConfig.element.id = Dom.generateId(); 397 398 if (local_attrs.container && Dom.get(local_attrs.container)) { 399 Dom.get(local_attrs.container).appendChild(oConfig.element); 400 } 401 } 402 403 404 if (!oConfig.element.id) { 405 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId()); 406 } 407 408 var fs = document.createElement('fieldset'); 409 var lg = document.createElement('legend'); 410 lg.innerHTML = 'Toolbar'; 411 fs.appendChild(lg); 412 413 var cont = document.createElement('DIV'); 414 oConfig.attributes.cont = cont; 415 Dom.addClass(cont, 'yui-toolbar-subcont'); 416 fs.appendChild(cont); 417 oConfig.element.appendChild(fs); 418 419 oConfig.element.tabIndex = -1; 420 421 422 oConfig.attributes.element = oConfig.element; 423 oConfig.attributes.id = oConfig.element.id; 424 425 this._configuredButtons = []; 426 427 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 428 429 }; 430 431 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, { 432 /** 433 * @protected 434 * @property _configuredButtons 435 * @type Array 436 */ 437 _configuredButtons: null, 438 /** 439 * @method _addMenuClasses 440 * @private 441 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items 442 * @param {String} ev The event that fired. 443 * @param {Array} na Array of event information. 444 * @param {Object} o Button config object. 445 */ 446 _addMenuClasses: function(ev, na, o) { 447 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu'); 448 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) { 449 Dom.addClass(this.element, 'yui-toolbar-select-menu'); 450 } 451 var items = this.getItems(); 452 for (var i = 0; i < items.length; i++) { 453 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())); 454 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-'))); 455 } 456 }, 457 /** 458 * @property buttonType 459 * @description The default button to use 460 * @type Object 461 */ 462 buttonType: YAHOO.widget.ToolbarButton, 463 /** 464 * @property dd 465 * @description The DragDrop instance associated with the Toolbar 466 * @type Object 467 */ 468 dd: null, 469 /** 470 * @property _colorData 471 * @description Object reference containing colors hex and text values. 472 * @type Object 473 */ 474 _colorData: { 475 /* {{{ _colorData */ 476 '#111111': 'Obsidian', 477 '#2D2D2D': 'Dark Gray', 478 '#434343': 'Shale', 479 '#5B5B5B': 'Flint', 480 '#737373': 'Gray', 481 '#8B8B8B': 'Concrete', 482 '#A2A2A2': 'Gray', 483 '#B9B9B9': 'Titanium', 484 '#000000': 'Black', 485 '#D0D0D0': 'Light Gray', 486 '#E6E6E6': 'Silver', 487 '#FFFFFF': 'White', 488 '#BFBF00': 'Pumpkin', 489 '#FFFF00': 'Yellow', 490 '#FFFF40': 'Banana', 491 '#FFFF80': 'Pale Yellow', 492 '#FFFFBF': 'Butter', 493 '#525330': 'Raw Siena', 494 '#898A49': 'Mildew', 495 '#AEA945': 'Olive', 496 '#7F7F00': 'Paprika', 497 '#C3BE71': 'Earth', 498 '#E0DCAA': 'Khaki', 499 '#FCFAE1': 'Cream', 500 '#60BF00': 'Cactus', 501 '#80FF00': 'Chartreuse', 502 '#A0FF40': 'Green', 503 '#C0FF80': 'Pale Lime', 504 '#DFFFBF': 'Light Mint', 505 '#3B5738': 'Green', 506 '#668F5A': 'Lime Gray', 507 '#7F9757': 'Yellow', 508 '#407F00': 'Clover', 509 '#8A9B55': 'Pistachio', 510 '#B7C296': 'Light Jade', 511 '#E6EBD5': 'Breakwater', 512 '#00BF00': 'Spring Frost', 513 '#00FF80': 'Pastel Green', 514 '#40FFA0': 'Light Emerald', 515 '#80FFC0': 'Sea Foam', 516 '#BFFFDF': 'Sea Mist', 517 '#033D21': 'Dark Forrest', 518 '#438059': 'Moss', 519 '#7FA37C': 'Medium Green', 520 '#007F40': 'Pine', 521 '#8DAE94': 'Yellow Gray Green', 522 '#ACC6B5': 'Aqua Lung', 523 '#DDEBE2': 'Sea Vapor', 524 '#00BFBF': 'Fog', 525 '#00FFFF': 'Cyan', 526 '#40FFFF': 'Turquoise Blue', 527 '#80FFFF': 'Light Aqua', 528 '#BFFFFF': 'Pale Cyan', 529 '#033D3D': 'Dark Teal', 530 '#347D7E': 'Gray Turquoise', 531 '#609A9F': 'Green Blue', 532 '#007F7F': 'Seaweed', 533 '#96BDC4': 'Green Gray', 534 '#B5D1D7': 'Soapstone', 535 '#E2F1F4': 'Light Turquoise', 536 '#0060BF': 'Summer Sky', 537 '#0080FF': 'Sky Blue', 538 '#40A0FF': 'Electric Blue', 539 '#80C0FF': 'Light Azure', 540 '#BFDFFF': 'Ice Blue', 541 '#1B2C48': 'Navy', 542 '#385376': 'Biscay', 543 '#57708F': 'Dusty Blue', 544 '#00407F': 'Sea Blue', 545 '#7792AC': 'Sky Blue Gray', 546 '#A8BED1': 'Morning Sky', 547 '#DEEBF6': 'Vapor', 548 '#0000BF': 'Deep Blue', 549 '#0000FF': 'Blue', 550 '#4040FF': 'Cerulean Blue', 551 '#8080FF': 'Evening Blue', 552 '#BFBFFF': 'Light Blue', 553 '#212143': 'Deep Indigo', 554 '#373E68': 'Sea Blue', 555 '#444F75': 'Night Blue', 556 '#00007F': 'Indigo Blue', 557 '#585E82': 'Dockside', 558 '#8687A4': 'Blue Gray', 559 '#D2D1E1': 'Light Blue Gray', 560 '#6000BF': 'Neon Violet', 561 '#8000FF': 'Blue Violet', 562 '#A040FF': 'Violet Purple', 563 '#C080FF': 'Violet Dusk', 564 '#DFBFFF': 'Pale Lavender', 565 '#302449': 'Cool Shale', 566 '#54466F': 'Dark Indigo', 567 '#655A7F': 'Dark Violet', 568 '#40007F': 'Violet', 569 '#726284': 'Smoky Violet', 570 '#9E8FA9': 'Slate Gray', 571 '#DCD1DF': 'Violet White', 572 '#BF00BF': 'Royal Violet', 573 '#FF00FF': 'Fuchsia', 574 '#FF40FF': 'Magenta', 575 '#FF80FF': 'Orchid', 576 '#FFBFFF': 'Pale Magenta', 577 '#4A234A': 'Dark Purple', 578 '#794A72': 'Medium Purple', 579 '#936386': 'Cool Granite', 580 '#7F007F': 'Purple', 581 '#9D7292': 'Purple Moon', 582 '#C0A0B6': 'Pale Purple', 583 '#ECDAE5': 'Pink Cloud', 584 '#BF005F': 'Hot Pink', 585 '#FF007F': 'Deep Pink', 586 '#FF409F': 'Grape', 587 '#FF80BF': 'Electric Pink', 588 '#FFBFDF': 'Pink', 589 '#451528': 'Purple Red', 590 '#823857': 'Purple Dino', 591 '#A94A76': 'Purple Gray', 592 '#7F003F': 'Rose', 593 '#BC6F95': 'Antique Mauve', 594 '#D8A5BB': 'Cool Marble', 595 '#F7DDE9': 'Pink Granite', 596 '#C00000': 'Apple', 597 '#FF0000': 'Fire Truck', 598 '#FF4040': 'Pale Red', 599 '#FF8080': 'Salmon', 600 '#FFC0C0': 'Warm Pink', 601 '#441415': 'Sepia', 602 '#82393C': 'Rust', 603 '#AA4D4E': 'Brick', 604 '#800000': 'Brick Red', 605 '#BC6E6E': 'Mauve', 606 '#D8A3A4': 'Shrimp Pink', 607 '#F8DDDD': 'Shell Pink', 608 '#BF5F00': 'Dark Orange', 609 '#FF7F00': 'Orange', 610 '#FF9F40': 'Grapefruit', 611 '#FFBF80': 'Canteloupe', 612 '#FFDFBF': 'Wax', 613 '#482C1B': 'Dark Brick', 614 '#855A40': 'Dirt', 615 '#B27C51': 'Tan', 616 '#7F3F00': 'Nutmeg', 617 '#C49B71': 'Mustard', 618 '#E1C4A8': 'Pale Tan', 619 '#FDEEE0': 'Marble' 620 /* }}} */ 621 }, 622 /** 623 * @property _colorPicker 624 * @description The HTML Element containing the colorPicker 625 * @type HTMLElement 626 */ 627 _colorPicker: null, 628 /** 629 * @property STR_COLLAPSE 630 * @description String for Toolbar Collapse Button 631 * @type String 632 */ 633 STR_COLLAPSE: 'Collapse Toolbar', 634 /** 635 * @property STR_EXPAND 636 * @description String for Toolbar Collapse Button - Expand 637 * @type String 638 */ 639 STR_EXPAND: 'Expand Toolbar', 640 /** 641 * @property STR_SPIN_LABEL 642 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute 643 * @type String 644 */ 645 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.', 646 /** 647 * @property STR_SPIN_UP 648 * @description String for spinbutton up 649 * @type String 650 */ 651 STR_SPIN_UP: 'Click to increase the value of this input', 652 /** 653 * @property STR_SPIN_DOWN 654 * @description String for spinbutton down 655 * @type String 656 */ 657 STR_SPIN_DOWN: 'Click to decrease the value of this input', 658 /** 659 * @property _titlebar 660 * @description Object reference to the titlebar 661 * @type HTMLElement 662 */ 663 _titlebar: null, 664 /** 665 * @property browser 666 * @description Standard browser detection 667 * @type Object 668 */ 669 browser: YAHOO.env.ua, 670 /** 671 * @protected 672 * @property _buttonList 673 * @description Internal property list of current buttons in the toolbar 674 * @type Array 675 */ 676 _buttonList: null, 677 /** 678 * @protected 679 * @property _buttonGroupList 680 * @description Internal property list of current button groups in the toolbar 681 * @type Array 682 */ 683 _buttonGroupList: null, 684 /** 685 * @protected 686 * @property _sep 687 * @description Internal reference to the separator HTML Element for cloning 688 * @type HTMLElement 689 */ 690 _sep: null, 691 /** 692 * @protected 693 * @property _sepCount 694 * @description Internal refernce for counting separators, so we can give them a useful class name for styling 695 * @type Number 696 */ 697 _sepCount: null, 698 /** 699 * @protected 700 * @property draghandle 701 * @type HTMLElement 702 */ 703 _dragHandle: null, 704 /** 705 * @protected 706 * @property _toolbarConfigs 707 * @type Object 708 */ 709 _toolbarConfigs: { 710 renderer: true 711 }, 712 /** 713 * @protected 714 * @property CLASS_CONTAINER 715 * @description Default CSS class to apply to the toolbar container element 716 * @type String 717 */ 718 CLASS_CONTAINER: 'yui-toolbar-container', 719 /** 720 * @protected 721 * @property CLASS_DRAGHANDLE 722 * @description Default CSS class to apply to the toolbar's drag handle element 723 * @type String 724 */ 725 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle', 726 /** 727 * @protected 728 * @property CLASS_SEPARATOR 729 * @description Default CSS class to apply to all separators in the toolbar 730 * @type String 731 */ 732 CLASS_SEPARATOR: 'yui-toolbar-separator', 733 /** 734 * @protected 735 * @property CLASS_DISABLED 736 * @description Default CSS class to apply when the toolbar is disabled 737 * @type String 738 */ 739 CLASS_DISABLED: 'yui-toolbar-disabled', 740 /** 741 * @protected 742 * @property CLASS_PREFIX 743 * @description Default prefix for dynamically created class names 744 * @type String 745 */ 746 CLASS_PREFIX: 'yui-toolbar', 747 /** 748 * @method init 749 * @description The Toolbar class's initialization method 750 */ 751 init: function(p_oElement, p_oAttributes) { 752 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes); 753 }, 754 /** 755 * @method initAttributes 756 * @description Initializes all of the configuration attributes used to create 757 * the toolbar. 758 * @param {Object} attr Object literal specifying a set of 759 * configuration attributes used to create the toolbar. 760 */ 761 initAttributes: function(attr) { 762 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr); 763 this.addClass(this.CLASS_CONTAINER); 764 765 /** 766 * @attribute buttonType 767 * @description The buttonType to use (advanced or basic) 768 * @type String 769 */ 770 this.setAttributeConfig('buttonType', { 771 value: attr.buttonType || 'basic', 772 writeOnce: true, 773 validator: function(type) { 774 switch (type) { 775 case 'advanced': 776 case 'basic': 777 return true; 778 } 779 return false; 780 }, 781 method: function(type) { 782 if (type == 'advanced') { 783 if (YAHOO.widget.Button) { 784 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced; 785 } else { 786 this.buttonType = YAHOO.widget.ToolbarButton; 787 } 788 } else { 789 this.buttonType = YAHOO.widget.ToolbarButton; 790 } 791 } 792 }); 793 794 795 /** 796 * @attribute buttons 797 * @description Object specifying the buttons to include in the toolbar 798 * Example: 799 * <code><pre> 800 * { 801 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' }, 802 * { type: 'separator' }, 803 * { id: 'b4', type: 'menu', label: 'Align', value: 'align', 804 * menu: [ 805 * { text: "Left", value: 'alignleft' }, 806 * { text: "Center", value: 'aligncenter' }, 807 * { text: "Right", value: 'alignright' } 808 * ] 809 * } 810 * } 811 * </pre></code> 812 * @type Array 813 */ 814 815 this.setAttributeConfig('buttons', { 816 value: [], 817 writeOnce: true, 818 method: function(data) { 819 var i, button, buttons, len, b; 820 for (i in data) { 821 if (Lang.hasOwnProperty(data, i)) { 822 if (data[i].type == 'separator') { 823 this.addSeparator(); 824 } else if (data[i].group !== undefined) { 825 buttons = this.addButtonGroup(data[i]); 826 if (buttons) { 827 len = buttons.length; 828 for(b = 0; b < len; b++) { 829 if (buttons[b]) { 830 this._configuredButtons[this._configuredButtons.length] = buttons[b].id; 831 } 832 } 833 } 834 835 } else { 836 button = this.addButton(data[i]); 837 if (button) { 838 this._configuredButtons[this._configuredButtons.length] = button.id; 839 } 840 } 841 } 842 } 843 } 844 }); 845 846 /** 847 * @attribute disabled 848 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on. 849 * @default false 850 * @type Boolean 851 */ 852 this.setAttributeConfig('disabled', { 853 value: false, 854 method: function(disabled) { 855 if (this.get('disabled') === disabled) { 856 return false; 857 } 858 if (disabled) { 859 this.addClass(this.CLASS_DISABLED); 860 this.set('draggable', false); 861 this.disableAllButtons(); 862 } else { 863 this.removeClass(this.CLASS_DISABLED); 864 if (this._configs.draggable._initialConfig.value) { 865 //Draggable by default, set it back 866 this.set('draggable', true); 867 } 868 this.resetAllButtons(); 869 } 870 } 871 }); 872 873 /** 874 * @config cont 875 * @description The container for the toolbar. 876 * @type HTMLElement 877 */ 878 this.setAttributeConfig('cont', { 879 value: attr.cont, 880 readOnly: true 881 }); 882 883 884 /** 885 * @attribute grouplabels 886 * @description Boolean indicating if the toolbar should show the group label's text string. 887 * @default true 888 * @type Boolean 889 */ 890 this.setAttributeConfig('grouplabels', { 891 value: ((attr.grouplabels === false) ? false : true), 892 method: function(grouplabels) { 893 if (grouplabels) { 894 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels')); 895 } else { 896 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels')); 897 } 898 } 899 }); 900 /** 901 * @attribute titlebar 902 * @description Boolean indicating if the toolbar should have a titlebar. If 903 * passed a string, it will use that as the titlebar text 904 * @default false 905 * @type Boolean or String 906 */ 907 this.setAttributeConfig('titlebar', { 908 value: false, 909 method: function(titlebar) { 910 if (titlebar) { 911 if (this._titlebar && this._titlebar.parentNode) { 912 this._titlebar.parentNode.removeChild(this._titlebar); 913 } 914 this._titlebar = document.createElement('DIV'); 915 this._titlebar.tabIndex = '-1'; 916 Event.on(this._titlebar, 'focus', function() { 917 this._handleFocus(); 918 }, this, true); 919 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar'); 920 if (Lang.isString(titlebar)) { 921 var h2 = document.createElement('h2'); 922 h2.tabIndex = '-1'; 923 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>'; 924 this._titlebar.appendChild(h2); 925 Event.on(h2.firstChild, 'click', function(ev) { 926 Event.stopEvent(ev); 927 }); 928 Event.on([h2, h2.firstChild], 'focus', function() { 929 this._handleFocus(); 930 }, this, true); 931 } 932 if (this.get('firstChild')) { 933 this.insertBefore(this._titlebar, this.get('firstChild')); 934 } else { 935 this.appendChild(this._titlebar); 936 } 937 if (this.get('collapse')) { 938 this.set('collapse', true); 939 } 940 } else if (this._titlebar) { 941 if (this._titlebar && this._titlebar.parentNode) { 942 this._titlebar.parentNode.removeChild(this._titlebar); 943 } 944 } 945 } 946 }); 947 948 949 /** 950 * @attribute collapse 951 * @description Boolean indicating if the the titlebar should have a collapse button. 952 * The collapse button will not remove the toolbar, it will minimize it to the titlebar 953 * @default false 954 * @type Boolean 955 */ 956 this.setAttributeConfig('collapse', { 957 value: false, 958 method: function(collapse) { 959 if (this._titlebar) { 960 var collapseEl = null; 961 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 962 if (collapse) { 963 if (el.length > 0) { 964 //There is already a collapse button 965 return true; 966 } 967 collapseEl = document.createElement('SPAN'); 968 collapseEl.innerHTML = 'X'; 969 collapseEl.title = this.STR_COLLAPSE; 970 971 Dom.addClass(collapseEl, 'collapse'); 972 this._titlebar.appendChild(collapseEl); 973 Event.addListener(collapseEl, 'click', function() { 974 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) { 975 this.collapse(false); //Expand Toolbar 976 } else { 977 this.collapse(); //Collapse Toolbar 978 } 979 }, this, true); 980 } else { 981 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 982 if (collapseEl[0]) { 983 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) { 984 //We are closed, reopen the titlebar.. 985 this.collapse(false); //Expand Toolbar 986 } 987 collapseEl[0].parentNode.removeChild(collapseEl[0]); 988 } 989 } 990 } 991 } 992 }); 993 994 /** 995 * @attribute draggable 996 * @description Boolean indicating if the toolbar should be draggable. 997 * @default false 998 * @type Boolean 999 */ 1000 1001 this.setAttributeConfig('draggable', { 1002 value: (attr.draggable || false), 1003 method: function(draggable) { 1004 if (draggable && !this.get('titlebar')) { 1005 if (!this._dragHandle) { 1006 this._dragHandle = document.createElement('SPAN'); 1007 this._dragHandle.innerHTML = '|'; 1008 this._dragHandle.setAttribute('title', 'Click to drag the toolbar'); 1009 this._dragHandle.id = this.get('id') + '_draghandle'; 1010 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE); 1011 if (this.get('cont').hasChildNodes()) { 1012 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild); 1013 } else { 1014 this.get('cont').appendChild(this._dragHandle); 1015 } 1016 this.dd = new YAHOO.util.DD(this.get('id')); 1017 this.dd.setHandleElId(this._dragHandle.id); 1018 1019 } 1020 } else { 1021 if (this._dragHandle) { 1022 this._dragHandle.parentNode.removeChild(this._dragHandle); 1023 this._dragHandle = null; 1024 this.dd = null; 1025 } 1026 } 1027 if (this._titlebar) { 1028 if (draggable) { 1029 this.dd = new YAHOO.util.DD(this.get('id')); 1030 this.dd.setHandleElId(this._titlebar); 1031 Dom.addClass(this._titlebar, 'draggable'); 1032 } else { 1033 Dom.removeClass(this._titlebar, 'draggable'); 1034 if (this.dd) { 1035 this.dd.unreg(); 1036 this.dd = null; 1037 } 1038 } 1039 } 1040 }, 1041 validator: function(value) { 1042 var ret = true; 1043 if (!YAHOO.util.DD) { 1044 ret = false; 1045 } 1046 return ret; 1047 } 1048 }); 1049 1050 }, 1051 /** 1052 * @method addButtonGroup 1053 * @description Add a new button group to the toolbar. (uses addButton) 1054 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label) 1055 */ 1056 addButtonGroup: function(oGroup) { 1057 if (!this.get('element')) { 1058 this._queue[this._queue.length] = ['addButtonGroup', arguments]; 1059 return false; 1060 } 1061 1062 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) { 1063 this.addClass(this.CLASS_PREFIX + '-grouped'); 1064 } 1065 var div = document.createElement('DIV'); 1066 Dom.addClass(div, this.CLASS_PREFIX + '-group'); 1067 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group); 1068 if (oGroup.label) { 1069 var label = document.createElement('h3'); 1070 label.innerHTML = oGroup.label; 1071 div.appendChild(label); 1072 } 1073 if (!this.get('grouplabels')) { 1074 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels'); 1075 } 1076 1077 this.get('cont').appendChild(div); 1078 1079 //For accessibility, let's put all of the group buttons in an Unordered List 1080 var ul = document.createElement('ul'); 1081 div.appendChild(ul); 1082 1083 if (!this._buttonGroupList) { 1084 this._buttonGroupList = {}; 1085 } 1086 1087 this._buttonGroupList[oGroup.group] = ul; 1088 1089 //An array of the button ids added to this group 1090 //This is used for destruction later... 1091 var addedButtons = [], 1092 button; 1093 1094 1095 for (var i = 0; i < oGroup.buttons.length; i++) { 1096 var li = document.createElement('li'); 1097 li.className = this.CLASS_PREFIX + '-groupitem'; 1098 ul.appendChild(li); 1099 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') { 1100 this.addSeparator(li); 1101 } else { 1102 oGroup.buttons[i].container = li; 1103 button = this.addButton(oGroup.buttons[i]); 1104 if (button) { 1105 addedButtons[addedButtons.length] = button.id; 1106 } 1107 } 1108 } 1109 return addedButtons; 1110 }, 1111 /** 1112 * @method addButtonToGroup 1113 * @description Add a new button to a toolbar group. Buttons supported: 1114 * push, split, menu, select, color, spin 1115 * @param {Object} oButton Object literal reference to the Button's Config 1116 * @param {String} group The Group identifier passed into the initial config 1117 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1118 */ 1119 addButtonToGroup: function(oButton, group, after) { 1120 var groupCont = this._buttonGroupList[group], 1121 li = document.createElement('li'); 1122 1123 li.className = this.CLASS_PREFIX + '-groupitem'; 1124 oButton.container = li; 1125 this.addButton(oButton, after); 1126 groupCont.appendChild(li); 1127 }, 1128 /** 1129 * @method addButton 1130 * @description Add a new button to the toolbar. Buttons supported: 1131 * push, split, menu, select, color, spin 1132 * @param {Object} oButton Object literal reference to the Button's Config 1133 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1134 */ 1135 addButton: function(oButton, after) { 1136 if (!this.get('element')) { 1137 this._queue[this._queue.length] = ['addButton', arguments]; 1138 return false; 1139 } 1140 if (!this._buttonList) { 1141 this._buttonList = []; 1142 } 1143 if (!oButton.container) { 1144 oButton.container = this.get('cont'); 1145 } 1146 1147 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) { 1148 if (Lang.isArray(oButton.menu)) { 1149 for (var i in oButton.menu) { 1150 if (Lang.hasOwnProperty(oButton.menu, i)) { 1151 var funcObject = { 1152 fn: function(ev, x, oMenu) { 1153 if (!oButton.menucmd) { 1154 oButton.menucmd = oButton.value; 1155 } 1156 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue); 1157 }, 1158 scope: this 1159 }; 1160 oButton.menu[i].onclick = funcObject; 1161 } 1162 } 1163 } 1164 } 1165 var _oButton = {}, skip = false; 1166 for (var o in oButton) { 1167 if (Lang.hasOwnProperty(oButton, o)) { 1168 if (!this._toolbarConfigs[o]) { 1169 _oButton[o] = oButton[o]; 1170 } 1171 } 1172 } 1173 if (oButton.type == 'select') { 1174 _oButton.type = 'menu'; 1175 } 1176 if (oButton.type == 'spin') { 1177 _oButton.type = 'push'; 1178 } 1179 if (_oButton.type == 'color') { 1180 if (YAHOO.widget.Overlay) { 1181 _oButton = this._makeColorButton(_oButton); 1182 } else { 1183 skip = true; 1184 } 1185 } 1186 if (_oButton.menu) { 1187 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) { 1188 oButton.menu.showEvent.subscribe(function() { 1189 this._button = _oButton; 1190 }); 1191 } else { 1192 for (var m = 0; m < _oButton.menu.length; m++) { 1193 if (!_oButton.menu[m].value) { 1194 _oButton.menu[m].value = _oButton.menu[m].text; 1195 } 1196 } 1197 if (this.browser.webkit) { 1198 _oButton.focusmenu = false; 1199 } 1200 } 1201 } 1202 if (skip) { 1203 oButton = false; 1204 } else { 1205 //Add to .get('buttons') manually 1206 this._configs.buttons.value[this._configs.buttons.value.length] = oButton; 1207 1208 var tmp = new this.buttonType(_oButton); 1209 tmp.get('element').tabIndex = '-1'; 1210 tmp.get('element').setAttribute('role', 'button'); 1211 tmp._selected = true; 1212 1213 if (this.get('disabled')) { 1214 //Toolbar is disabled, disable the new button too! 1215 tmp.set('disabled', true); 1216 } 1217 if (!oButton.id) { 1218 oButton.id = tmp.get('id'); 1219 } 1220 1221 if (after) { 1222 var el = tmp.get('element'); 1223 var nextSib = null; 1224 if (after.get) { 1225 nextSib = after.get('element').nextSibling; 1226 } else if (after.nextSibling) { 1227 nextSib = after.nextSibling; 1228 } 1229 if (nextSib) { 1230 nextSib.parentNode.insertBefore(el, nextSib); 1231 } 1232 } 1233 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value')); 1234 1235 var icon = document.createElement('span'); 1236 icon.className = this.CLASS_PREFIX + '-icon'; 1237 tmp.get('element').insertBefore(icon, tmp.get('firstChild')); 1238 if (tmp._button.tagName.toLowerCase() == 'button') { 1239 tmp.get('element').setAttribute('unselectable', 'on'); 1240 //Replace the Button HTML Element with an a href if it exists 1241 var a = document.createElement('a'); 1242 a.innerHTML = tmp._button.innerHTML; 1243 a.href = '#'; 1244 a.tabIndex = '-1'; 1245 Event.on(a, 'click', function(ev) { 1246 Event.stopEvent(ev); 1247 }); 1248 tmp._button.parentNode.replaceChild(a, tmp._button); 1249 tmp._button = a; 1250 } 1251 1252 if (oButton.type == 'select') { 1253 if (tmp._button.tagName.toLowerCase() == 'select') { 1254 icon.parentNode.removeChild(icon); 1255 var iel = tmp._button, 1256 parEl = tmp.get('element'); 1257 parEl.parentNode.replaceChild(iel, parEl); 1258 //The 'element' value is currently the orphaned element 1259 //In order for "destroy" to execute we need to get('element') to reference the correct node. 1260 //I'm not sure if there is a direct approach to setting this value. 1261 tmp._configs.element.value = iel; 1262 } else { 1263 //Don't put a class on it if it's a real select element 1264 tmp.addClass(this.CLASS_PREFIX + '-select'); 1265 } 1266 } 1267 if (oButton.type == 'spin') { 1268 if (!Lang.isArray(oButton.range)) { 1269 oButton.range = [ 10, 100 ]; 1270 } 1271 this._makeSpinButton(tmp, oButton); 1272 } 1273 tmp.get('element').setAttribute('title', tmp.get('label')); 1274 if (oButton.type != 'spin') { 1275 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) { 1276 var showPicker = function(ev) { 1277 var exec = true; 1278 if (ev.keyCode && (ev.keyCode == 9)) { 1279 exec = false; 1280 } 1281 if (exec) { 1282 if (this._colorPicker) { 1283 this._colorPicker._button = oButton.value; 1284 } 1285 var menuEL = tmp.getMenu().element; 1286 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') { 1287 tmp.getMenu().show(); 1288 } else { 1289 tmp.getMenu().hide(); 1290 } 1291 } 1292 YAHOO.util.Event.stopEvent(ev); 1293 }; 1294 tmp.on('mousedown', showPicker, oButton, this); 1295 tmp.on('keydown', showPicker, oButton, this); 1296 1297 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) { 1298 tmp.on('keypress', this._buttonClick, oButton, this); 1299 tmp.on('mousedown', function(ev) { 1300 YAHOO.util.Event.stopEvent(ev); 1301 this._buttonClick(ev, oButton); 1302 }, oButton, this); 1303 tmp.on('click', function(ev) { 1304 YAHOO.util.Event.stopEvent(ev); 1305 }); 1306 } else { 1307 //Stop the mousedown event so we can trap the selection in the editor! 1308 tmp.on('mousedown', function(ev) { 1309 //YAHOO.util.Event.stopEvent(ev); 1310 }); 1311 tmp.on('click', function(ev) { 1312 //YAHOO.util.Event.stopEvent(ev); 1313 }); 1314 tmp.on('change', function(ev) { 1315 if (!ev.target) { 1316 if (!oButton.menucmd) { 1317 oButton.menucmd = oButton.value; 1318 } 1319 oButton.value = ev.value; 1320 this._buttonClick(ev, oButton); 1321 } 1322 }, this, true); 1323 1324 var self = this; 1325 //Hijack the mousedown event in the menu and make it fire a button click.. 1326 tmp.on('appendTo', function() { 1327 var tmp = this; 1328 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) { 1329 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) { 1330 var oMenu = args[1]; 1331 YAHOO.util.Event.stopEvent(args[0]); 1332 tmp._onMenuClick(args[0], tmp); 1333 if (!oButton.menucmd) { 1334 oButton.menucmd = oButton.value; 1335 } 1336 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue); 1337 self._buttonClick.call(self, args[1], oButton); 1338 tmp._hideMenu(); 1339 return false; 1340 }); 1341 tmp.getMenu().clickEvent.subscribe(function(ev, args) { 1342 YAHOO.util.Event.stopEvent(args[0]); 1343 }); 1344 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) { 1345 YAHOO.util.Event.stopEvent(args[0]); 1346 }); 1347 } 1348 }); 1349 1350 } 1351 } else { 1352 //Stop the mousedown event so we can trap the selection in the editor! 1353 tmp.on('mousedown', function(ev) { 1354 YAHOO.util.Event.stopEvent(ev); 1355 }); 1356 tmp.on('click', function(ev) { 1357 YAHOO.util.Event.stopEvent(ev); 1358 }); 1359 } 1360 if (this.browser.ie) { 1361 /* 1362 //Add a couple of new events for IE 1363 tmp.DOM_EVENTS.focusin = true; 1364 tmp.DOM_EVENTS.focusout = true; 1365 1366 //Stop them so we don't loose focus in the Editor 1367 tmp.on('focusin', function(ev) { 1368 YAHOO.util.Event.stopEvent(ev); 1369 }, oButton, this); 1370 1371 tmp.on('focusout', function(ev) { 1372 YAHOO.util.Event.stopEvent(ev); 1373 }, oButton, this); 1374 tmp.on('click', function(ev) { 1375 YAHOO.util.Event.stopEvent(ev); 1376 }, oButton, this); 1377 */ 1378 } 1379 if (this.browser.webkit) { 1380 //This will keep the document from gaining focus and the editor from loosing it.. 1381 //Forcefully remove the focus calls in button! 1382 tmp.hasFocus = function() { 1383 return true; 1384 }; 1385 } 1386 this._buttonList[this._buttonList.length] = tmp; 1387 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) { 1388 if (Lang.isArray(oButton.menu)) { 1389 var menu = tmp.getMenu(); 1390 if (menu && menu.renderEvent) { 1391 menu.renderEvent.subscribe(this._addMenuClasses, tmp); 1392 if (oButton.renderer) { 1393 menu.renderEvent.subscribe(oButton.renderer, tmp); 1394 } 1395 } 1396 } 1397 } 1398 } 1399 return oButton; 1400 }, 1401 /** 1402 * @method addSeparator 1403 * @description Add a new button separator to the toolbar. 1404 * @param {HTMLElement} cont Optional HTML element to insert this button into. 1405 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1406 */ 1407 addSeparator: function(cont, after) { 1408 if (!this.get('element')) { 1409 this._queue[this._queue.length] = ['addSeparator', arguments]; 1410 return false; 1411 } 1412 var sepCont = ((cont) ? cont : this.get('cont')); 1413 if (!this.get('element')) { 1414 this._queue[this._queue.length] = ['addSeparator', arguments]; 1415 return false; 1416 } 1417 if (this._sepCount === null) { 1418 this._sepCount = 0; 1419 } 1420 if (!this._sep) { 1421 this._sep = document.createElement('SPAN'); 1422 Dom.addClass(this._sep, this.CLASS_SEPARATOR); 1423 this._sep.innerHTML = '|'; 1424 } 1425 var _sep = this._sep.cloneNode(true); 1426 this._sepCount++; 1427 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount); 1428 if (after) { 1429 var nextSib = null; 1430 if (after.get) { 1431 nextSib = after.get('element').nextSibling; 1432 } else if (after.nextSibling) { 1433 nextSib = after.nextSibling; 1434 } else { 1435 nextSib = after; 1436 } 1437 if (nextSib) { 1438 if (nextSib == after) { 1439 nextSib.parentNode.appendChild(_sep); 1440 } else { 1441 nextSib.parentNode.insertBefore(_sep, nextSib); 1442 } 1443 } 1444 } else { 1445 sepCont.appendChild(_sep); 1446 } 1447 return _sep; 1448 }, 1449 /** 1450 * @method _createColorPicker 1451 * @private 1452 * @description Creates the core DOM reference to the color picker menu item. 1453 * @param {String} id the id of the toolbar to prefix this DOM container with. 1454 */ 1455 _createColorPicker: function(id) { 1456 if (Dom.get(id + '_colors')) { 1457 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors')); 1458 } 1459 var picker = document.createElement('div'); 1460 picker.className = 'yui-toolbar-colors'; 1461 picker.id = id + '_colors'; 1462 picker.style.display = 'none'; 1463 Event.on(window, 'load', function() { 1464 document.body.appendChild(picker); 1465 }, this, true); 1466 1467 this._colorPicker = picker; 1468 1469 var html = ''; 1470 for (var i in this._colorData) { 1471 if (Lang.hasOwnProperty(this._colorData, i)) { 1472 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>'; 1473 } 1474 } 1475 html += '<span><em>X</em><strong></strong></span>'; 1476 window.setTimeout(function() { 1477 picker.innerHTML = html; 1478 }, 0); 1479 1480 Event.on(picker, 'mouseover', function(ev) { 1481 var picker = this._colorPicker; 1482 var em = picker.getElementsByTagName('em')[0]; 1483 var strong = picker.getElementsByTagName('strong')[0]; 1484 var tar = Event.getTarget(ev); 1485 if (tar.tagName.toLowerCase() == 'a') { 1486 em.style.backgroundColor = tar.style.backgroundColor; 1487 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML; 1488 } 1489 }, this, true); 1490 Event.on(picker, 'focus', function(ev) { 1491 Event.stopEvent(ev); 1492 }); 1493 Event.on(picker, 'click', function(ev) { 1494 Event.stopEvent(ev); 1495 }); 1496 Event.on(picker, 'mousedown', function(ev) { 1497 Event.stopEvent(ev); 1498 var tar = Event.getTarget(ev); 1499 if (tar.tagName.toLowerCase() == 'a') { 1500 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } ); 1501 if (retVal !== false) { 1502 var info = { 1503 color: tar.innerHTML, 1504 colorName: this._colorData['#' + tar.innerHTML], 1505 value: this._colorPicker._button 1506 }; 1507 1508 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info }); 1509 } 1510 this.getButtonByValue(this._colorPicker._button).getMenu().hide(); 1511 } 1512 }, this, true); 1513 }, 1514 /** 1515 * @method _resetColorPicker 1516 * @private 1517 * @description Clears the currently selected color or mouseover color in the color picker. 1518 */ 1519 _resetColorPicker: function() { 1520 var em = this._colorPicker.getElementsByTagName('em')[0]; 1521 var strong = this._colorPicker.getElementsByTagName('strong')[0]; 1522 em.style.backgroundColor = 'transparent'; 1523 strong.innerHTML = ''; 1524 }, 1525 /** 1526 * @method _makeColorButton 1527 * @private 1528 * @description Called to turn a "color" button into a menu button with an Overlay for the menu. 1529 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference 1530 */ 1531 _makeColorButton: function(_oButton) { 1532 if (!this._colorPicker) { 1533 this._createColorPicker(this.get('id')); 1534 } 1535 _oButton.type = 'color'; 1536 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true }); 1537 _oButton.menu.setBody(''); 1538 _oButton.menu.render(this.get('cont')); 1539 Dom.addClass(_oButton.menu.element, 'yui-button-menu'); 1540 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu'); 1541 _oButton.menu.beforeShowEvent.subscribe(function() { 1542 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why. 1543 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why. 1544 //Move the DOM reference of the color picker to the Overlay that we are about to show. 1545 this._resetColorPicker(); 1546 var _p = this._colorPicker; 1547 if (_p.parentNode) { 1548 _p.parentNode.removeChild(_p); 1549 } 1550 _oButton.menu.setBody(''); 1551 _oButton.menu.appendToBody(_p); 1552 this._colorPicker.style.display = 'block'; 1553 }, this, true); 1554 return _oButton; 1555 }, 1556 /** 1557 * @private 1558 * @method _makeSpinButton 1559 * @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. 1560 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference 1561 * @param {Object} oButton Object literal containing the buttons initial config 1562 */ 1563 _makeSpinButton: function(_button, oButton) { 1564 _button.addClass(this.CLASS_PREFIX + '-spinbutton'); 1565 var self = this, 1566 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child 1567 range = oButton.range, 1568 _b1 = document.createElement('a'), 1569 _b2 = document.createElement('a'); 1570 _b1.href = '#'; 1571 _b2.href = '#'; 1572 _b1.tabIndex = '-1'; 1573 _b2.tabIndex = '-1'; 1574 1575 //Setup the up and down arrows 1576 _b1.className = 'up'; 1577 _b1.title = this.STR_SPIN_UP; 1578 _b1.innerHTML = this.STR_SPIN_UP; 1579 _b2.className = 'down'; 1580 _b2.title = this.STR_SPIN_DOWN; 1581 _b2.innerHTML = this.STR_SPIN_DOWN; 1582 1583 //Append them to the container 1584 _par.appendChild(_b1); 1585 _par.appendChild(_b2); 1586 1587 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') }); 1588 _button.set('title', label); 1589 1590 var cleanVal = function(value) { 1591 value = ((value < range[0]) ? range[0] : value); 1592 value = ((value > range[1]) ? range[1] : value); 1593 return value; 1594 }; 1595 1596 var br = this.browser; 1597 var tbar = false; 1598 var strLabel = this.STR_SPIN_LABEL; 1599 if (this._titlebar && this._titlebar.firstChild) { 1600 tbar = this._titlebar.firstChild; 1601 } 1602 1603 var _intUp = function(ev) { 1604 YAHOO.util.Event.stopEvent(ev); 1605 if (!_button.get('disabled') && (ev.keyCode != 9)) { 1606 var value = parseInt(_button.get('label'), 10); 1607 value++; 1608 value = cleanVal(value); 1609 _button.set('label', ''+value); 1610 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') }); 1611 _button.set('title', label); 1612 if (!br.webkit && tbar) { 1613 //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 1614 //_button.focus(); 1615 } 1616 self._buttonClick(ev, oButton); 1617 } 1618 }; 1619 1620 var _intDown = function(ev) { 1621 YAHOO.util.Event.stopEvent(ev); 1622 if (!_button.get('disabled') && (ev.keyCode != 9)) { 1623 var value = parseInt(_button.get('label'), 10); 1624 value--; 1625 value = cleanVal(value); 1626 1627 _button.set('label', ''+value); 1628 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') }); 1629 _button.set('title', label); 1630 if (!br.webkit && tbar) { 1631 //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 1632 //_button.focus(); 1633 } 1634 self._buttonClick(ev, oButton); 1635 } 1636 }; 1637 1638 var _intKeyUp = function(ev) { 1639 if (ev.keyCode == 38) { 1640 _intUp(ev); 1641 } else if (ev.keyCode == 40) { 1642 _intDown(ev); 1643 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key 1644 _intUp(ev); 1645 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key 1646 _intDown(ev); 1647 } 1648 }; 1649 1650 //Handle arrow keys.. 1651 _button.on('keydown', _intKeyUp, this, true); 1652 1653 //Listen for the click on the up button and act on it 1654 //Listen for the click on the down button and act on it 1655 Event.on(_b1, 'mousedown',function(ev) { 1656 Event.stopEvent(ev); 1657 }, this, true); 1658 Event.on(_b2, 'mousedown', function(ev) { 1659 Event.stopEvent(ev); 1660 }, this, true); 1661 Event.on(_b1, 'click', _intUp, this, true); 1662 Event.on(_b2, 'click', _intDown, this, true); 1663 }, 1664 /** 1665 * @protected 1666 * @method _buttonClick 1667 * @description Click handler for all buttons in the toolbar. 1668 * @param {String} ev The event that was passed in. 1669 * @param {Object} info Object literal of information about the button that was clicked. 1670 */ 1671 _buttonClick: function(ev, info) { 1672 var doEvent = true; 1673 1674 if (ev && ev.type == 'keypress') { 1675 if (ev.keyCode == 9) { 1676 doEvent = false; 1677 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) { 1678 } else { 1679 doEvent = false; 1680 } 1681 } 1682 1683 if (doEvent) { 1684 var fireNextEvent = true, 1685 retValue = false; 1686 1687 info.isSelected = this.isSelected(info.id); 1688 1689 if (info.value) { 1690 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info }); 1691 if (retValue === false) { 1692 fireNextEvent = false; 1693 } 1694 } 1695 1696 if (info.menucmd && fireNextEvent) { 1697 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info }); 1698 if (retValue === false) { 1699 fireNextEvent = false; 1700 } 1701 } 1702 if (fireNextEvent) { 1703 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info }); 1704 } 1705 1706 if (info.type == 'select') { 1707 var button = this.getButtonById(info.id); 1708 if (button.buttonType == 'rich') { 1709 var txt = info.value; 1710 for (var i = 0; i < info.menu.length; i++) { 1711 if (info.menu[i].value == info.value) { 1712 txt = info.menu[i].text; 1713 break; 1714 } 1715 } 1716 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>'); 1717 var _items = button.getMenu().getItems(); 1718 for (var m = 0; m < _items.length; m++) { 1719 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) { 1720 _items[m].cfg.setProperty('checked', true); 1721 } else { 1722 _items[m].cfg.setProperty('checked', false); 1723 } 1724 } 1725 } 1726 } 1727 if (ev) { 1728 Event.stopEvent(ev); 1729 } 1730 } 1731 }, 1732 /** 1733 * @private 1734 * @property _keyNav 1735 * @description Flag to determine if the arrow nav listeners have been attached 1736 * @type Boolean 1737 */ 1738 _keyNav: null, 1739 /** 1740 * @private 1741 * @property _navCounter 1742 * @description Internal counter for walking the buttons in the toolbar with the arrow keys 1743 * @type Number 1744 */ 1745 _navCounter: null, 1746 /** 1747 * @private 1748 * @method _navigateButtons 1749 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys 1750 * @param {Event} ev The Key Event 1751 */ 1752 _navigateButtons: function(ev) { 1753 switch (ev.keyCode) { 1754 case 37: 1755 case 39: 1756 if (ev.keyCode == 37) { 1757 this._navCounter--; 1758 } else { 1759 this._navCounter++; 1760 } 1761 if (this._navCounter > (this._buttonList.length - 1)) { 1762 this._navCounter = 0; 1763 } 1764 if (this._navCounter < 0) { 1765 this._navCounter = (this._buttonList.length - 1); 1766 } 1767 if (this._buttonList[this._navCounter]) { 1768 var el = this._buttonList[this._navCounter].get('element'); 1769 if (this.browser.ie) { 1770 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0]; 1771 } 1772 if (this._buttonList[this._navCounter].get('disabled')) { 1773 this._navigateButtons(ev); 1774 } else { 1775 el.focus(); 1776 } 1777 } 1778 break; 1779 } 1780 }, 1781 /** 1782 * @private 1783 * @method _handleFocus 1784 * @description Sets up the listeners for the arrow key navigation 1785 */ 1786 _handleFocus: function() { 1787 if (!this._keyNav) { 1788 var ev = 'keypress'; 1789 if (this.browser.ie) { 1790 ev = 'keydown'; 1791 } 1792 Event.on(this.get('element'), ev, this._navigateButtons, this, true); 1793 this._keyNav = true; 1794 this._navCounter = -1; 1795 } 1796 }, 1797 /** 1798 * @method getButtonById 1799 * @description Gets a button instance from the toolbar by is Dom id. 1800 * @param {String} id The Dom id to query for. 1801 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>} 1802 */ 1803 getButtonById: function(id) { 1804 var len = this._buttonList.length; 1805 for (var i = 0; i < len; i++) { 1806 if (this._buttonList[i] && this._buttonList[i].get('id') == id) { 1807 return this._buttonList[i]; 1808 } 1809 } 1810 return false; 1811 }, 1812 /** 1813 * @method getButtonByValue 1814 * @description Gets a button instance or a menuitem instance from the toolbar by it's value. 1815 * @param {String} value The button value to query for. 1816 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>} 1817 */ 1818 getButtonByValue: function(value) { 1819 var _buttons = this.get('buttons'); 1820 if (!_buttons) { 1821 return false; 1822 } 1823 var len = _buttons.length; 1824 for (var i = 0; i < len; i++) { 1825 if (_buttons[i].group !== undefined) { 1826 for (var m = 0; m < _buttons[i].buttons.length; m++) { 1827 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) { 1828 return this.getButtonById(_buttons[i].buttons[m].id); 1829 } 1830 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values 1831 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) { 1832 if (_buttons[i].buttons[m].menu[s].value == value) { 1833 return this.getButtonById(_buttons[i].buttons[m].id); 1834 } 1835 } 1836 } 1837 } 1838 } else { 1839 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) { 1840 return this.getButtonById(_buttons[i].id); 1841 } 1842 if (_buttons[i].menu) { //Menu Button, loop through the values 1843 for (var j = 0; j < _buttons[i].menu.length; j++) { 1844 if (_buttons[i].menu[j].value == value) { 1845 return this.getButtonById(_buttons[i].id); 1846 } 1847 } 1848 } 1849 } 1850 } 1851 return false; 1852 }, 1853 /** 1854 * @method getButtonByIndex 1855 * @description Gets a button instance from the toolbar by is index in _buttonList. 1856 * @param {Number} index The index of the button in _buttonList. 1857 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>} 1858 */ 1859 getButtonByIndex: function(index) { 1860 if (this._buttonList[index]) { 1861 return this._buttonList[index]; 1862 } else { 1863 return false; 1864 } 1865 }, 1866 /** 1867 * @method getButtons 1868 * @description Returns an array of buttons in the current toolbar 1869 * @return {Array} 1870 */ 1871 getButtons: function() { 1872 return this._buttonList; 1873 }, 1874 /** 1875 * @method disableButton 1876 * @description Disables a button in the toolbar. 1877 * @param {String/Number} id Disable a button by it's id, index or value. 1878 * @return {Boolean} 1879 */ 1880 disableButton: function(id) { 1881 var button = getButton.call(this, id); 1882 if (button) { 1883 button.set('disabled', true); 1884 } else { 1885 return false; 1886 } 1887 }, 1888 /** 1889 * @method enableButton 1890 * @description Enables a button in the toolbar. 1891 * @param {String/Number} id Enable a button by it's id, index or value. 1892 * @return {Boolean} 1893 */ 1894 enableButton: function(id) { 1895 if (this.get('disabled')) { 1896 return false; 1897 } 1898 var button = getButton.call(this, id); 1899 if (button) { 1900 if (button.get('disabled')) { 1901 button.set('disabled', false); 1902 } 1903 } else { 1904 return false; 1905 } 1906 }, 1907 /** 1908 * @method isSelected 1909 * @description Tells if a button is selected or not. 1910 * @param {String/Number} id A button by it's id, index or value. 1911 * @return {Boolean} 1912 */ 1913 isSelected: function(id) { 1914 var button = getButton.call(this, id); 1915 if (button) { 1916 return button._selected; 1917 } 1918 return false; 1919 }, 1920 /** 1921 * @method selectButton 1922 * @description Selects a button in the toolbar. 1923 * @param {String/Number} id Select a button by it's id, index or value. 1924 * @param {String} value If this is a Menu Button, check this item in the menu 1925 * @return {Boolean} 1926 */ 1927 selectButton: function(id, value) { 1928 var button = getButton.call(this, id); 1929 if (button) { 1930 button.addClass('yui-button-selected'); 1931 button.addClass('yui-button-' + button.get('value') + '-selected'); 1932 button._selected = true; 1933 if (value) { 1934 if (button.buttonType == 'rich') { 1935 var _items = button.getMenu().getItems(); 1936 for (var m = 0; m < _items.length; m++) { 1937 if (_items[m].value == value) { 1938 _items[m].cfg.setProperty('checked', true); 1939 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>'); 1940 } else { 1941 _items[m].cfg.setProperty('checked', false); 1942 } 1943 } 1944 } 1945 } 1946 } else { 1947 return false; 1948 } 1949 }, 1950 /** 1951 * @method deselectButton 1952 * @description Deselects a button in the toolbar. 1953 * @param {String/Number} id Deselect a button by it's id, index or value. 1954 * @return {Boolean} 1955 */ 1956 deselectButton: function(id) { 1957 var button = getButton.call(this, id); 1958 if (button) { 1959 button.removeClass('yui-button-selected'); 1960 button.removeClass('yui-button-' + button.get('value') + '-selected'); 1961 button.removeClass('yui-button-hover'); 1962 button._selected = false; 1963 } else { 1964 return false; 1965 } 1966 }, 1967 /** 1968 * @method deselectAllButtons 1969 * @description Deselects all buttons in the toolbar. 1970 * @return {Boolean} 1971 */ 1972 deselectAllButtons: function() { 1973 var len = this._buttonList.length; 1974 for (var i = 0; i < len; i++) { 1975 this.deselectButton(this._buttonList[i]); 1976 } 1977 }, 1978 /** 1979 * @method disableAllButtons 1980 * @description Disables all buttons in the toolbar. 1981 * @return {Boolean} 1982 */ 1983 disableAllButtons: function() { 1984 if (this.get('disabled')) { 1985 return false; 1986 } 1987 var len = this._buttonList.length; 1988 for (var i = 0; i < len; i++) { 1989 this.disableButton(this._buttonList[i]); 1990 } 1991 }, 1992 /** 1993 * @method enableAllButtons 1994 * @description Enables all buttons in the toolbar. 1995 * @return {Boolean} 1996 */ 1997 enableAllButtons: function() { 1998 if (this.get('disabled')) { 1999 return false; 2000 } 2001 var len = this._buttonList.length; 2002 for (var i = 0; i < len; i++) { 2003 this.enableButton(this._buttonList[i]); 2004 } 2005 }, 2006 /** 2007 * @method resetAllButtons 2008 * @description Resets all buttons to their initial state. 2009 * @param {Object} _ex Except these buttons 2010 * @return {Boolean} 2011 */ 2012 resetAllButtons: function(_ex) { 2013 if (!Lang.isObject(_ex)) { 2014 _ex = {}; 2015 } 2016 if (this.get('disabled') || !this._buttonList) { 2017 return false; 2018 } 2019 var len = this._buttonList.length; 2020 for (var i = 0; i < len; i++) { 2021 var _button = this._buttonList[i]; 2022 if (_button) { 2023 var disabled = _button._configs.disabled._initialConfig.value; 2024 if (_ex[_button.get('id')]) { 2025 this.enableButton(_button); 2026 this.selectButton(_button); 2027 } else { 2028 if (disabled) { 2029 this.disableButton(_button); 2030 } else { 2031 this.enableButton(_button); 2032 } 2033 this.deselectButton(_button); 2034 } 2035 } 2036 } 2037 }, 2038 /** 2039 * @method destroyButton 2040 * @description Destroy a button in the toolbar. 2041 * @param {String/Number} id Destroy a button by it's id or index. 2042 * @return {Boolean} 2043 */ 2044 destroyButton: function(id) { 2045 var button = getButton.call(this, id); 2046 if (button) { 2047 var thisID = button.get('id'), 2048 new_list = [], i = 0, 2049 len = this._buttonList.length; 2050 2051 button.destroy(); 2052 2053 for (i = 0; i < len; i++) { 2054 if (this._buttonList[i].get('id') != thisID) { 2055 new_list[new_list.length]= this._buttonList[i]; 2056 } 2057 } 2058 2059 this._buttonList = new_list; 2060 } else { 2061 return false; 2062 } 2063 }, 2064 /** 2065 * @method destroy 2066 * @description Destroys the toolbar, all of it's elements and objects. 2067 * @return {Boolean} 2068 */ 2069 destroy: function() { 2070 var len = this._configuredButtons.length, j, i, b; 2071 for(b = 0; b < len; b++) { 2072 this.destroyButton(this._configuredButtons[b]); 2073 } 2074 2075 this._configuredButtons = null; 2076 2077 this.get('element').innerHTML = ''; 2078 this.get('element').className = ''; 2079 //Brutal Object Destroy 2080 for (i in this) { 2081 if (Lang.hasOwnProperty(this, i)) { 2082 this[i] = null; 2083 } 2084 } 2085 return true; 2086 }, 2087 /** 2088 * @method collapse 2089 * @description Programatically collapse the toolbar. 2090 * @param {Boolean} collapse True to collapse, false to expand. 2091 */ 2092 collapse: function(collapse) { 2093 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 2094 if (collapse === false) { 2095 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed'); 2096 if (el[0]) { 2097 Dom.removeClass(el[0], 'collapsed'); 2098 el[0].title = this.STR_COLLAPSE; 2099 } 2100 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this }); 2101 } else { 2102 if (el[0]) { 2103 Dom.addClass(el[0], 'collapsed'); 2104 el[0].title = this.STR_EXPAND; 2105 } 2106 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed'); 2107 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this }); 2108 } 2109 }, 2110 /** 2111 * @method toString 2112 * @description Returns a string representing the toolbar. 2113 * @return {String} 2114 */ 2115 toString: function() { 2116 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.'; 2117 } 2118 }); 2119 /** 2120 * @event buttonClick 2121 * @param {Object} o The object passed to this handler is the button config used to create the button. 2122 * @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. 2123 * @type YAHOO.util.CustomEvent 2124 */ 2125 /** 2126 * @event valueClick 2127 * @param {Object} o The object passed to this handler is the button config used to create the button. 2128 * @description This is a special dynamic event that is created and dispatched based on the value property 2129 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 2130 * Example: 2131 * <code><pre> 2132 * buttons : [ 2133 * { type: 'button', value: 'test', value: 'testButton' } 2134 * ]</pre> 2135 * </code> 2136 * With the valueClick event you could subscribe to this buttons click event with this: 2137 * tbar.in('testButtonClick', function() { alert('test button clicked'); }) 2138 * @type YAHOO.util.CustomEvent 2139 */ 2140 /** 2141 * @event toolbarExpanded 2142 * @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. 2143 * @type YAHOO.util.CustomEvent 2144 */ 2145 /** 2146 * @event toolbarCollapsed 2147 * @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. 2148 * @type YAHOO.util.CustomEvent 2149 */ 2150 })(); 2151 /** 2152 * @module editor 2153 * @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> 2154 * @namespace YAHOO.widget 2155 * @requires yahoo, dom, element, event, toolbar 2156 * @optional animation, container_core, resize, dragdrop 2157 */ 2158 2159 (function() { 2160 var Dom = YAHOO.util.Dom, 2161 Event = YAHOO.util.Event, 2162 Lang = YAHOO.lang, 2163 Toolbar = YAHOO.widget.Toolbar; 2164 2165 /** 2166 * 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. 2167 * @constructor 2168 * @class SimpleEditor 2169 * @extends YAHOO.util.Element 2170 * @param {String/HTMLElement} el The textarea element to turn into an editor. 2171 * @param {Object} attrs Object liternal containing configuration parameters. 2172 */ 2173 2174 YAHOO.widget.SimpleEditor = function(el, attrs) { 2175 2176 var o = {}; 2177 if (Lang.isObject(el) && (!el.tagName) && !attrs) { 2178 Lang.augmentObject(o, el); //Break the config reference 2179 el = document.createElement('textarea'); 2180 this.DOMReady = true; 2181 if (o.container) { 2182 var c = Dom.get(o.container); 2183 c.appendChild(el); 2184 } else { 2185 document.body.appendChild(el); 2186 } 2187 } else { 2188 if (attrs) { 2189 Lang.augmentObject(o, attrs); //Break the config reference 2190 } 2191 } 2192 2193 var oConfig = { 2194 element: null, 2195 attributes: o 2196 }, id = null; 2197 2198 if (Lang.isString(el)) { 2199 id = el; 2200 } else { 2201 if (oConfig.attributes.id) { 2202 id = oConfig.attributes.id; 2203 } else { 2204 this.DOMReady = true; 2205 id = Dom.generateId(el); 2206 } 2207 } 2208 oConfig.element = el; 2209 2210 var element_cont = document.createElement('DIV'); 2211 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, { 2212 id: id + '_container' 2213 }); 2214 var div = document.createElement('div'); 2215 Dom.addClass(div, 'first-child'); 2216 oConfig.attributes.element_cont.appendChild(div); 2217 2218 if (!oConfig.attributes.toolbar_cont) { 2219 oConfig.attributes.toolbar_cont = document.createElement('DIV'); 2220 oConfig.attributes.toolbar_cont.id = id + '_toolbar'; 2221 div.appendChild(oConfig.attributes.toolbar_cont); 2222 } 2223 var editorWrapper = document.createElement('DIV'); 2224 div.appendChild(editorWrapper); 2225 oConfig.attributes.editor_wrapper = editorWrapper; 2226 2227 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 2228 }; 2229 2230 2231 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, { 2232 /** 2233 * @private 2234 * @property _resizeConfig 2235 * @description The default config for the Resize Utility 2236 */ 2237 _resizeConfig: { 2238 handles: ['br'], 2239 autoRatio: true, 2240 status: true, 2241 proxy: true, 2242 useShim: true, 2243 setSize: false 2244 }, 2245 /** 2246 * @private 2247 * @method _setupResize 2248 * @description Creates the Resize instance and binds its events. 2249 */ 2250 _setupResize: function() { 2251 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; } 2252 if (this.get('resize')) { 2253 var config = {}; 2254 Lang.augmentObject(config, this._resizeConfig); //Break the config reference 2255 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config); 2256 this.resize.on('resize', function(args) { 2257 var anim = this.get('animate'); 2258 this.set('animate', false); 2259 this.set('width', args.width + 'px'); 2260 var h = args.height, 2261 th = (this.toolbar.get('element').clientHeight + 2), 2262 dh = 0; 2263 if (this.dompath) { 2264 dh = (this.dompath.clientHeight + 1); //It has a 1px top border.. 2265 } 2266 var newH = (h - th - dh); 2267 this.set('height', newH + 'px'); 2268 this.get('element_cont').setStyle('height', ''); 2269 this.set('animate', anim); 2270 }, this, true); 2271 } 2272 }, 2273 /** 2274 * @property resize 2275 * @description A reference to the Resize object 2276 * @type YAHOO.util.Resize 2277 */ 2278 resize: null, 2279 /** 2280 * @private 2281 * @method _setupDD 2282 * @description Sets up the DD instance used from the 'drag' config option. 2283 */ 2284 _setupDD: function() { 2285 if (!YAHOO.util.DD) { return false; } 2286 if (this.get('drag')) { 2287 var d = this.get('drag'), 2288 dd = YAHOO.util.DD; 2289 if (d === 'proxy') { 2290 dd = YAHOO.util.DDProxy; 2291 } 2292 2293 this.dd = new dd(this.get('element_cont').get('element')); 2294 this.toolbar.addClass('draggable'); 2295 this.dd.setHandleElId(this.toolbar._titlebar); 2296 } 2297 }, 2298 /** 2299 * @property dd 2300 * @description A reference to the DragDrop object. 2301 * @type YAHOO.util.DD/YAHOO.util.DDProxy 2302 */ 2303 dd: null, 2304 /** 2305 * @private 2306 * @property _lastCommand 2307 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level) 2308 * @type String 2309 */ 2310 _lastCommand: null, 2311 _undoNodeChange: function() {}, 2312 _storeUndo: function() {}, 2313 /** 2314 * @private 2315 * @method _checkKey 2316 * @description Checks a keyMap entry against a key event 2317 * @param {Object} k The _keyMap object 2318 * @param {Event} e The Mouse Event 2319 * @return {Boolean} 2320 */ 2321 _checkKey: function(k, e) { 2322 var ret = false; 2323 if ((e.keyCode === k.key)) { 2324 if (k.mods && (k.mods.length > 0)) { 2325 var val = 0; 2326 for (var i = 0; i < k.mods.length; i++) { 2327 if (this.browser.mac) { 2328 if (k.mods[i] == 'ctrl') { 2329 k.mods[i] = 'meta'; 2330 } 2331 } 2332 if (e[k.mods[i] + 'Key'] === true) { 2333 val++; 2334 } 2335 } 2336 if (val === k.mods.length) { 2337 ret = true; 2338 } 2339 } else { 2340 ret = true; 2341 } 2342 } 2343 return ret; 2344 }, 2345 /** 2346 * @private 2347 * @property _keyMap 2348 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>. 2349 * This entry shows that when key 87 (W) is found with the modifiers of shift and control, the window will close. You can customize this object to tweak keyboard shortcuts. 2350 * @type {Object/Mixed} 2351 */ 2352 _keyMap: { 2353 SELECT_ALL: { 2354 key: 65, //A key 2355 mods: ['ctrl'] 2356 }, 2357 CLOSE_WINDOW: { 2358 key: 87, //W key 2359 mods: ['shift', 'ctrl'] 2360 }, 2361 FOCUS_TOOLBAR: { 2362 key: 27, 2363 mods: ['shift'] 2364 }, 2365 FOCUS_AFTER: { 2366 key: 27 2367 }, 2368 FONT_SIZE_UP: { 2369 key: 38, 2370 mods: ['shift', 'ctrl'] 2371 }, 2372 FONT_SIZE_DOWN: { 2373 key: 40, 2374 mods: ['shift', 'ctrl'] 2375 }, 2376 CREATE_LINK: { 2377 key: 76, 2378 mods: ['shift', 'ctrl'] 2379 }, 2380 BOLD: { 2381 key: 66, 2382 mods: ['shift', 'ctrl'] 2383 }, 2384 ITALIC: { 2385 key: 73, 2386 mods: ['shift', 'ctrl'] 2387 }, 2388 UNDERLINE: { 2389 key: 85, 2390 mods: ['shift', 'ctrl'] 2391 }, 2392 UNDO: { 2393 key: 90, 2394 mods: ['ctrl'] 2395 }, 2396 REDO: { 2397 key: 90, 2398 mods: ['shift', 'ctrl'] 2399 }, 2400 JUSTIFY_LEFT: { 2401 key: 219, 2402 mods: ['shift', 'ctrl'] 2403 }, 2404 JUSTIFY_CENTER: { 2405 key: 220, 2406 mods: ['shift', 'ctrl'] 2407 }, 2408 JUSTIFY_RIGHT: { 2409 key: 221, 2410 mods: ['shift', 'ctrl'] 2411 } 2412 }, 2413 /** 2414 * @private 2415 * @method _cleanClassName 2416 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s. 2417 * @param {String} str The classname to clean up 2418 * @return {String} 2419 */ 2420 _cleanClassName: function(str) { 2421 return str.replace(/ /g, '-').toLowerCase(); 2422 }, 2423 /** 2424 * @property _textarea 2425 * @description Flag to determine if we are using a textarea or an HTML Node. 2426 * @type Boolean 2427 */ 2428 _textarea: null, 2429 /** 2430 * @property _docType 2431 * @description The DOCTYPE to use in the editable container. 2432 * @type String 2433 */ 2434 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">', 2435 /** 2436 * @property editorDirty 2437 * @description This flag will be set when certain things in the Editor happen. It is to be used by the developer to check to see if content has changed. 2438 * @type Boolean 2439 */ 2440 editorDirty: null, 2441 /** 2442 * @property _defaultCSS 2443 * @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' } 2444 * @type String 2445 */ 2446 _defaultCSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } .warning-localfile { border-bottom: 1px dashed red !important; } .yui-busy { cursor: wait !important; } img.selected { border: 2px dotted #808080; } img { cursor: pointer !important; border: none; } body.ptags.webkit div.yui-wk-p { margin: 11px 0; } body.ptags.webkit div.yui-wk-div { margin: 0; }', 2447 /** 2448 * @property _defaultToolbar 2449 * @private 2450 * @description Default toolbar config. 2451 * @type Object 2452 */ 2453 _defaultToolbar: null, 2454 /** 2455 * @property _lastButton 2456 * @private 2457 * @description The last button pressed, so we don't disable it. 2458 * @type Object 2459 */ 2460 _lastButton: null, 2461 /** 2462 * @property _baseHREF 2463 * @private 2464 * @description The base location of the editable page (this page) so that relative paths for image work. 2465 * @type String 2466 */ 2467 _baseHREF: function() { 2468 var href = document.location.href; 2469 if (href.indexOf('?') !== -1) { //Remove the query string 2470 href = href.substring(0, href.indexOf('?')); 2471 } 2472 href = href.substring(0, href.lastIndexOf('/')) + '/'; 2473 return href; 2474 }(), 2475 /** 2476 * @property _lastImage 2477 * @private 2478 * @description Safari reference for the last image selected (for styling as selected). 2479 * @type HTMLElement 2480 */ 2481 _lastImage: null, 2482 /** 2483 * @property _blankImageLoaded 2484 * @private 2485 * @description Don't load the blank image more than once.. 2486 * @type Boolean 2487 */ 2488 _blankImageLoaded: null, 2489 /** 2490 * @property _fixNodesTimer 2491 * @private 2492 * @description Holder for the fixNodes timer 2493 * @type Date 2494 */ 2495 _fixNodesTimer: null, 2496 /** 2497 * @property _nodeChangeTimer 2498 * @private 2499 * @description Holds a reference to the nodeChange setTimeout call 2500 * @type Number 2501 */ 2502 _nodeChangeTimer: null, 2503 /** 2504 * @property _nodeChangeDelayTimer 2505 * @private 2506 * @description Holds a reference to the nodeChangeDelay setTimeout call 2507 * @type Number 2508 */ 2509 _nodeChangeDelayTimer: null, 2510 /** 2511 * @property _lastNodeChangeEvent 2512 * @private 2513 * @description Flag to determine the last event that fired a node change 2514 * @type Event 2515 */ 2516 _lastNodeChangeEvent: null, 2517 /** 2518 * @property _lastNodeChange 2519 * @private 2520 * @description Flag to determine when the last node change was fired 2521 * @type Date 2522 */ 2523 _lastNodeChange: 0, 2524 /** 2525 * @property _rendered 2526 * @private 2527 * @description Flag to determine if editor has been rendered or not 2528 * @type Boolean 2529 */ 2530 _rendered: null, 2531 /** 2532 * @property DOMReady 2533 * @private 2534 * @description Flag to determine if DOM is ready or not 2535 * @type Boolean 2536 */ 2537 DOMReady: null, 2538 /** 2539 * @property _selection 2540 * @private 2541 * @description Holder for caching iframe selections 2542 * @type Object 2543 */ 2544 _selection: null, 2545 /** 2546 * @property _mask 2547 * @private 2548 * @description DOM Element holder for the editor Mask when disabled 2549 * @type Object 2550 */ 2551 _mask: null, 2552 /** 2553 * @property _showingHiddenElements 2554 * @private 2555 * @description Status of the hidden elements button 2556 * @type Boolean 2557 */ 2558 _showingHiddenElements: null, 2559 /** 2560 * @property currentWindow 2561 * @description A reference to the currently open EditorWindow 2562 * @type Object 2563 */ 2564 currentWindow: null, 2565 /** 2566 * @property currentEvent 2567 * @description A reference to the current editor event 2568 * @type Event 2569 */ 2570 currentEvent: null, 2571 /** 2572 * @property operaEvent 2573 * @private 2574 * @description setTimeout holder for Opera and Image DoubleClick event.. 2575 * @type Object 2576 */ 2577 operaEvent: null, 2578 /** 2579 * @property currentFont 2580 * @description A reference to the last font selected from the Toolbar 2581 * @type HTMLElement 2582 */ 2583 currentFont: null, 2584 /** 2585 * @property currentElement 2586 * @description A reference to the current working element in the editor 2587 * @type Array 2588 */ 2589 currentElement: null, 2590 /** 2591 * @property dompath 2592 * @description A reference to the dompath container for writing the current working dom path to. 2593 * @type HTMLElement 2594 */ 2595 dompath: null, 2596 /** 2597 * @property beforeElement 2598 * @description A reference to the H2 placed before the editor for Accessibilty. 2599 * @type HTMLElement 2600 */ 2601 beforeElement: null, 2602 /** 2603 * @property afterElement 2604 * @description A reference to the H2 placed after the editor for Accessibilty. 2605 * @type HTMLElement 2606 */ 2607 afterElement: null, 2608 /** 2609 * @property invalidHTML 2610 * @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found. If you set the value of a key to "{ keepContents: true }", then the element will be replaced with a yui-non span to be filtered out when cleanHTML is called. The only tag that is ignored here is the span tag as it will force the Editor into a loop and freeze the browser. However.. all of these tags will be removed in the cleanHTML routine. 2611 * @type Object 2612 */ 2613 invalidHTML: { 2614 form: true, 2615 input: true, 2616 button: true, 2617 select: true, 2618 link: true, 2619 html: true, 2620 body: true, 2621 iframe: true, 2622 script: true, 2623 style: true, 2624 textarea: true 2625 }, 2626 /** 2627 * @property toolbar 2628 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance 2629 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> 2630 */ 2631 toolbar: null, 2632 /** 2633 * @private 2634 * @property _contentTimer 2635 * @description setTimeout holder for documentReady check 2636 */ 2637 _contentTimer: null, 2638 /** 2639 * @private 2640 * @property _contentTimerMax 2641 * @description The number of times the loaded content should be checked before giving up. Default: 500 2642 */ 2643 _contentTimerMax: 500, 2644 /** 2645 * @private 2646 * @property _contentTimerCounter 2647 * @description Counter to check the number of times the body is polled for before giving up 2648 * @type Number 2649 */ 2650 _contentTimerCounter: 0, 2651 /** 2652 * @private 2653 * @property _disabled 2654 * @description The Toolbar items that should be disabled if there is no selection present in the editor. 2655 * @type Array 2656 */ 2657 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ], 2658 /** 2659 * @private 2660 * @property _alwaysDisabled 2661 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor. 2662 * @type Object 2663 */ 2664 _alwaysDisabled: { undo: true, redo: true }, 2665 /** 2666 * @private 2667 * @property _alwaysEnabled 2668 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor. 2669 * @type Object 2670 */ 2671 _alwaysEnabled: { }, 2672 /** 2673 * @private 2674 * @property _semantic 2675 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles. 2676 * @type Object 2677 */ 2678 _semantic: { 'bold': true, 'italic' : true, 'underline' : true }, 2679 /** 2680 * @private 2681 * @property _tag2cmd 2682 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button. 2683 * @type Object 2684 */ 2685 _tag2cmd: { 2686 'b': 'bold', 2687 'strong': 'bold', 2688 'i': 'italic', 2689 'em': 'italic', 2690 'u': 'underline', 2691 'sup': 'superscript', 2692 'sub': 'subscript', 2693 'img': 'insertimage', 2694 'a' : 'createlink', 2695 'ul' : 'insertunorderedlist', 2696 'ol' : 'insertorderedlist' 2697 }, 2698 2699 /** 2700 * @private _createIframe 2701 * @description Creates the DOM and YUI Element for the iFrame editor area. 2702 * @param {String} id The string ID to prefix the iframe with 2703 * @return {Object} iFrame object 2704 */ 2705 _createIframe: function() { 2706 var ifrmDom = document.createElement('iframe'); 2707 ifrmDom.id = this.get('id') + '_editor'; 2708 var config = { 2709 border: '0', 2710 frameBorder: '0', 2711 marginWidth: '0', 2712 marginHeight: '0', 2713 leftMargin: '0', 2714 topMargin: '0', 2715 allowTransparency: 'true', 2716 width: '100%' 2717 }; 2718 if (this.get('autoHeight')) { 2719 config.scrolling = 'no'; 2720 } 2721 for (var i in config) { 2722 if (Lang.hasOwnProperty(config, i)) { 2723 ifrmDom.setAttribute(i, config[i]); 2724 } 2725 } 2726 var isrc = 'javascript:;'; 2727 if (this.browser.ie) { 2728 //isrc = 'about:blank'; 2729 //TODO - Check this, I have changed it before.. 2730 isrc = 'javascript:false;'; 2731 } 2732 ifrmDom.setAttribute('src', isrc); 2733 var ifrm = new YAHOO.util.Element(ifrmDom); 2734 ifrm.setStyle('visibility', 'hidden'); 2735 return ifrm; 2736 }, 2737 /** 2738 * @private _isElement 2739 * @description Checks to see if an Element reference is a valid one and has a certain tag type 2740 * @param {HTMLElement} el The element to check 2741 * @param {String} tag The tag that the element needs to be 2742 * @return {Boolean} 2743 */ 2744 _isElement: function(el, tag) { 2745 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) { 2746 return true; 2747 } 2748 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) { 2749 return true; 2750 } 2751 return false; 2752 }, 2753 /** 2754 * @private _hasParent 2755 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type 2756 * @param {HTMLElement} el The element to check 2757 * @param {String} tag The tag that the element needs to be 2758 * @return HTMLElement 2759 */ 2760 _hasParent: function(el, tag) { 2761 if (!el || !el.parentNode) { 2762 return false; 2763 } 2764 2765 while (el.parentNode) { 2766 if (this._isElement(el, tag)) { 2767 return el; 2768 } 2769 if (el.parentNode) { 2770 el = el.parentNode; 2771 } else { 2772 return false; 2773 } 2774 } 2775 return false; 2776 }, 2777 /** 2778 * @private 2779 * @method _getDoc 2780 * @description Get the Document of the IFRAME 2781 * @return {Object} 2782 */ 2783 _getDoc: function() { 2784 var value = false; 2785 try { 2786 if (this.get('iframe').get('element').contentWindow.document) { 2787 value = this.get('iframe').get('element').contentWindow.document; 2788 return value; 2789 } 2790 } catch (e) { 2791 return false; 2792 } 2793 }, 2794 /** 2795 * @private 2796 * @method _getWindow 2797 * @description Get the Window of the IFRAME 2798 * @return {Object} 2799 */ 2800 _getWindow: function() { 2801 return this.get('iframe').get('element').contentWindow; 2802 }, 2803 /** 2804 * @method focus 2805 * @description Attempt to set the focus of the iframes window. 2806 */ 2807 focus: function() { 2808 this._getWindow().focus(); 2809 }, 2810 /** 2811 * @private 2812 * @depreciated - This should not be used, moved to this.focus(); 2813 * @method _focusWindow 2814 * @description Attempt to set the focus of the iframes window. 2815 */ 2816 _focusWindow: function() { 2817 this.focus(); 2818 }, 2819 /** 2820 * @private 2821 * @method _hasSelection 2822 * @description Determines if there is a selection in the editor document. 2823 * @return {Boolean} 2824 */ 2825 _hasSelection: function() { 2826 var sel = this._getSelection(); 2827 var range = this._getRange(); 2828 var hasSel = false; 2829 2830 if (!sel || !range) { 2831 return hasSel; 2832 } 2833 2834 //Internet Explorer 2835 if (this.browser.ie) { 2836 if (range.text) { 2837 hasSel = true; 2838 } 2839 if (range.html) { 2840 hasSel = true; 2841 } 2842 } else { 2843 if (this.browser.webkit) { 2844 if (sel+'' !== '') { 2845 hasSel = true; 2846 } 2847 } else { 2848 if (sel && (sel.toString() !== '') && (sel !== undefined)) { 2849 hasSel = true; 2850 } 2851 } 2852 } 2853 return hasSel; 2854 }, 2855 /** 2856 * @private 2857 * @method _getSelection 2858 * @description Handles the different selection objects across the A-Grade list. 2859 * @return {Object} Selection Object 2860 */ 2861 _getSelection: function() { 2862 var _sel = null; 2863 if (this._getDoc() && this._getWindow()) { 2864 if (this._getDoc().selection &&! this.browser.opera) { 2865 _sel = this._getDoc().selection; 2866 } else { 2867 _sel = this._getWindow().getSelection(); 2868 } 2869 //Handle Safari's lack of Selection Object 2870 if (this.browser.webkit) { 2871 if (_sel.baseNode) { 2872 this._selection = {}; 2873 this._selection.baseNode = _sel.baseNode; 2874 this._selection.baseOffset = _sel.baseOffset; 2875 this._selection.extentNode = _sel.extentNode; 2876 this._selection.extentOffset = _sel.extentOffset; 2877 } else if (this._selection !== null) { 2878 _sel = this._getWindow().getSelection(); 2879 _sel.setBaseAndExtent( 2880 this._selection.baseNode, 2881 this._selection.baseOffset, 2882 this._selection.extentNode, 2883 this._selection.extentOffset); 2884 this._selection = null; 2885 } 2886 } 2887 } 2888 return _sel; 2889 }, 2890 /** 2891 * @private 2892 * @method _selectNode 2893 * @description Places the highlight around a given node 2894 * @param {HTMLElement} node The node to select 2895 */ 2896 _selectNode: function(node, collapse) { 2897 if (!node) { 2898 return false; 2899 } 2900 var sel = this._getSelection(), 2901 range = null; 2902 2903 if (this.browser.ie) { 2904 try { //IE freaks out here sometimes.. 2905 range = this._getDoc().body.createTextRange(); 2906 range.moveToElementText(node); 2907 range.select(); 2908 } catch (e) { 2909 } 2910 } else if (this.browser.webkit) { 2911 if (collapse) { 2912 sel.setBaseAndExtent(node, 1, node, node.innerText.length); 2913 } else { 2914 sel.setBaseAndExtent(node, 0, node, node.innerText.length); 2915 } 2916 } else if (this.browser.opera) { 2917 sel = this._getWindow().getSelection(); 2918 range = this._getDoc().createRange(); 2919 range.selectNode(node); 2920 sel.removeAllRanges(); 2921 sel.addRange(range); 2922 } else { 2923 range = this._getDoc().createRange(); 2924 range.selectNodeContents(node); 2925 sel.removeAllRanges(); 2926 sel.addRange(range); 2927 } 2928 //TODO - Check Performance 2929 this.nodeChange(); 2930 }, 2931 /** 2932 * @private 2933 * @method _getRange 2934 * @description Handles the different range objects across the A-Grade list. 2935 * @return {Object} Range Object 2936 */ 2937 _getRange: function() { 2938 var sel = this._getSelection(); 2939 2940 if (sel === null) { 2941 return null; 2942 } 2943 2944 if (this.browser.webkit && !sel.getRangeAt) { 2945 var _range = this._getDoc().createRange(); 2946 try { 2947 _range.setStart(sel.anchorNode, sel.anchorOffset); 2948 _range.setEnd(sel.focusNode, sel.focusOffset); 2949 } catch (e) { 2950 _range = this._getWindow().getSelection()+''; 2951 } 2952 return _range; 2953 } 2954 2955 if (this.browser.ie) { 2956 try { 2957 return sel.createRange(); 2958 } catch (e2) { 2959 return null; 2960 } 2961 } 2962 2963 if (sel.rangeCount > 0) { 2964 return sel.getRangeAt(0); 2965 } 2966 return null; 2967 }, 2968 /** 2969 * @private 2970 * @method _setDesignMode 2971 * @description Sets the designMode property of the iFrame document's body. 2972 * @param {String} state This should be either on or off 2973 */ 2974 _setDesignMode: function(state) { 2975 if (this.get('setDesignMode')) { 2976 try { 2977 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on'); 2978 } catch(e) { } 2979 } 2980 }, 2981 /** 2982 * @private 2983 * @method _toggleDesignMode 2984 * @description Toggles the designMode property of the iFrame document on and off. 2985 * @return {String} The state that it was set to. 2986 */ 2987 _toggleDesignMode: function() { 2988 var _dMode = this._getDoc().designMode, 2989 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on'); 2990 this._setDesignMode(_state); 2991 return _state; 2992 }, 2993 /** 2994 * @private 2995 * @property _focused 2996 * @description Holder for trapping focus/blur state and prevent double events 2997 * @type Boolean 2998 */ 2999 _focused: null, 3000 /** 3001 * @private 3002 * @method _handleFocus 3003 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event. 3004 * @param {Event} e The DOM Event 3005 */ 3006 _handleFocus: function(e) { 3007 if (!this._focused) { 3008 this._focused = true; 3009 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this }); 3010 } 3011 }, 3012 /** 3013 * @private 3014 * @method _handleBlur 3015 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event. 3016 * @param {Event} e The DOM Event 3017 */ 3018 _handleBlur: function(e) { 3019 if (this._focused) { 3020 this._focused = false; 3021 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this }); 3022 } 3023 }, 3024 /** 3025 * @private 3026 * @method _initEditorEvents 3027 * @description This method sets up the listeners on the Editors document. 3028 */ 3029 _initEditorEvents: function() { 3030 //Setup Listeners on iFrame 3031 var doc = this._getDoc(), 3032 win = this._getWindow(); 3033 3034 Event.on(doc, 'mouseup', this._handleMouseUp, this, true); 3035 Event.on(doc, 'mousedown', this._handleMouseDown, this, true); 3036 Event.on(doc, 'click', this._handleClick, this, true); 3037 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true); 3038 Event.on(doc, 'keypress', this._handleKeyPress, this, true); 3039 Event.on(doc, 'keyup', this._handleKeyUp, this, true); 3040 Event.on(doc, 'keydown', this._handleKeyDown, this, true); 3041 /* TODO -- Everyone but Opera works here.. 3042 Event.on(doc, 'paste', function() { 3043 }, this, true); 3044 */ 3045 3046 //Focus and blur.. 3047 Event.on(win, 'focus', this._handleFocus, this, true); 3048 Event.on(win, 'blur', this._handleBlur, this, true); 3049 }, 3050 /** 3051 * @private 3052 * @method _removeEditorEvents 3053 * @description This method removes the listeners on the Editors document (for disabling). 3054 */ 3055 _removeEditorEvents: function() { 3056 //Remove Listeners on iFrame 3057 var doc = this._getDoc(), 3058 win = this._getWindow(); 3059 3060 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true); 3061 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true); 3062 Event.removeListener(doc, 'click', this._handleClick, this, true); 3063 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true); 3064 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true); 3065 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true); 3066 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true); 3067 3068 //Focus and blur.. 3069 Event.removeListener(win, 'focus', this._handleFocus, this, true); 3070 Event.removeListener(win, 'blur', this._handleBlur, this, true); 3071 }, 3072 _fixWebkitDivs: function() { 3073 if (this.browser.webkit) { 3074 var divs = this._getDoc().body.getElementsByTagName('div'); 3075 Dom.addClass(divs, 'yui-wk-div'); 3076 } 3077 }, 3078 /** 3079 * @private 3080 * @method _initEditor 3081 * @param {Boolean} raw Don't add events. 3082 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners. 3083 */ 3084 _initEditor: function(raw) { 3085 if (this._editorInit) { 3086 return; 3087 } 3088 this._editorInit = true; 3089 if (this.browser.ie) { 3090 this._getDoc().body.style.margin = '0'; 3091 } 3092 if (!this.get('disabled')) { 3093 this._setDesignMode('on'); 3094 this._contentTimerCounter = 0; 3095 } 3096 if (!this._getDoc().body) { 3097 this._contentTimerCounter = 0; 3098 this._editorInit = false; 3099 this._checkLoaded(); 3100 return false; 3101 } 3102 3103 if (!raw) { 3104 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true); 3105 } 3106 if (!this.get('disabled')) { 3107 this._initEditorEvents(); 3108 this.toolbar.set('disabled', false); 3109 } 3110 3111 if (raw) { 3112 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this }); 3113 } else { 3114 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this }); 3115 } 3116 this._fixWebkitDivs(); 3117 if (this.get('dompath')) { 3118 var self = this; 3119 setTimeout(function() { 3120 self._writeDomPath.call(self); 3121 self._setupResize.call(self); 3122 }, 150); 3123 } 3124 var br = []; 3125 for (var i in this.browser) { 3126 if (this.browser[i]) { 3127 br.push(i); 3128 } 3129 } 3130 if (this.get('ptags')) { 3131 br.push('ptags'); 3132 } 3133 Dom.addClass(this._getDoc().body, br.join(' ')); 3134 this.nodeChange(true); 3135 }, 3136 /** 3137 * @private 3138 * @method _checkLoaded 3139 * @param {Boolean} raw Don't add events. 3140 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor. 3141 */ 3142 _checkLoaded: function(raw) { 3143 this._editorInit = false; 3144 this._contentTimerCounter++; 3145 if (this._contentTimer) { 3146 clearTimeout(this._contentTimer); 3147 } 3148 if (this._contentTimerCounter > this._contentTimerMax) { 3149 return false; 3150 } 3151 var init = false; 3152 try { 3153 if (this._getDoc() && this._getDoc().body) { 3154 if (this.browser.ie) { 3155 if (this._getDoc().body.readyState == 'complete') { 3156 init = true; 3157 } 3158 } else { 3159 if (this._getDoc().body._rteLoaded === true) { 3160 init = true; 3161 } 3162 } 3163 } 3164 } catch (e) { 3165 init = false; 3166 } 3167 3168 if (init === true) { 3169 //The onload event has fired, clean up after ourselves and fire the _initEditor method 3170 this._initEditor(raw); 3171 } else { 3172 var self = this; 3173 this._contentTimer = setTimeout(function() { 3174 self._checkLoaded.call(self, raw); 3175 }, 20); 3176 } 3177 }, 3178 /** 3179 * @private 3180 * @method _setInitialContent 3181 * @param {Boolean} raw Don't add events. 3182 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking. 3183 */ 3184 _setInitialContent: function(raw) { 3185 3186 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML), 3187 doc = null; 3188 3189 if (value === '') { 3190 value = '<br>'; 3191 } 3192 3193 var html = Lang.substitute(this.get('html'), { 3194 TITLE: this.STR_TITLE, 3195 CONTENT: this._cleanIncomingHTML(value), 3196 CSS: this.get('css'), 3197 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'), 3198 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */') 3199 }), 3200 check = true; 3201 3202 html = html.replace(/RIGHT_BRACKET/gi, '{'); 3203 html = html.replace(/LEFT_BRACKET/gi, '}'); 3204 3205 if (document.compatMode != 'BackCompat') { 3206 html = this._docType + "\n" + html; 3207 } else { 3208 } 3209 3210 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) { 3211 //Firefox 1.5 doesn't like setting designMode on an document created with a data url 3212 try { 3213 //Adobe AIR Code 3214 if (this.browser.air) { 3215 doc = this._getDoc().implementation.createHTMLDocument(); 3216 var origDoc = this._getDoc(); 3217 origDoc.open(); 3218 origDoc.close(); 3219 doc.open(); 3220 doc.write(html); 3221 doc.close(); 3222 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true); 3223 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]); 3224 origDoc.body._rteLoaded = true; 3225 } else { 3226 doc = this._getDoc(); 3227 doc.open(); 3228 doc.write(html); 3229 doc.close(); 3230 } 3231 } catch (e) { 3232 //Safari will only be here if we are hidden 3233 check = false; 3234 } 3235 } else { 3236 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality 3237 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html); 3238 } 3239 this.get('iframe').setStyle('visibility', ''); 3240 if (check) { 3241 this._checkLoaded(raw); 3242 } 3243 }, 3244 /** 3245 * @private 3246 * @method _setMarkupType 3247 * @param {String} action The action to take. Possible values are: css, default or semantic 3248 * @description This method will turn on/off the useCSS execCommand. 3249 */ 3250 _setMarkupType: function(action) { 3251 switch (this.get('markup')) { 3252 case 'css': 3253 this._setEditorStyle(true); 3254 break; 3255 case 'default': 3256 this._setEditorStyle(false); 3257 break; 3258 case 'semantic': 3259 case 'xhtml': 3260 if (this._semantic[action]) { 3261 this._setEditorStyle(false); 3262 } else { 3263 this._setEditorStyle(true); 3264 } 3265 break; 3266 } 3267 }, 3268 /** 3269 * Set the editor to use CSS instead of HTML 3270 * @param {Booleen} stat True/False 3271 */ 3272 _setEditorStyle: function(stat) { 3273 try { 3274 this._getDoc().execCommand('useCSS', false, !stat); 3275 } catch (ex) { 3276 } 3277 }, 3278 /** 3279 * @private 3280 * @method _getSelectedElement 3281 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event. 3282 * @return {HTMLElement} The currently selected element. 3283 */ 3284 _getSelectedElement: function() { 3285 var doc = this._getDoc(), 3286 range = null, 3287 sel = null, 3288 elm = null, 3289 check = true; 3290 3291 if (this.browser.ie) { 3292 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event; 3293 range = this._getRange(); 3294 if (range) { 3295 elm = range.item ? range.item(0) : range.parentElement(); 3296 if (this._hasSelection()) { 3297 //TODO 3298 //WTF.. Why can't I get an element reference here?!??! 3299 } 3300 if (elm === doc.body) { 3301 elm = null; 3302 } 3303 } 3304 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) { 3305 elm = Event.getTarget(this.currentEvent); 3306 } 3307 } else { 3308 sel = this._getSelection(); 3309 range = this._getRange(); 3310 3311 if (!sel || !range) { 3312 return null; 3313 } 3314 //TODO 3315 if (!this._hasSelection() && this.browser.webkit3) { 3316 //check = false; 3317 } 3318 if (this.browser.gecko) { 3319 //Added in 2.6.0 3320 if (range.startContainer) { 3321 if (range.startContainer.nodeType === 3) { 3322 elm = range.startContainer.parentNode; 3323 } else if (range.startContainer.nodeType === 1) { 3324 elm = range.startContainer; 3325 } 3326 //Added in 2.7.0 3327 if (this.currentEvent) { 3328 var tar = Event.getTarget(this.currentEvent); 3329 if (!this._isElement(tar, 'html')) { 3330 if (elm !== tar) { 3331 elm = tar; 3332 } 3333 } 3334 } 3335 } 3336 } 3337 3338 if (check) { 3339 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) { 3340 if (sel.anchorNode.parentNode) { //next check parentNode 3341 elm = sel.anchorNode.parentNode; 3342 } 3343 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) { 3344 elm = sel.anchorNode.nextSibling; 3345 } 3346 } 3347 if (this._isElement(elm, 'br')) { 3348 elm = null; 3349 } 3350 if (!elm) { 3351 elm = range.commonAncestorContainer; 3352 if (!range.collapsed) { 3353 if (range.startContainer == range.endContainer) { 3354 if (range.startOffset - range.endOffset < 2) { 3355 if (range.startContainer.hasChildNodes()) { 3356 elm = range.startContainer.childNodes[range.startOffset]; 3357 } 3358 } 3359 } 3360 } 3361 } 3362 } 3363 } 3364 3365 if (this.currentEvent !== null) { 3366 try { 3367 switch (this.currentEvent.type) { 3368 case 'click': 3369 case 'mousedown': 3370 case 'mouseup': 3371 if (this.browser.webkit) { 3372 elm = Event.getTarget(this.currentEvent); 3373 } 3374 break; 3375 default: 3376 //Do nothing 3377 break; 3378 } 3379 } catch (e) { 3380 } 3381 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) { 3382 //TODO is this still needed? 3383 //elm = this.currentElement[0]; 3384 } 3385 3386 3387 if (this.browser.opera || this.browser.webkit) { 3388 if (this.currentEvent && !elm) { 3389 elm = YAHOO.util.Event.getTarget(this.currentEvent); 3390 } 3391 } 3392 if (!elm || !elm.tagName) { 3393 elm = doc.body; 3394 } 3395 if (this._isElement(elm, 'html')) { 3396 //Safari sometimes gives us the HTML node back.. 3397 elm = doc.body; 3398 } 3399 if (this._isElement(elm, 'body')) { 3400 //make sure that body means this body not the parent.. 3401 elm = doc.body; 3402 } 3403 if (elm && !elm.parentNode) { //Not in document 3404 elm = doc.body; 3405 } 3406 if (elm === undefined) { 3407 elm = null; 3408 } 3409 return elm; 3410 }, 3411 /** 3412 * @private 3413 * @method _getDomPath 3414 * @description This method will attempt to build the DOM path from the currently selected element. 3415 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used 3416 * @return {Array} An array of node references that will create the DOM Path. 3417 */ 3418 _getDomPath: function(el) { 3419 if (!el) { 3420 el = this._getSelectedElement(); 3421 } 3422 var domPath = []; 3423 while (el !== null) { 3424 if (el.ownerDocument != this._getDoc()) { 3425 el = null; 3426 break; 3427 } 3428 //Check to see if we get el.nodeName and nodeType 3429 if (el.nodeName && el.nodeType && (el.nodeType == 1)) { 3430 domPath[domPath.length] = el; 3431 } 3432 3433 if (this._isElement(el, 'body')) { 3434 break; 3435 } 3436 3437 el = el.parentNode; 3438 } 3439 if (domPath.length === 0) { 3440 if (this._getDoc() && this._getDoc().body) { 3441 domPath[0] = this._getDoc().body; 3442 } 3443 } 3444 return domPath.reverse(); 3445 }, 3446 /** 3447 * @private 3448 * @method _writeDomPath 3449 * @description Write the current DOM path out to the dompath container below the editor. 3450 */ 3451 _writeDomPath: function() { 3452 var path = this._getDomPath(), 3453 pathArr = [], 3454 classPath = '', 3455 pathStr = ''; 3456 3457 for (var i = 0; i < path.length; i++) { 3458 var tag = path[i].tagName.toLowerCase(); 3459 if ((tag == 'ol') && (path[i].type)) { 3460 tag += ':' + path[i].type; 3461 } 3462 if (Dom.hasClass(path[i], 'yui-tag')) { 3463 tag = path[i].getAttribute('tag'); 3464 } 3465 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) { 3466 switch (tag) { 3467 case 'b': tag = 'strong'; break; 3468 case 'i': tag = 'em'; break; 3469 } 3470 } 3471 if (!Dom.hasClass(path[i], 'yui-non')) { 3472 if (Dom.hasClass(path[i], 'yui-tag')) { 3473 pathStr = tag; 3474 } else { 3475 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : ''); 3476 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) { 3477 classPath = ''; 3478 } 3479 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath; 3480 } 3481 switch (tag) { 3482 case 'body': 3483 pathStr = 'body'; 3484 break; 3485 case 'a': 3486 if (path[i].getAttribute('href', 2)) { 3487 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp 3488 } 3489 break; 3490 case 'img': 3491 var h = path[i].height; 3492 var w = path[i].width; 3493 if (path[i].style.height) { 3494 h = parseInt(path[i].style.height, 10); 3495 } 3496 if (path[i].style.width) { 3497 w = parseInt(path[i].style.width, 10); 3498 } 3499 pathStr += '(' + w + 'x' + h + ')'; 3500 break; 3501 } 3502 3503 if (pathStr.length > 10) { 3504 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>'; 3505 } else { 3506 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>'; 3507 } 3508 pathArr[pathArr.length] = pathStr; 3509 } 3510 } 3511 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' '); 3512 //Prevent flickering 3513 if (this.dompath.innerHTML != str) { 3514 this.dompath.innerHTML = str; 3515 } 3516 }, 3517 /** 3518 * @private 3519 * @method _fixNodes 3520 * @description Fix href and imgs as well as remove invalid HTML. 3521 */ 3522 _fixNodes: function() { 3523 try { 3524 var doc = this._getDoc(), 3525 els = []; 3526 3527 for (var v in this.invalidHTML) { 3528 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) { 3529 if (v.toLowerCase() != 'span') { 3530 var tags = doc.body.getElementsByTagName(v); 3531 if (tags.length) { 3532 for (var i = 0; i < tags.length; i++) { 3533 els.push(tags[i]); 3534 } 3535 } 3536 } 3537 } 3538 } 3539 for (var h = 0; h < els.length; h++) { 3540 if (els[h].parentNode) { 3541 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) { 3542 this._swapEl(els[h], 'span', function(el) { 3543 el.className = 'yui-non'; 3544 }); 3545 } else { 3546 els[h].parentNode.removeChild(els[h]); 3547 } 3548 } 3549 } 3550 var imgs = this._getDoc().getElementsByTagName('img'); 3551 Dom.addClass(imgs, 'yui-img'); 3552 } catch(e) {} 3553 }, 3554 /** 3555 * @private 3556 * @method _isNonEditable 3557 * @param Event ev The Dom event being checked 3558 * @description Method is called at the beginning of all event handlers to check if this element or a parent element has the class yui-noedit (this.CLASS_NOEDIT) applied. 3559 * If it does, then this method will stop the event and return true. The event handlers will then return false and stop the nodeChange from occuring. This method will also 3560 * disable and enable the Editor's toolbar based on the noedit state. 3561 * @return Boolean 3562 */ 3563 _isNonEditable: function(ev) { 3564 if (this.get('allowNoEdit')) { 3565 var el = Event.getTarget(ev); 3566 if (this._isElement(el, 'html')) { 3567 el = null; 3568 } 3569 var path = this._getDomPath(el); 3570 for (var i = (path.length - 1); i > -1; i--) { 3571 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) { 3572 //if (this.toolbar.get('disabled') === false) { 3573 // this.toolbar.set('disabled', true); 3574 //} 3575 try { 3576 this._getDoc().execCommand('enableObjectResizing', false, 'false'); 3577 } catch (e) {} 3578 this.nodeChange(); 3579 Event.stopEvent(ev); 3580 return true; 3581 } 3582 } 3583 //if (this.toolbar.get('disabled') === true) { 3584 //Should only happen once.. 3585 //this.toolbar.set('disabled', false); 3586 try { 3587 this._getDoc().execCommand('enableObjectResizing', false, 'true'); 3588 } catch (e2) {} 3589 //} 3590 } 3591 return false; 3592 }, 3593 /** 3594 * @private 3595 * @method _setCurrentEvent 3596 * @param {Event} ev The event to cache 3597 * @description Sets the current event property 3598 */ 3599 _setCurrentEvent: function(ev) { 3600 this.currentEvent = ev; 3601 }, 3602 /** 3603 * @private 3604 * @method _handleClick 3605 * @param {Event} ev The event we are working on. 3606 * @description Handles all click events inside the iFrame document. 3607 */ 3608 _handleClick: function(ev) { 3609 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev }); 3610 if (ret === false) { 3611 return false; 3612 } 3613 if (this._isNonEditable(ev)) { 3614 return false; 3615 } 3616 this._setCurrentEvent(ev); 3617 if (this.currentWindow) { 3618 this.closeWindow(); 3619 } 3620 if (this.currentWindow) { 3621 this.closeWindow(); 3622 } 3623 if (this.browser.webkit) { 3624 var tar =Event.getTarget(ev); 3625 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) { 3626 Event.stopEvent(ev); 3627 this.nodeChange(); 3628 } 3629 } else { 3630 this.nodeChange(); 3631 } 3632 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev }); 3633 }, 3634 /** 3635 * @private 3636 * @method _handleMouseUp 3637 * @param {Event} ev The event we are working on. 3638 * @description Handles all mouseup events inside the iFrame document. 3639 */ 3640 _handleMouseUp: function(ev) { 3641 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev }); 3642 if (ret === false) { 3643 return false; 3644 } 3645 if (this._isNonEditable(ev)) { 3646 return false; 3647 } 3648 //Don't set current event for mouseup. 3649 //It get's fired after a menu is closed and gives up a bogus event to work with 3650 //this._setCurrentEvent(ev); 3651 var self = this; 3652 if (this.browser.opera) { 3653 /* 3654 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on.. 3655 * @browser Opera 3656 * @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. 3657 */ 3658 var sel = Event.getTarget(ev); 3659 if (this._isElement(sel, 'img')) { 3660 this.nodeChange(); 3661 if (this.operaEvent) { 3662 clearTimeout(this.operaEvent); 3663 this.operaEvent = null; 3664 this._handleDoubleClick(ev); 3665 } else { 3666 this.operaEvent = window.setTimeout(function() { 3667 self.operaEvent = false; 3668 }, 700); 3669 } 3670 } 3671 } 3672 //This will stop Safari from selecting the entire document if you select all the text in the editor 3673 if (this.browser.webkit || this.browser.opera) { 3674 if (this.browser.webkit) { 3675 Event.stopEvent(ev); 3676 } 3677 } 3678 this.nodeChange(); 3679 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev }); 3680 }, 3681 /** 3682 * @private 3683 * @method _handleMouseDown 3684 * @param {Event} ev The event we are working on. 3685 * @description Handles all mousedown events inside the iFrame document. 3686 */ 3687 _handleMouseDown: function(ev) { 3688 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev }); 3689 if (ret === false) { 3690 return false; 3691 } 3692 if (this._isNonEditable(ev)) { 3693 return false; 3694 } 3695 this._setCurrentEvent(ev); 3696 var sel = Event.getTarget(ev); 3697 if (this.browser.webkit && this._hasSelection()) { 3698 var _sel = this._getSelection(); 3699 if (!this.browser.webkit3) { 3700 _sel.collapse(true); 3701 } else { 3702 _sel.collapseToStart(); 3703 } 3704 } 3705 if (this.browser.webkit && this._lastImage) { 3706 Dom.removeClass(this._lastImage, 'selected'); 3707 this._lastImage = null; 3708 } 3709 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) { 3710 if (this.browser.webkit) { 3711 Event.stopEvent(ev); 3712 if (this._isElement(sel, 'img')) { 3713 Dom.addClass(sel, 'selected'); 3714 this._lastImage = sel; 3715 } 3716 } 3717 if (this.currentWindow) { 3718 this.closeWindow(); 3719 } 3720 this.nodeChange(); 3721 } 3722 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev }); 3723 }, 3724 /** 3725 * @private 3726 * @method _handleDoubleClick 3727 * @param {Event} ev The event we are working on. 3728 * @description Handles all doubleclick events inside the iFrame document. 3729 */ 3730 _handleDoubleClick: function(ev) { 3731 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev }); 3732 if (ret === false) { 3733 return false; 3734 } 3735 if (this._isNonEditable(ev)) { 3736 return false; 3737 } 3738 this._setCurrentEvent(ev); 3739 var sel = Event.getTarget(ev); 3740 if (this._isElement(sel, 'img')) { 3741 this.currentElement[0] = sel; 3742 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar }); 3743 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3744 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a 3745 this.currentElement[0] = this._hasParent(sel, 'a'); 3746 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar }); 3747 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3748 } 3749 this.nodeChange(); 3750 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev }); 3751 }, 3752 /** 3753 * @private 3754 * @method _handleKeyUp 3755 * @param {Event} ev The event we are working on. 3756 * @description Handles all keyup events inside the iFrame document. 3757 */ 3758 _handleKeyUp: function(ev) { 3759 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev }); 3760 if (ret === false) { 3761 return false; 3762 } 3763 if (this._isNonEditable(ev)) { 3764 return false; 3765 } 3766 this._storeUndo(); 3767 this._setCurrentEvent(ev); 3768 switch (ev.keyCode) { 3769 case this._keyMap.SELECT_ALL.key: 3770 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) { 3771 this.nodeChange(); 3772 } 3773 break; 3774 case 32: //Space Bar 3775 case 35: //End 3776 case 36: //Home 3777 case 37: //Left Arrow 3778 case 38: //Up Arrow 3779 case 39: //Right Arrow 3780 case 40: //Down Arrow 3781 case 46: //Forward Delete 3782 case 8: //Delete 3783 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open 3784 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) { 3785 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) { 3786 this.closeWindow(); 3787 } 3788 } else { 3789 if (!this.browser.ie) { 3790 if (this._nodeChangeTimer) { 3791 clearTimeout(this._nodeChangeTimer); 3792 } 3793 var self = this; 3794 this._nodeChangeTimer = setTimeout(function() { 3795 self._nodeChangeTimer = null; 3796 self.nodeChange.call(self); 3797 }, 100); 3798 } else { 3799 this.nodeChange(); 3800 } 3801 this.editorDirty = true; 3802 } 3803 break; 3804 } 3805 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev }); 3806 }, 3807 /** 3808 * @private 3809 * @method _handleKeyPress 3810 * @param {Event} ev The event we are working on. 3811 * @description Handles all keypress events inside the iFrame document. 3812 */ 3813 _handleKeyPress: function(ev) { 3814 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev }); 3815 if (ret === false) { 3816 return false; 3817 } 3818 3819 if (this.get('allowNoEdit')) { 3820 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) { 3821 if (ev && ev.keyCode && (ev.keyCode == 63272)) { 3822 //Forward delete key 3823 Event.stopEvent(ev); 3824 } 3825 } 3826 if (this._isNonEditable(ev)) { 3827 return false; 3828 } 3829 this._setCurrentEvent(ev); 3830 this._storeUndo(); 3831 if (this.browser.opera) { 3832 if (ev.keyCode === 13) { 3833 var tar = this._getSelectedElement(); 3834 if (!this._isElement(tar, 'li')) { 3835 this.execCommand('inserthtml', '<br>'); 3836 Event.stopEvent(ev); 3837 } 3838 } 3839 } 3840 if (this.browser.webkit) { 3841 if (!this.browser.webkit3) { 3842 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) { 3843 //This is CMD + z (for undo) 3844 if (this._hasParent(this._getSelectedElement(), 'li')) { 3845 Event.stopEvent(ev); 3846 } 3847 } 3848 } 3849 this._listFix(ev); 3850 } 3851 this._fixListDupIds(); 3852 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev }); 3853 }, 3854 /** 3855 * @private 3856 * @method _handleKeyDown 3857 * @param {Event} ev The event we are working on. 3858 * @description Handles all keydown events inside the iFrame document. 3859 */ 3860 _handleKeyDown: function(ev) { 3861 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev }); 3862 if (ret === false) { 3863 return false; 3864 } 3865 var tar = null, _range = null; 3866 if (this._isNonEditable(ev)) { 3867 return false; 3868 } 3869 this._setCurrentEvent(ev); 3870 if (this.currentWindow) { 3871 this.closeWindow(); 3872 } 3873 if (this.currentWindow) { 3874 this.closeWindow(); 3875 } 3876 var doExec = false, 3877 action = null, 3878 value = null, 3879 exec = false; 3880 3881 3882 switch (ev.keyCode) { 3883 case this._keyMap.FOCUS_TOOLBAR.key: 3884 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) { 3885 var h = this.toolbar.getElementsByTagName('h2')[0]; 3886 if (h && h.firstChild) { 3887 h.firstChild.focus(); 3888 } 3889 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) { 3890 //Focus After Element - Esc 3891 this.afterElement.focus(); 3892 } 3893 Event.stopEvent(ev); 3894 doExec = false; 3895 break; 3896 //case 76: //L 3897 case this._keyMap.CREATE_LINK.key: //L 3898 if (this._hasSelection()) { 3899 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) { 3900 var makeLink = true; 3901 if (this.get('limitCommands')) { 3902 if (!this.toolbar.getButtonByValue('createlink')) { 3903 makeLink = false; 3904 } 3905 } 3906 if (makeLink) { 3907 this.execCommand('createlink', ''); 3908 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar }); 3909 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3910 doExec = false; 3911 } 3912 } 3913 } 3914 break; 3915 //case 90: //Z 3916 case this._keyMap.UNDO.key: 3917 case this._keyMap.REDO.key: 3918 if (this._checkKey(this._keyMap.REDO, ev)) { 3919 action = 'redo'; 3920 doExec = true; 3921 } else if (this._checkKey(this._keyMap.UNDO, ev)) { 3922 action = 'undo'; 3923 doExec = true; 3924 } 3925 break; 3926 //case 66: //B 3927 case this._keyMap.BOLD.key: 3928 if (this._checkKey(this._keyMap.BOLD, ev)) { 3929 action = 'bold'; 3930 doExec = true; 3931 } 3932 break; 3933 case this._keyMap.FONT_SIZE_UP.key: 3934 case this._keyMap.FONT_SIZE_DOWN.key: 3935 var uk = false, dk = false; 3936 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) { 3937 uk = true; 3938 } 3939 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) { 3940 dk = true; 3941 } 3942 if (uk || dk) { 3943 var fs_button = this.toolbar.getButtonByValue('fontsize'), 3944 label = parseInt(fs_button.get('label'), 10), 3945 newValue = (label + 1); 3946 3947 if (dk) { 3948 newValue = (label - 1); 3949 } 3950 3951 action = 'fontsize'; 3952 value = newValue + 'px'; 3953 doExec = true; 3954 } 3955 break; 3956 //case 73: //I 3957 case this._keyMap.ITALIC.key: 3958 if (this._checkKey(this._keyMap.ITALIC, ev)) { 3959 action = 'italic'; 3960 doExec = true; 3961 } 3962 break; 3963 //case 85: //U 3964 case this._keyMap.UNDERLINE.key: 3965 if (this._checkKey(this._keyMap.UNDERLINE, ev)) { 3966 action = 'underline'; 3967 doExec = true; 3968 } 3969 break; 3970 case 9: 3971 if (this.browser.ie) { 3972 //Insert a tab in Internet Explorer 3973 _range = this._getRange(); 3974 tar = this._getSelectedElement(); 3975 if (!this._isElement(tar, 'li')) { 3976 if (_range) { 3977 _range.pasteHTML(' '); 3978 _range.collapse(false); 3979 _range.select(); 3980 } 3981 Event.stopEvent(ev); 3982 } 3983 } 3984 //Firefox 3 code 3985 if (this.browser.gecko > 1.8) { 3986 tar = this._getSelectedElement(); 3987 if (this._isElement(tar, 'li')) { 3988 if (ev.shiftKey) { 3989 this._getDoc().execCommand('outdent', null, ''); 3990 } else { 3991 this._getDoc().execCommand('indent', null, ''); 3992 } 3993 3994 } else if (!this._hasSelection()) { 3995 this.execCommand('inserthtml', ' '); 3996 } 3997 Event.stopEvent(ev); 3998 } 3999 break; 4000 case 13: 4001 var p = null, i = 0; 4002 if (this.get('ptags') && !ev.shiftKey) { 4003 if (this.browser.gecko) { 4004 tar = this._getSelectedElement(); 4005 if (!this._hasParent(tar, 'li')) { 4006 if (this._hasParent(tar, 'p')) { 4007 p = this._getDoc().createElement('p'); 4008 p.innerHTML = ' '; 4009 Dom.insertAfter(p, tar); 4010 this._selectNode(p.firstChild); 4011 } else if (this._isElement(tar, 'body')) { 4012 this.execCommand('insertparagraph', null); 4013 var ps = this._getDoc().body.getElementsByTagName('p'); 4014 for (i = 0; i < ps.length; i++) { 4015 if (ps[i].getAttribute('_moz_dirty') !== null) { 4016 p = this._getDoc().createElement('p'); 4017 p.innerHTML = ' '; 4018 Dom.insertAfter(p, ps[i]); 4019 this._selectNode(p.firstChild); 4020 ps[i].removeAttribute('_moz_dirty'); 4021 } 4022 } 4023 } else { 4024 doExec = true; 4025 action = 'insertparagraph'; 4026 } 4027 Event.stopEvent(ev); 4028 } 4029 } 4030 if (this.browser.webkit) { 4031 tar = this._getSelectedElement(); 4032 if (!this._hasParent(tar, 'li')) { 4033 this.execCommand('insertparagraph', null); 4034 var divs = this._getDoc().body.getElementsByTagName('div'); 4035 for (i = 0; i < divs.length; i++) { 4036 if (!Dom.hasClass(divs[i], 'yui-wk-div')) { 4037 Dom.addClass(divs[i], 'yui-wk-p'); 4038 } 4039 } 4040 Event.stopEvent(ev); 4041 } 4042 } 4043 } else { 4044 if (this.browser.webkit) { 4045 tar = this._getSelectedElement(); 4046 if (!this._hasParent(tar, 'li')) { 4047 if (this.browser.webkit4) { 4048 this.execCommand('insertlinebreak'); 4049 } else { 4050 this.execCommand('inserthtml', '<var id="yui-br"></var>'); 4051 var holder = this._getDoc().getElementById('yui-br'), 4052 br = this._getDoc().createElement('br'), 4053 caret = this._getDoc().createElement('span'); 4054 4055 holder.parentNode.replaceChild(br, holder); 4056 caret.className = 'yui-non'; 4057 caret.innerHTML = ' '; 4058 Dom.insertAfter(caret, br); 4059 this._selectNode(caret); 4060 } 4061 Event.stopEvent(ev); 4062 } 4063 } 4064 if (this.browser.ie) { 4065 //Insert a <br> instead of a <p></p> in Internet Explorer 4066 _range = this._getRange(); 4067 tar = this._getSelectedElement(); 4068 if (!this._isElement(tar, 'li')) { 4069 if (_range) { 4070 _range.pasteHTML('<br>'); 4071 _range.collapse(false); 4072 _range.select(); 4073 } 4074 Event.stopEvent(ev); 4075 } 4076 } 4077 } 4078 break; 4079 } 4080 if (this.browser.ie) { 4081 this._listFix(ev); 4082 } 4083 if (doExec && action) { 4084 this.execCommand(action, value); 4085 Event.stopEvent(ev); 4086 this.nodeChange(); 4087 } 4088 this._storeUndo(); 4089 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev }); 4090 }, 4091 /** 4092 * @private 4093 * @property _fixListRunning 4094 * @type Boolean 4095 * @description Keeps more than one _fixListDupIds from running at the same time. 4096 */ 4097 _fixListRunning: null, 4098 /** 4099 * @private 4100 * @method _fixListDupIds 4101 * @description Some browsers will duplicate the id of an LI when created in designMode. 4102 * This method will fix the duplicate id issue. However it will only preserve the first element 4103 * in the document list with the unique id. 4104 */ 4105 _fixListDupIds: function() { 4106 if (this._fixListRunning) { 4107 return false; 4108 } 4109 if (this._getDoc()) { 4110 this._fixListRunning = true; 4111 var lis = this._getDoc().body.getElementsByTagName('li'), 4112 i = 0, ids = {}; 4113 for (i = 0; i < lis.length; i++) { 4114 if (lis[i].id) { 4115 if (ids[lis[i].id]) { 4116 lis[i].id = ''; 4117 } 4118 ids[lis[i].id] = true; 4119 } 4120 } 4121 this._fixListRunning = false; 4122 } 4123 }, 4124 /** 4125 * @private 4126 * @method _listFix 4127 * @param {Event} ev The event we are working on. 4128 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items. 4129 */ 4130 _listFix: function(ev) { 4131 var testLi = null, par = null, preContent = false, range = null; 4132 //Enter Key 4133 if (this.browser.webkit) { 4134 if (ev.keyCode && (ev.keyCode == 13)) { 4135 if (this._hasParent(this._getSelectedElement(), 'li')) { 4136 var tar = this._hasParent(this._getSelectedElement(), 'li'); 4137 if (tar.previousSibling) { 4138 if (tar.firstChild && (tar.firstChild.length == 1)) { 4139 this._selectNode(tar); 4140 } 4141 } 4142 } 4143 } 4144 } 4145 //Shift + Tab Key 4146 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) { 4147 testLi = this._getSelectedElement(); 4148 if (this._hasParent(testLi, 'li')) { 4149 testLi = this._hasParent(testLi, 'li'); 4150 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) { 4151 par = this._hasParent(testLi, 'ul'); 4152 if (!par) { 4153 par = this._hasParent(testLi, 'ol'); 4154 } 4155 if (this._isElement(par.previousSibling, 'li')) { 4156 par.removeChild(testLi); 4157 par.parentNode.insertBefore(testLi, par.nextSibling); 4158 if (this.browser.ie) { 4159 range = this._getDoc().body.createTextRange(); 4160 range.moveToElementText(testLi); 4161 range.collapse(false); 4162 range.select(); 4163 } 4164 if (this.browser.webkit) { 4165 this._selectNode(testLi.firstChild); 4166 } 4167 Event.stopEvent(ev); 4168 } 4169 } 4170 } 4171 } 4172 //Tab Key 4173 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) { 4174 var preLi = this._getSelectedElement(); 4175 if (this._hasParent(preLi, 'li')) { 4176 preContent = this._hasParent(preLi, 'li').innerHTML; 4177 } 4178 if (this.browser.webkit) { 4179 this._getDoc().execCommand('inserttext', false, '\t'); 4180 } 4181 testLi = this._getSelectedElement(); 4182 if (this._hasParent(testLi, 'li')) { 4183 par = this._hasParent(testLi, 'li'); 4184 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase()); 4185 if (this.browser.webkit) { 4186 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par); 4187 //Remove the span element that Safari puts in 4188 if (span[0]) { 4189 par.removeChild(span[0]); 4190 par.innerHTML = Lang.trim(par.innerHTML); 4191 //Put the HTML from the LI into this new LI 4192 if (preContent) { 4193 par.innerHTML = '<span class="yui-non">' + preContent + '</span> '; 4194 } else { 4195 par.innerHTML = '<span class="yui-non"> </span> '; 4196 } 4197 } 4198 } else { 4199 if (preContent) { 4200 par.innerHTML = preContent + ' '; 4201 } else { 4202 par.innerHTML = ' '; 4203 } 4204 } 4205 4206 par.parentNode.replaceChild(newUl, par); 4207 newUl.appendChild(par); 4208 if (this.browser.webkit) { 4209 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length); 4210 if (!this.browser.webkit3) { 4211 par.parentNode.parentNode.style.display = 'list-item'; 4212 setTimeout(function() { 4213 par.parentNode.parentNode.style.display = 'block'; 4214 }, 1); 4215 } 4216 } else if (this.browser.ie) { 4217 range = this._getDoc().body.createTextRange(); 4218 range.moveToElementText(par); 4219 range.collapse(false); 4220 range.select(); 4221 } else { 4222 this._selectNode(par); 4223 } 4224 Event.stopEvent(ev); 4225 } 4226 if (this.browser.webkit) { 4227 Event.stopEvent(ev); 4228 } 4229 this.nodeChange(); 4230 } 4231 }, 4232 /** 4233 * @method nodeChange 4234 * @param {Boolean} force Optional paramenter to skip the threshold counter 4235 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes. 4236 */ 4237 nodeChange: function(force) { 4238 var NCself = this; 4239 this._storeUndo(); 4240 if (this.get('nodeChangeDelay')) { 4241 this._nodeChangeDelayTimer = window.setTimeout(function() { 4242 NCself._nodeChangeDelayTimer = null; 4243 NCself._nodeChange.apply(NCself, arguments); 4244 }, 0); 4245 } else { 4246 this._nodeChange(); 4247 } 4248 }, 4249 /** 4250 * @private 4251 * @method _nodeChange 4252 * @param {Boolean} force Optional paramenter to skip the threshold counter 4253 * @description Fired from nodeChange in a setTimeout. 4254 */ 4255 _nodeChange: function(force) { 4256 var threshold = parseInt(this.get('nodeChangeThreshold'), 10), 4257 thisNodeChange = Math.round(new Date().getTime() / 1000), 4258 self = this; 4259 4260 if (force === true) { 4261 this._lastNodeChange = 0; 4262 } 4263 4264 if ((this._lastNodeChange + threshold) < thisNodeChange) { 4265 if (this._fixNodesTimer === null) { 4266 this._fixNodesTimer = window.setTimeout(function() { 4267 self._fixNodes.call(self); 4268 self._fixNodesTimer = null; 4269 }, 0); 4270 } 4271 } 4272 this._lastNodeChange = thisNodeChange; 4273 if (this.currentEvent) { 4274 try { 4275 this._lastNodeChangeEvent = this.currentEvent.type; 4276 } catch (e) {} 4277 } 4278 4279 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this }); 4280 if (beforeNodeChange === false) { 4281 return false; 4282 } 4283 if (this.get('dompath')) { 4284 window.setTimeout(function() { 4285 self._writeDomPath.call(self); 4286 }, 0); 4287 } 4288 //Check to see if we are disabled before continuing 4289 if (!this.get('disabled')) { 4290 if (this.STOP_NODE_CHANGE) { 4291 //Reset this var for next action 4292 this.STOP_NODE_CHANGE = false; 4293 return false; 4294 } else { 4295 var sel = this._getSelection(), 4296 range = this._getRange(), 4297 el = this._getSelectedElement(), 4298 fn_button = this.toolbar.getButtonByValue('fontname'), 4299 fs_button = this.toolbar.getButtonByValue('fontsize'), 4300 undo_button = this.toolbar.getButtonByValue('undo'), 4301 redo_button = this.toolbar.getButtonByValue('redo'); 4302 4303 //Handle updating the toolbar with active buttons 4304 var _ex = {}; 4305 if (this._lastButton) { 4306 _ex[this._lastButton.id] = true; 4307 //this._lastButton = null; 4308 } 4309 if (!this._isElement(el, 'body')) { 4310 if (fn_button) { 4311 _ex[fn_button.get('id')] = true; 4312 } 4313 if (fs_button) { 4314 _ex[fs_button.get('id')] = true; 4315 } 4316 } 4317 if (redo_button) { 4318 delete _ex[redo_button.get('id')]; 4319 } 4320 this.toolbar.resetAllButtons(_ex); 4321 4322 //Handle disabled buttons 4323 for (var d = 0; d < this._disabled.length; d++) { 4324 var _button = this.toolbar.getButtonByValue(this._disabled[d]); 4325 if (_button && _button.get) { 4326 if (this._lastButton && (_button.get('id') === this._lastButton.id)) { 4327 //Skip 4328 } else { 4329 if (!this._hasSelection() && !this.get('insert')) { 4330 switch (this._disabled[d]) { 4331 case 'fontname': 4332 case 'fontsize': 4333 break; 4334 default: 4335 //No Selection - disable 4336 this.toolbar.disableButton(_button); 4337 } 4338 } else { 4339 if (!this._alwaysDisabled[this._disabled[d]]) { 4340 this.toolbar.enableButton(_button); 4341 } 4342 } 4343 if (!this._alwaysEnabled[this._disabled[d]]) { 4344 this.toolbar.deselectButton(_button); 4345 } 4346 } 4347 } 4348 } 4349 var path = this._getDomPath(); 4350 var tag = null, cmd = null; 4351 for (var i = 0; i < path.length; i++) { 4352 tag = path[i].tagName.toLowerCase(); 4353 if (path[i].getAttribute('tag')) { 4354 tag = path[i].getAttribute('tag').toLowerCase(); 4355 } 4356 cmd = this._tag2cmd[tag]; 4357 if (cmd === undefined) { 4358 cmd = []; 4359 } 4360 if (!Lang.isArray(cmd)) { 4361 cmd = [cmd]; 4362 } 4363 4364 //Bold and Italic styles 4365 if (path[i].style.fontWeight.toLowerCase() == 'bold') { 4366 cmd[cmd.length] = 'bold'; 4367 } 4368 if (path[i].style.fontStyle.toLowerCase() == 'italic') { 4369 cmd[cmd.length] = 'italic'; 4370 } 4371 if (path[i].style.textDecoration.toLowerCase() == 'underline') { 4372 cmd[cmd.length] = 'underline'; 4373 } 4374 if (path[i].style.textDecoration.toLowerCase() == 'line-through') { 4375 cmd[cmd.length] = 'strikethrough'; 4376 } 4377 if (cmd.length > 0) { 4378 for (var j = 0; j < cmd.length; j++) { 4379 this.toolbar.selectButton(cmd[j]); 4380 this.toolbar.enableButton(cmd[j]); 4381 } 4382 } 4383 //Handle Alignment 4384 switch (path[i].style.textAlign.toLowerCase()) { 4385 case 'left': 4386 case 'right': 4387 case 'center': 4388 case 'justify': 4389 var alignType = path[i].style.textAlign.toLowerCase(); 4390 if (path[i].style.textAlign.toLowerCase() == 'justify') { 4391 alignType = 'full'; 4392 } 4393 this.toolbar.selectButton('justify' + alignType); 4394 this.toolbar.enableButton('justify' + alignType); 4395 break; 4396 } 4397 } 4398 //After for loop 4399 4400 //Reset Font Family and Size to the inital configs 4401 if (fn_button) { 4402 var family = fn_button._configs.label._initialConfig.value; 4403 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>'); 4404 this._updateMenuChecked('fontname', family); 4405 } 4406 4407 if (fs_button) { 4408 fs_button.set('label', fs_button._configs.label._initialConfig.value); 4409 } 4410 4411 var hd_button = this.toolbar.getButtonByValue('heading'); 4412 if (hd_button) { 4413 hd_button.set('label', hd_button._configs.label._initialConfig.value); 4414 this._updateMenuChecked('heading', 'none'); 4415 } 4416 var img_button = this.toolbar.getButtonByValue('insertimage'); 4417 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) { 4418 this.toolbar.disableButton(img_button); 4419 } 4420 if (this._lastButton && this._lastButton.isSelected) { 4421 this.toolbar.deselectButton(this._lastButton.id); 4422 } 4423 this._undoNodeChange(); 4424 } 4425 } 4426 4427 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this }); 4428 }, 4429 /** 4430 * @private 4431 * @method _updateMenuChecked 4432 * @param {Object} button The command identifier of the button you want to check 4433 * @param {String} value The value of the menu item you want to check 4434 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar) 4435 * @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. 4436 */ 4437 _updateMenuChecked: function(button, value, tbar) { 4438 if (!tbar) { 4439 tbar = this.toolbar; 4440 } 4441 var _button = tbar.getButtonByValue(button); 4442 _button.checkValue(value); 4443 }, 4444 /** 4445 * @private 4446 * @method _handleToolbarClick 4447 * @param {Event} ev The event that triggered the button click 4448 * @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. 4449 */ 4450 _handleToolbarClick: function(ev) { 4451 var value = ''; 4452 var str = ''; 4453 var cmd = ev.button.value; 4454 if (ev.button.menucmd) { 4455 value = cmd; 4456 cmd = ev.button.menucmd; 4457 } 4458 this._lastButton = ev.button; 4459 if (this.STOP_EXEC_COMMAND) { 4460 this.STOP_EXEC_COMMAND = false; 4461 return false; 4462 } else { 4463 this.execCommand(cmd, value); 4464 if (!this.browser.webkit) { 4465 var Fself = this; 4466 setTimeout(function() { 4467 Fself.focus.call(Fself); 4468 }, 5); 4469 } 4470 } 4471 Event.stopEvent(ev); 4472 }, 4473 /** 4474 * @private 4475 * @method _setupAfterElement 4476 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation. 4477 */ 4478 _setupAfterElement: function() { 4479 if (!this.beforeElement) { 4480 this.beforeElement = document.createElement('h2'); 4481 this.beforeElement.className = 'yui-editor-skipheader'; 4482 this.beforeElement.tabIndex = '-1'; 4483 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR; 4484 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling')); 4485 } 4486 if (!this.afterElement) { 4487 this.afterElement = document.createElement('h2'); 4488 this.afterElement.className = 'yui-editor-skipheader'; 4489 this.afterElement.tabIndex = '-1'; 4490 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR; 4491 this.get('element_cont').get('firstChild').appendChild(this.afterElement); 4492 } 4493 }, 4494 /** 4495 * @private 4496 * @method _disableEditor 4497 * @param {Boolean} disabled Pass true to disable, false to enable 4498 * @description Creates a mask to place over the Editor. 4499 */ 4500 _disableEditor: function(disabled) { 4501 var iframe, par, html, height; 4502 if (!this.get('disabled_iframe')) { 4503 iframe = this._createIframe(); 4504 iframe.set('id', 'disabled_' + this.get('iframe').get('id')); 4505 iframe.setStyle('height', '100%'); 4506 iframe.setStyle('display', 'none'); 4507 iframe.setStyle('visibility', 'visible'); 4508 this.set('disabled_iframe', iframe); 4509 par = this.get('iframe').get('parentNode'); 4510 par.appendChild(iframe.get('element')); 4511 } 4512 if (!iframe) { 4513 iframe = this.get('disabled_iframe'); 4514 } 4515 if (disabled) { 4516 this._orgIframe = this.get('iframe'); 4517 4518 if (this.toolbar) { 4519 this.toolbar.set('disabled', true); 4520 } 4521 4522 html = this.getEditorHTML(); 4523 height = this.get('iframe').get('offsetHeight'); 4524 iframe.setStyle('visibility', ''); 4525 iframe.setStyle('position', ''); 4526 iframe.setStyle('top', ''); 4527 iframe.setStyle('left', ''); 4528 this._orgIframe.setStyle('visibility', 'hidden'); 4529 this._orgIframe.setStyle('position', 'absolute'); 4530 this._orgIframe.setStyle('top', '-99999px'); 4531 this._orgIframe.setStyle('left', '-99999px'); 4532 this.set('iframe', iframe); 4533 this._setInitialContent(true); 4534 4535 if (!this._mask) { 4536 this._mask = document.createElement('DIV'); 4537 Dom.addClass(this._mask, 'yui-editor-masked'); 4538 if (this.browser.ie) { 4539 this._mask.style.height = height + 'px'; 4540 } 4541 this.get('iframe').get('parentNode').appendChild(this._mask); 4542 } 4543 this.on('editorContentReloaded', function() { 4544 this._getDoc().body._rteLoaded = false; 4545 this.setEditorHTML(html); 4546 iframe.setStyle('display', 'block'); 4547 this.unsubscribeAll('editorContentReloaded'); 4548 }); 4549 } else { 4550 if (this._mask) { 4551 this._mask.parentNode.removeChild(this._mask); 4552 this._mask = null; 4553 if (this.toolbar) { 4554 this.toolbar.set('disabled', false); 4555 } 4556 iframe.setStyle('visibility', 'hidden'); 4557 iframe.setStyle('position', 'absolute'); 4558 iframe.setStyle('top', '-99999px'); 4559 iframe.setStyle('left', '-99999px'); 4560 this._orgIframe.setStyle('visibility', ''); 4561 this._orgIframe.setStyle('position', ''); 4562 this._orgIframe.setStyle('top', ''); 4563 this._orgIframe.setStyle('left', ''); 4564 this.set('iframe', this._orgIframe); 4565 4566 this.focus(); 4567 var self = this; 4568 window.setTimeout(function() { 4569 self.nodeChange.call(self); 4570 }, 100); 4571 } 4572 } 4573 }, 4574 /** 4575 * @property SEP_DOMPATH 4576 * @description The value to place in between the Dom path items 4577 * @type String 4578 */ 4579 SEP_DOMPATH: '<', 4580 /** 4581 * @property STR_LEAVE_EDITOR 4582 * @description The accessibility string for the element after the iFrame 4583 * @type String 4584 */ 4585 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.', 4586 /** 4587 * @property STR_BEFORE_EDITOR 4588 * @description The accessibility string for the element before the iFrame 4589 * @type String 4590 */ 4591 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Shift + Escape to place focus on the toolbar and navigate between options with your arrow keys. To exit this text editor use the Escape key and continue tabbing. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift L adds an HTML link</li></ul>', 4592 /** 4593 * @property STR_TITLE 4594 * @description The Title of the HTML document that is created in the iFrame 4595 * @type String 4596 */ 4597 STR_TITLE: 'Rich Text Area.', 4598 /** 4599 * @property STR_IMAGE_HERE 4600 * @description The text to place in the URL textbox when using the blankimage. 4601 * @type String 4602 */ 4603 STR_IMAGE_HERE: 'Image URL Here', 4604 /** 4605 * @property STR_IMAGE_URL 4606 * @description The label string for Image URL 4607 * @type String 4608 */ 4609 STR_IMAGE_URL: 'Image URL', 4610 /** 4611 * @property STR_LINK_URL 4612 * @description The label string for the Link URL. 4613 * @type String 4614 */ 4615 STR_LINK_URL: 'Link URL', 4616 /** 4617 * @protected 4618 * @property STOP_EXEC_COMMAND 4619 * @description Set to true when you want the default execCommand function to not process anything 4620 * @type Boolean 4621 */ 4622 STOP_EXEC_COMMAND: false, 4623 /** 4624 * @protected 4625 * @property STOP_NODE_CHANGE 4626 * @description Set to true when you want the default nodeChange function to not process anything 4627 * @type Boolean 4628 */ 4629 STOP_NODE_CHANGE: false, 4630 /** 4631 * @protected 4632 * @property CLASS_NOEDIT 4633 * @description CSS class applied to elements that are not editable. 4634 * @type String 4635 */ 4636 CLASS_NOEDIT: 'yui-noedit', 4637 /** 4638 * @protected 4639 * @property CLASS_CONTAINER 4640 * @description Default CSS class to apply to the editors container element 4641 * @type String 4642 */ 4643 CLASS_CONTAINER: 'yui-editor-container', 4644 /** 4645 * @protected 4646 * @property CLASS_EDITABLE 4647 * @description Default CSS class to apply to the editors iframe element 4648 * @type String 4649 */ 4650 CLASS_EDITABLE: 'yui-editor-editable', 4651 /** 4652 * @protected 4653 * @property CLASS_EDITABLE_CONT 4654 * @description Default CSS class to apply to the editors iframe's parent element 4655 * @type String 4656 */ 4657 CLASS_EDITABLE_CONT: 'yui-editor-editable-container', 4658 /** 4659 * @protected 4660 * @property CLASS_PREFIX 4661 * @description Default prefix for dynamically created class names 4662 * @type String 4663 */ 4664 CLASS_PREFIX: 'yui-editor', 4665 /** 4666 * @property browser 4667 * @description Standard browser detection 4668 * @type Object 4669 */ 4670 browser: function() { 4671 var br = YAHOO.env.ua; 4672 //Check for webkit3 4673 if (br.webkit >= 420) { 4674 br.webkit3 = br.webkit; 4675 } else { 4676 br.webkit3 = 0; 4677 } 4678 if (br.webkit >= 530) { 4679 br.webkit4 = br.webkit; 4680 } else { 4681 br.webkit4 = 0; 4682 } 4683 br.mac = false; 4684 //Check for Mac 4685 if (navigator.userAgent.indexOf('Macintosh') !== -1) { 4686 br.mac = true; 4687 } 4688 4689 return br; 4690 }(), 4691 /** 4692 * @method init 4693 * @description The Editor class' initialization method 4694 */ 4695 init: function(p_oElement, p_oAttributes) { 4696 4697 if (!this._defaultToolbar) { 4698 this._defaultToolbar = { 4699 collapse: true, 4700 titlebar: 'Text Editing Tools', 4701 draggable: false, 4702 buttons: [ 4703 { group: 'fontstyle', label: 'Font Name and Size', 4704 buttons: [ 4705 { type: 'select', label: 'Arial', value: 'fontname', disabled: true, 4706 menu: [ 4707 { text: 'Arial', checked: true }, 4708 { text: 'Arial Black' }, 4709 { text: 'Comic Sans MS' }, 4710 { text: 'Courier New' }, 4711 { text: 'Lucida Console' }, 4712 { text: 'Tahoma' }, 4713 { text: 'Times New Roman' }, 4714 { text: 'Trebuchet MS' }, 4715 { text: 'Verdana' } 4716 ] 4717 }, 4718 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true } 4719 ] 4720 }, 4721 { type: 'separator' }, 4722 { group: 'textstyle', label: 'Font Style', 4723 buttons: [ 4724 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' }, 4725 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' }, 4726 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' }, 4727 { type: 'push', label: 'Strike Through', value: 'strikethrough' }, 4728 { type: 'separator' }, 4729 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true }, 4730 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true } 4731 4732 ] 4733 }, 4734 { type: 'separator' }, 4735 { group: 'indentlist', label: 'Lists', 4736 buttons: [ 4737 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' }, 4738 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' } 4739 ] 4740 }, 4741 { type: 'separator' }, 4742 { group: 'insertitem', label: 'Insert Item', 4743 buttons: [ 4744 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true }, 4745 { type: 'push', label: 'Insert Image', value: 'insertimage' } 4746 ] 4747 } 4748 ] 4749 }; 4750 } 4751 4752 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes); 4753 YAHOO.widget.EditorInfo._instances[this.get('id')] = this; 4754 4755 4756 this.currentElement = []; 4757 this.on('contentReady', function() { 4758 this.DOMReady = true; 4759 this.fireQueue(); 4760 }, this, true); 4761 4762 }, 4763 /** 4764 * @method initAttributes 4765 * @description Initializes all of the configuration attributes used to create 4766 * the editor. 4767 * @param {Object} attr Object literal specifying a set of 4768 * configuration attributes used to create the editor. 4769 */ 4770 initAttributes: function(attr) { 4771 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr); 4772 var self = this; 4773 4774 /** 4775 * @config setDesignMode 4776 * @description Should the Editor set designMode on the document. Default: true. 4777 * @default true 4778 * @type Boolean 4779 */ 4780 this.setAttributeConfig('setDesignMode', { 4781 value: ((attr.setDesignMode === false) ? false : true) 4782 }); 4783 /** 4784 * @config nodeChangeDelay 4785 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true. 4786 * @default true 4787 * @type Boolean 4788 */ 4789 this.setAttributeConfig('nodeChangeDelay', { 4790 value: ((attr.nodeChangeDelay === false) ? false : true) 4791 }); 4792 /** 4793 * @config maxUndo 4794 * @description The max number of undo levels to store. 4795 * @default 30 4796 * @type Number 4797 */ 4798 this.setAttributeConfig('maxUndo', { 4799 writeOnce: true, 4800 value: attr.maxUndo || 30 4801 }); 4802 4803 /** 4804 * @config ptags 4805 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>) 4806 * @default false 4807 * @type Boolean 4808 */ 4809 this.setAttributeConfig('ptags', { 4810 writeOnce: true, 4811 value: attr.ptags || false 4812 }); 4813 /** 4814 * @config insert 4815 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor. 4816 * @default false 4817 * @type Boolean 4818 */ 4819 this.setAttributeConfig('insert', { 4820 writeOnce: true, 4821 value: attr.insert || false, 4822 method: function(insert) { 4823 if (insert) { 4824 var buttons = { 4825 fontname: true, 4826 fontsize: true, 4827 forecolor: true, 4828 backcolor: true 4829 }; 4830 var tmp = this._defaultToolbar.buttons; 4831 for (var i = 0; i < tmp.length; i++) { 4832 if (tmp[i].buttons) { 4833 for (var a = 0; a < tmp[i].buttons.length; a++) { 4834 if (tmp[i].buttons[a].value) { 4835 if (buttons[tmp[i].buttons[a].value]) { 4836 delete tmp[i].buttons[a].disabled; 4837 } 4838 } 4839 } 4840 } 4841 } 4842 } 4843 } 4844 }); 4845 /** 4846 * @config container 4847 * @description Used when dynamically creating the Editor from Javascript with no default textarea. 4848 * We will create one and place it in this container. If no container is passed we will append to document.body. 4849 * @default false 4850 * @type HTMLElement 4851 */ 4852 this.setAttributeConfig('container', { 4853 writeOnce: true, 4854 value: attr.container || false 4855 }); 4856 /** 4857 * @config plainText 4858 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds. 4859 * @default false 4860 * @type Boolean 4861 */ 4862 this.setAttributeConfig('plainText', { 4863 writeOnce: true, 4864 value: attr.plainText || false 4865 }); 4866 /** 4867 * @private 4868 * @config iframe 4869 * @description Internal config for holding the iframe element. 4870 * @default null 4871 * @type HTMLElement 4872 */ 4873 this.setAttributeConfig('iframe', { 4874 value: null 4875 }); 4876 /** 4877 * @private 4878 * @config disabled_iframe 4879 * @description Internal config for holding the iframe element used when disabling the Editor. 4880 * @default null 4881 * @type HTMLElement 4882 */ 4883 this.setAttributeConfig('disabled_iframe', { 4884 value: null 4885 }); 4886 /** 4887 * @private 4888 * @depreciated - No longer used, should use this.get('element') 4889 * @config textarea 4890 * @description Internal config for holding the textarea element (replaced with element). 4891 * @default null 4892 * @type HTMLElement 4893 */ 4894 this.setAttributeConfig('textarea', { 4895 value: null, 4896 writeOnce: true 4897 }); 4898 /** 4899 * @config nodeChangeThreshold 4900 * @description The number of seconds that need to be in between nodeChange processing 4901 * @default 3 4902 * @type Number 4903 */ 4904 this.setAttributeConfig('nodeChangeThreshold', { 4905 value: attr.nodeChangeThreshold || 3, 4906 validator: YAHOO.lang.isNumber 4907 }); 4908 /** 4909 * @config allowNoEdit 4910 * @description Should the editor check for non-edit fields. It should be noted that this technique is not perfect. If the user does the right things, they will still be able to make changes. 4911 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key. 4912 * @default false 4913 * @type Boolean 4914 */ 4915 this.setAttributeConfig('allowNoEdit', { 4916 value: attr.allowNoEdit || false, 4917 validator: YAHOO.lang.isBoolean 4918 }); 4919 /** 4920 * @config limitCommands 4921 * @description Should the Editor limit the allowed execCommands to the ones available in the toolbar. If true, then execCommand and keyboard shortcuts will fail if they are not defined in the toolbar. 4922 * @default false 4923 * @type Boolean 4924 */ 4925 this.setAttributeConfig('limitCommands', { 4926 value: attr.limitCommands || false, 4927 validator: YAHOO.lang.isBoolean 4928 }); 4929 /** 4930 * @config element_cont 4931 * @description Internal config for the editors container 4932 * @default false 4933 * @type HTMLElement 4934 */ 4935 this.setAttributeConfig('element_cont', { 4936 value: attr.element_cont 4937 }); 4938 /** 4939 * @private 4940 * @config editor_wrapper 4941 * @description The outter wrapper for the entire editor. 4942 * @default null 4943 * @type HTMLElement 4944 */ 4945 this.setAttributeConfig('editor_wrapper', { 4946 value: attr.editor_wrapper || null, 4947 writeOnce: true 4948 }); 4949 /** 4950 * @attribute height 4951 * @description The height of the editor iframe container, not including the toolbar.. 4952 * @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 4953 * @type String 4954 */ 4955 this.setAttributeConfig('height', { 4956 value: attr.height || Dom.getStyle(self.get('element'), 'height'), 4957 method: function(height) { 4958 if (this._rendered) { 4959 //We have been rendered, change the height 4960 if (this.get('animate')) { 4961 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), { 4962 height: { 4963 to: parseInt(height, 10) 4964 } 4965 }, 0.5); 4966 anim.animate(); 4967 } else { 4968 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height); 4969 } 4970 } 4971 } 4972 }); 4973 /** 4974 * @config autoHeight 4975 * @description Remove the scrollbars from the edit area and resize it to fit the content. It will not go any lower than the current config height. 4976 * @default false 4977 * @type Boolean || Number 4978 */ 4979 this.setAttributeConfig('autoHeight', { 4980 value: attr.autoHeight || false, 4981 method: function(a) { 4982 if (a) { 4983 if (this.get('iframe')) { 4984 this.get('iframe').get('element').setAttribute('scrolling', 'no'); 4985 } 4986 this.on('afterNodeChange', this._handleAutoHeight, this, true); 4987 this.on('editorKeyDown', this._handleAutoHeight, this, true); 4988 this.on('editorKeyPress', this._handleAutoHeight, this, true); 4989 } else { 4990 if (this.get('iframe')) { 4991 this.get('iframe').get('element').setAttribute('scrolling', 'auto'); 4992 } 4993 this.unsubscribe('afterNodeChange', this._handleAutoHeight); 4994 this.unsubscribe('editorKeyDown', this._handleAutoHeight); 4995 this.unsubscribe('editorKeyPress', this._handleAutoHeight); 4996 } 4997 } 4998 }); 4999 /** 5000 * @attribute width 5001 * @description The width of the editor container. 5002 * @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 5003 * @type String 5004 */ 5005 this.setAttributeConfig('width', { 5006 value: attr.width || Dom.getStyle(this.get('element'), 'width'), 5007 method: function(width) { 5008 if (this._rendered) { 5009 //We have been rendered, change the width 5010 if (this.get('animate')) { 5011 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), { 5012 width: { 5013 to: parseInt(width, 10) 5014 } 5015 }, 0.5); 5016 anim.animate(); 5017 } else { 5018 this.get('element_cont').setStyle('width', width); 5019 } 5020 } 5021 } 5022 }); 5023 5024 /** 5025 * @attribute blankimage 5026 * @description The URL for the image placeholder to put in when inserting an image. 5027 * @default The yahooapis.com address for the current release + 'assets/blankimage.png' 5028 * @type String 5029 */ 5030 this.setAttributeConfig('blankimage', { 5031 value: attr.blankimage || this._getBlankImage() 5032 }); 5033 /** 5034 * @attribute css 5035 * @description The Base CSS used to format the content of the editor 5036 * @default <code><pre>html { 5037 height: 95%; 5038 } 5039 body { 5040 height: 100%; 5041 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; 5042 } 5043 a { 5044 color: blue; 5045 text-decoration: underline; 5046 cursor: pointer; 5047 } 5048 .warning-localfile { 5049 border-bottom: 1px dashed red !important; 5050 } 5051 .yui-busy { 5052 cursor: wait !important; 5053 } 5054 img.selected { //Safari image selection 5055 border: 2px dotted #808080; 5056 } 5057 img { 5058 cursor: pointer !important; 5059 border: none; 5060 } 5061 </pre></code> 5062 * @type String 5063 */ 5064 this.setAttributeConfig('css', { 5065 value: attr.css || this._defaultCSS, 5066 writeOnce: true 5067 }); 5068 /** 5069 * @attribute html 5070 * @description The default HTML to be written to the iframe document before the contents are loaded (Note that the DOCTYPE attr will be added at render item) 5071 * @default This HTML requires a few things if you are to override: 5072 <p><code>{TITLE}, {CSS}, {HIDDEN_CSS}, {EXTRA_CSS}</code> and <code>{CONTENT}</code> need to be there, they are passed to YAHOO.lang.substitute to be replace with other strings.<p> 5073 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p> 5074 <code> 5075 <pre> 5076 <html> 5077 <head> 5078 <title>{TITLE}</title> 5079 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 5080 <style> 5081 {CSS} 5082 </style> 5083 <style> 5084 {HIDDEN_CSS} 5085 </style> 5086 <style> 5087 {EXTRA_CSS} 5088 </style> 5089 </head> 5090 <body onload="document.body._rteLoaded = true;"> 5091 {CONTENT} 5092 </body> 5093 </html> 5094 </pre> 5095 </code> 5096 * @type String 5097 */ 5098 this.setAttributeConfig('html', { 5099 value: attr.html || '<html><head><title>{TITLE}</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><base href="' + this._baseHREF + '"><style>{CSS}</style><style>{HIDDEN_CSS}</style><style>{EXTRA_CSS}</style></head><body onload="document.body._rteLoaded = true;">{CONTENT}</body></html>', 5100 writeOnce: true 5101 }); 5102 5103 /** 5104 * @attribute extracss 5105 * @description Extra user defined css to load after the default SimpleEditor CSS 5106 * @default '' 5107 * @type String 5108 */ 5109 this.setAttributeConfig('extracss', { 5110 value: attr.extracss || '', 5111 writeOnce: true 5112 }); 5113 5114 /** 5115 * @attribute handleSubmit 5116 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler. 5117 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form. 5118 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted. 5119 * @default false 5120 * @type Boolean 5121 */ 5122 this.setAttributeConfig('handleSubmit', { 5123 value: attr.handleSubmit || false, 5124 method: function(exec) { 5125 if (this.get('element').form) { 5126 if (!this._formButtons) { 5127 this._formButtons = []; 5128 } 5129 if (exec) { 5130 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true); 5131 var i = this.get('element').form.getElementsByTagName('input'); 5132 for (var s = 0; s < i.length; s++) { 5133 var type = i[s].getAttribute('type'); 5134 if (type && (type.toLowerCase() == 'submit')) { 5135 Event.on(i[s], 'click', this._handleFormButtonClick, this, true); 5136 this._formButtons[this._formButtons.length] = i[s]; 5137 } 5138 } 5139 } else { 5140 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit); 5141 if (this._formButtons) { 5142 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick); 5143 } 5144 } 5145 } 5146 } 5147 }); 5148 /** 5149 * @attribute disabled 5150 * @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. 5151 All Toolbar buttons are also disabled so they cannot be used. 5152 * @default false 5153 * @type Boolean 5154 */ 5155 5156 this.setAttributeConfig('disabled', { 5157 value: false, 5158 method: function(disabled) { 5159 if (this._rendered) { 5160 this._disableEditor(disabled); 5161 } 5162 } 5163 }); 5164 /** 5165 * @config saveEl 5166 * @description When save HTML is called, this element will be updated as well as the source of data. 5167 * @default element 5168 * @type HTMLElement 5169 */ 5170 this.setAttributeConfig('saveEl', { 5171 value: this.get('element') 5172 }); 5173 /** 5174 * @config toolbar_cont 5175 * @description Internal config for the toolbars container 5176 * @default false 5177 * @type Boolean 5178 */ 5179 this.setAttributeConfig('toolbar_cont', { 5180 value: null, 5181 writeOnce: true 5182 }); 5183 /** 5184 * @attribute toolbar 5185 * @description The default toolbar config. 5186 * @type Object 5187 */ 5188 this.setAttributeConfig('toolbar', { 5189 value: attr.toolbar || this._defaultToolbar, 5190 writeOnce: true, 5191 method: function(toolbar) { 5192 if (!toolbar.buttonType) { 5193 toolbar.buttonType = this._defaultToolbar.buttonType; 5194 } 5195 this._defaultToolbar = toolbar; 5196 } 5197 }); 5198 /** 5199 * @attribute animate 5200 * @description Should the editor animate window movements 5201 * @default false unless Animation is found, then true 5202 * @type Boolean 5203 */ 5204 this.setAttributeConfig('animate', { 5205 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false), 5206 validator: function(value) { 5207 var ret = true; 5208 if (!YAHOO.util.Anim) { 5209 ret = false; 5210 } 5211 return ret; 5212 } 5213 }); 5214 /** 5215 * @config panel 5216 * @description A reference to the panel we are using for windows. 5217 * @default false 5218 * @type Boolean 5219 */ 5220 this.setAttributeConfig('panel', { 5221 value: null, 5222 writeOnce: true, 5223 validator: function(value) { 5224 var ret = true; 5225 if (!YAHOO.widget.Overlay) { 5226 ret = false; 5227 } 5228 return ret; 5229 } 5230 }); 5231 /** 5232 * @attribute focusAtStart 5233 * @description Should we focus the window when the content is ready? 5234 * @default false 5235 * @type Boolean 5236 */ 5237 this.setAttributeConfig('focusAtStart', { 5238 value: attr.focusAtStart || false, 5239 writeOnce: true, 5240 method: function(fs) { 5241 if (fs) { 5242 this.on('editorContentLoaded', function() { 5243 var self = this; 5244 setTimeout(function() { 5245 self.focus.call(self); 5246 self.editorDirty = false; 5247 }, 400); 5248 }, this, true); 5249 } 5250 } 5251 }); 5252 /** 5253 * @attribute dompath 5254 * @description Toggle the display of the current Dom path below the editor 5255 * @default false 5256 * @type Boolean 5257 */ 5258 this.setAttributeConfig('dompath', { 5259 value: attr.dompath || false, 5260 method: function(dompath) { 5261 if (dompath && !this.dompath) { 5262 this.dompath = document.createElement('DIV'); 5263 this.dompath.id = this.get('id') + '_dompath'; 5264 Dom.addClass(this.dompath, 'dompath'); 5265 this.get('element_cont').get('firstChild').appendChild(this.dompath); 5266 if (this.get('iframe')) { 5267 this._writeDomPath(); 5268 } 5269 } else if (!dompath && this.dompath) { 5270 this.dompath.parentNode.removeChild(this.dompath); 5271 this.dompath = null; 5272 } 5273 } 5274 }); 5275 /** 5276 * @attribute markup 5277 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml 5278 * @default "semantic" 5279 * @type String 5280 */ 5281 this.setAttributeConfig('markup', { 5282 value: attr.markup || 'semantic', 5283 validator: function(markup) { 5284 switch (markup.toLowerCase()) { 5285 case 'semantic': 5286 case 'css': 5287 case 'default': 5288 case 'xhtml': 5289 return true; 5290 } 5291 return false; 5292 } 5293 }); 5294 /** 5295 * @attribute removeLineBreaks 5296 * @description Should we remove linebreaks and extra spaces on cleanup 5297 * @default false 5298 * @type Boolean 5299 */ 5300 this.setAttributeConfig('removeLineBreaks', { 5301 value: attr.removeLineBreaks || false, 5302 validator: YAHOO.lang.isBoolean 5303 }); 5304 5305 /** 5306 * @config drag 5307 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy. 5308 * @type {Boolean/String} 5309 */ 5310 this.setAttributeConfig('drag', { 5311 writeOnce: true, 5312 value: attr.drag || false 5313 }); 5314 5315 /** 5316 * @config resize 5317 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig 5318 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar. 5319 * @type Boolean 5320 */ 5321 this.setAttributeConfig('resize', { 5322 writeOnce: true, 5323 value: attr.resize || false 5324 }); 5325 5326 /** 5327 * @config filterWord 5328 * @description Attempt to filter out MS Word HTML from the Editor's output. 5329 * @type Boolean 5330 */ 5331 this.setAttributeConfig('filterWord', { 5332 value: attr.filterWord || false, 5333 validator: YAHOO.lang.isBoolean 5334 }); 5335 5336 }, 5337 /** 5338 * @private 5339 * @method _getBlankImage 5340 * @description Retrieves the full url of the image to use as the blank image. 5341 * @return {String} The URL to the blank image 5342 */ 5343 _getBlankImage: function() { 5344 if (!this.DOMReady) { 5345 this._queue[this._queue.length] = ['_getBlankImage', arguments]; 5346 return ''; 5347 } 5348 var img = ''; 5349 if (!this._blankImageLoaded) { 5350 if (YAHOO.widget.EditorInfo.blankImage) { 5351 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage); 5352 this._blankImageLoaded = true; 5353 } else { 5354 var div = document.createElement('div'); 5355 div.style.position = 'absolute'; 5356 div.style.top = '-9999px'; 5357 div.style.left = '-9999px'; 5358 div.className = this.CLASS_PREFIX + '-blankimage'; 5359 document.body.appendChild(div); 5360 img = YAHOO.util.Dom.getStyle(div, 'background-image'); 5361 img = img.replace('url(', '').replace(')', '').replace(/"/g, ''); 5362 //Adobe AIR Code 5363 img = img.replace('app:/', ''); 5364 this.set('blankimage', img); 5365 this._blankImageLoaded = true; 5366 div.parentNode.removeChild(div); 5367 YAHOO.widget.EditorInfo.blankImage = img; 5368 } 5369 } else { 5370 img = this.get('blankimage'); 5371 } 5372 return img; 5373 }, 5374 /** 5375 * @private 5376 * @method _handleAutoHeight 5377 * @description Handles resizing the editor's height based on the content 5378 */ 5379 _handleAutoHeight: function() { 5380 var doc = this._getDoc(), 5381 body = doc.body, 5382 docEl = doc.documentElement; 5383 5384 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10); 5385 var newHeight = body.scrollHeight; 5386 if (this.browser.webkit) { 5387 newHeight = docEl.scrollHeight; 5388 } 5389 if (newHeight < parseInt(this.get('height'), 10)) { 5390 newHeight = parseInt(this.get('height'), 10); 5391 } 5392 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) { 5393 var anim = this.get('animate'); 5394 this.set('animate', false); 5395 this.set('height', newHeight + 'px'); 5396 this.set('animate', anim); 5397 if (this.browser.ie) { 5398 //Internet Explorer needs this 5399 this.get('iframe').setStyle('height', '99%'); 5400 this.get('iframe').setStyle('zoom', '1'); 5401 var self = this; 5402 window.setTimeout(function() { 5403 self.get('iframe').setStyle('height', '100%'); 5404 }, 1); 5405 } 5406 } 5407 }, 5408 /** 5409 * @private 5410 * @property _formButtons 5411 * @description Array of buttons that are in the Editor's parent form (for handleSubmit) 5412 * @type Array 5413 */ 5414 _formButtons: null, 5415 /** 5416 * @private 5417 * @property _formButtonClicked 5418 * @description The form button that was clicked to submit the form. 5419 * @type HTMLElement 5420 */ 5421 _formButtonClicked: null, 5422 /** 5423 * @private 5424 * @method _handleFormButtonClick 5425 * @description The click listener assigned to each submit button in the Editor's parent form. 5426 * @param {Event} ev The click event 5427 */ 5428 _handleFormButtonClick: function(ev) { 5429 var tar = Event.getTarget(ev); 5430 this._formButtonClicked = tar; 5431 }, 5432 /** 5433 * @private 5434 * @method _handleFormSubmit 5435 * @description Handles the form submission. 5436 * @param {Object} ev The Form Submit Event 5437 */ 5438 _handleFormSubmit: function(ev) { 5439 this.saveHTML(); 5440 5441 var form = this.get('element').form, 5442 tar = this._formButtonClicked || false; 5443 5444 Event.removeListener(form, 'submit', this._handleFormSubmit); 5445 if (YAHOO.env.ua.ie) { 5446 //form.fireEvent("onsubmit"); 5447 if (tar && !tar.disabled) { 5448 tar.click(); 5449 } 5450 } else { // Gecko, Opera, and Safari 5451 if (tar && !tar.disabled) { 5452 tar.click(); 5453 } 5454 var oEvent = document.createEvent("HTMLEvents"); 5455 oEvent.initEvent("submit", true, true); 5456 form.dispatchEvent(oEvent); 5457 if (YAHOO.env.ua.webkit) { 5458 if (YAHOO.lang.isFunction(form.submit)) { 5459 form.submit(); 5460 } 5461 } 5462 } 5463 //2.6.0 5464 //Removed this, not need since removing Safari 2.x 5465 //Event.stopEvent(ev); 5466 }, 5467 /** 5468 * @private 5469 * @method _handleFontSize 5470 * @description Handles the font size button in the toolbar. 5471 * @param {Object} o Object returned from Toolbar's buttonClick Event 5472 */ 5473 _handleFontSize: function(o) { 5474 var button = this.toolbar.getButtonById(o.button.id); 5475 var value = button.get('label') + 'px'; 5476 this.execCommand('fontsize', value); 5477 return false; 5478 }, 5479 /** 5480 * @private 5481 * @description Handles the colorpicker buttons in the toolbar. 5482 * @param {Object} o Object returned from Toolbar's buttonClick Event 5483 */ 5484 _handleColorPicker: function(o) { 5485 var cmd = o.button; 5486 var value = '#' + o.color; 5487 if ((cmd == 'forecolor') || (cmd == 'backcolor')) { 5488 this.execCommand(cmd, value); 5489 } 5490 }, 5491 /** 5492 * @private 5493 * @method _handleAlign 5494 * @description Handles the alignment buttons in the toolbar. 5495 * @param {Object} o Object returned from Toolbar's buttonClick Event 5496 */ 5497 _handleAlign: function(o) { 5498 var cmd = null; 5499 for (var i = 0; i < o.button.menu.length; i++) { 5500 if (o.button.menu[i].value == o.button.value) { 5501 cmd = o.button.menu[i].value; 5502 } 5503 } 5504 var value = this._getSelection(); 5505 5506 this.execCommand(cmd, value); 5507 return false; 5508 }, 5509 /** 5510 * @private 5511 * @method _handleAfterNodeChange 5512 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state). 5513 */ 5514 _handleAfterNodeChange: function() { 5515 var path = this._getDomPath(), 5516 elm = null, 5517 family = null, 5518 fontsize = null, 5519 validFont = false, 5520 fn_button = this.toolbar.getButtonByValue('fontname'), 5521 fs_button = this.toolbar.getButtonByValue('fontsize'), 5522 hd_button = this.toolbar.getButtonByValue('heading'); 5523 5524 for (var i = 0; i < path.length; i++) { 5525 elm = path[i]; 5526 5527 var tag = elm.tagName.toLowerCase(); 5528 5529 5530 if (elm.getAttribute('tag')) { 5531 tag = elm.getAttribute('tag'); 5532 } 5533 5534 family = elm.getAttribute('face'); 5535 if (Dom.getStyle(elm, 'font-family')) { 5536 family = Dom.getStyle(elm, 'font-family'); 5537 //Adobe AIR Code 5538 family = family.replace(/'/g, ''); 5539 } 5540 5541 if (tag.substring(0, 1) == 'h') { 5542 if (hd_button) { 5543 for (var h = 0; h < hd_button._configs.menu.value.length; h++) { 5544 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) { 5545 hd_button.set('label', hd_button._configs.menu.value[h].text); 5546 } 5547 } 5548 this._updateMenuChecked('heading', tag); 5549 } 5550 } 5551 } 5552 5553 if (fn_button) { 5554 for (var b = 0; b < fn_button._configs.menu.value.length; b++) { 5555 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) { 5556 validFont = true; 5557 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button 5558 } 5559 } 5560 if (!validFont) { 5561 family = fn_button._configs.label._initialConfig.value; 5562 } 5563 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>'; 5564 if (fn_button.get('label') != familyLabel) { 5565 fn_button.set('label', familyLabel); 5566 this._updateMenuChecked('fontname', family); 5567 } 5568 } 5569 5570 if (fs_button) { 5571 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10); 5572 if ((fontsize === null) || isNaN(fontsize)) { 5573 fontsize = fs_button._configs.label._initialConfig.value; 5574 } 5575 fs_button.set('label', ''+fontsize); 5576 } 5577 5578 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) { 5579 this.toolbar.enableButton(fn_button); 5580 this.toolbar.enableButton(fs_button); 5581 this.toolbar.enableButton('forecolor'); 5582 this.toolbar.enableButton('backcolor'); 5583 } 5584 if (this._isElement(elm, 'img')) { 5585 if (YAHOO.widget.Overlay) { 5586 this.toolbar.enableButton('createlink'); 5587 } 5588 } 5589 if (this._hasParent(elm, 'blockquote')) { 5590 this.toolbar.selectButton('indent'); 5591 this.toolbar.disableButton('indent'); 5592 this.toolbar.enableButton('outdent'); 5593 } 5594 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) { 5595 this.toolbar.disableButton('indent'); 5596 } 5597 this._lastButton = null; 5598 5599 }, 5600 /** 5601 * @private 5602 * @method _handleInsertImageClick 5603 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked. 5604 */ 5605 _handleInsertImageClick: function() { 5606 if (this.get('limitCommands')) { 5607 if (!this.toolbar.getButtonByValue('insertimage')) { 5608 return false; 5609 } 5610 } 5611 5612 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing 5613 var _handleAEC = function() { 5614 var el = this.currentElement[0], 5615 src = 'http://'; 5616 if (!el) { 5617 el = this._getSelectedElement(); 5618 } 5619 if (el) { 5620 if (el.getAttribute('src')) { 5621 src = el.getAttribute('src', 2); 5622 if (src.indexOf(this.get('blankimage')) != -1) { 5623 src = this.STR_IMAGE_HERE; 5624 } 5625 } 5626 } 5627 var str = prompt(this.STR_IMAGE_URL + ': ', src); 5628 if ((str !== '') && (str !== null)) { 5629 el.setAttribute('src', str); 5630 } else if (str === '') { 5631 el.parentNode.removeChild(el); 5632 this.currentElement = []; 5633 this.nodeChange(); 5634 } else if ((str === null)) { 5635 src = el.getAttribute('src', 2); 5636 if (src.indexOf(this.get('blankimage')) != -1) { 5637 el.parentNode.removeChild(el); 5638 this.currentElement = []; 5639 this.nodeChange(); 5640 } 5641 } 5642 this.closeWindow(); 5643 this.toolbar.set('disabled', false); 5644 this.unsubscribe('afterExecCommand', _handleAEC, this, true); 5645 }; 5646 this.on('afterExecCommand', _handleAEC, this, true); 5647 }, 5648 /** 5649 * @private 5650 * @method _handleInsertImageWindowClose 5651 * @description Handles the closing of the Image Properties Window. 5652 */ 5653 _handleInsertImageWindowClose: function() { 5654 this.nodeChange(); 5655 }, 5656 /** 5657 * @private 5658 * @method _isLocalFile 5659 * @param {String} url THe url/string to check 5660 * @description Checks to see if a string (href or img src) is possibly a local file reference.. 5661 */ 5662 _isLocalFile: function(url) { 5663 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) { 5664 return true; 5665 } 5666 return false; 5667 }, 5668 /** 5669 * @private 5670 * @method _handleCreateLinkClick 5671 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked. 5672 */ 5673 _handleCreateLinkClick: function() { 5674 if (this.get('limitCommands')) { 5675 if (!this.toolbar.getButtonByValue('createlink')) { 5676 return false; 5677 } 5678 } 5679 5680 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing 5681 5682 var _handleAEC = function() { 5683 var el = this.currentElement[0], 5684 url = ''; 5685 5686 if (el) { 5687 if (el.getAttribute('href', 2) !== null) { 5688 url = el.getAttribute('href', 2); 5689 } 5690 } 5691 var str = prompt(this.STR_LINK_URL + ': ', url); 5692 if ((str !== '') && (str !== null)) { 5693 var urlValue = str; 5694 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 5695 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 5696 //Found an @ sign, prefix with mailto: 5697 urlValue = 'mailto:' + urlValue; 5698 } else { 5699 /* :// not found adding */ 5700 if (urlValue.substring(0, 1) != '#') { 5701 //urlValue = 'http:/'+'/' + urlValue; 5702 } 5703 } 5704 } 5705 el.setAttribute('href', urlValue); 5706 } else if (str !== null) { 5707 var _span = this._getDoc().createElement('span'); 5708 _span.innerHTML = el.innerHTML; 5709 Dom.addClass(_span, 'yui-non'); 5710 el.parentNode.replaceChild(_span, el); 5711 } 5712 this.closeWindow(); 5713 this.toolbar.set('disabled', false); 5714 this.unsubscribe('afterExecCommand', _handleAEC, this, true); 5715 }; 5716 this.on('afterExecCommand', _handleAEC, this); 5717 5718 }, 5719 /** 5720 * @private 5721 * @method _handleCreateLinkWindowClose 5722 * @description Handles the closing of the Link Properties Window. 5723 */ 5724 _handleCreateLinkWindowClose: function() { 5725 this.nodeChange(); 5726 this.currentElement = []; 5727 }, 5728 /** 5729 * @method render 5730 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load. 5731 */ 5732 render: function() { 5733 if (this._rendered) { 5734 return false; 5735 } 5736 if (!this.DOMReady) { 5737 this._queue[this._queue.length] = ['render', arguments]; 5738 return false; 5739 } 5740 if (this.get('element')) { 5741 if (this.get('element').tagName) { 5742 this._textarea = true; 5743 if (this.get('element').tagName.toLowerCase() !== 'textarea') { 5744 this._textarea = false; 5745 } 5746 } else { 5747 return false; 5748 } 5749 } else { 5750 return false; 5751 } 5752 this._rendered = true; 5753 var self = this; 5754 window.setTimeout(function() { 5755 self._render.call(self); 5756 }, 4); 5757 }, 5758 /** 5759 * @private 5760 * @method _render 5761 * @description Causes the toolbar and the editor to render and replace the textarea. 5762 */ 5763 _render: function() { 5764 var self = this; 5765 this.set('textarea', this.get('element')); 5766 5767 this.get('element_cont').setStyle('display', 'none'); 5768 this.get('element_cont').addClass(this.CLASS_CONTAINER); 5769 5770 this.set('iframe', this._createIframe()); 5771 5772 window.setTimeout(function() { 5773 self._setInitialContent.call(self); 5774 }, 10); 5775 5776 this.get('editor_wrapper').appendChild(this.get('iframe').get('element')); 5777 5778 if (this.get('disabled')) { 5779 this._disableEditor(true); 5780 } 5781 5782 var tbarConf = this.get('toolbar'); 5783 //Create Toolbar instance 5784 if (tbarConf instanceof Toolbar) { 5785 this.toolbar = tbarConf; 5786 //Set the toolbar to disabled until content is loaded 5787 this.toolbar.set('disabled', true); 5788 } else { 5789 //Set the toolbar to disabled until content is loaded 5790 tbarConf.disabled = true; 5791 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf); 5792 } 5793 5794 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar }); 5795 5796 5797 this.toolbar.on('toolbarCollapsed', function() { 5798 if (this.currentWindow) { 5799 this.moveWindow(); 5800 } 5801 }, this, true); 5802 this.toolbar.on('toolbarExpanded', function() { 5803 if (this.currentWindow) { 5804 this.moveWindow(); 5805 } 5806 }, this, true); 5807 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true); 5808 5809 this.toolbar.on('colorPickerClicked', function(o) { 5810 this._handleColorPicker(o); 5811 return false; //Stop the buttonClick event 5812 }, this, true); 5813 5814 this.toolbar.on('alignClick', this._handleAlign, this, true); 5815 this.on('afterNodeChange', this._handleAfterNodeChange, this, true); 5816 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true); 5817 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true); 5818 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true); 5819 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true); 5820 5821 5822 //Replace Textarea with editable area 5823 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element')); 5824 5825 5826 this.setStyle('visibility', 'hidden'); 5827 this.setStyle('position', 'absolute'); 5828 this.setStyle('top', '-9999px'); 5829 this.setStyle('left', '-9999px'); 5830 this.get('element_cont').appendChild(this.get('element')); 5831 this.get('element_cont').setStyle('display', 'block'); 5832 5833 5834 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT); 5835 this.get('iframe').addClass(this.CLASS_EDITABLE); 5836 5837 //Set height and width of editor container 5838 this.get('element_cont').setStyle('width', this.get('width')); 5839 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height')); 5840 5841 this.get('iframe').setStyle('width', '100%'); //WIDTH 5842 this.get('iframe').setStyle('height', '100%'); 5843 5844 this._setupDD(); 5845 5846 window.setTimeout(function() { 5847 self._setupAfterElement.call(self); 5848 }, 0); 5849 this.fireEvent('afterRender', { type: 'afterRender', target: this }); 5850 }, 5851 /** 5852 * @method execCommand 5853 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml) 5854 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana' 5855 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions 5856 */ 5857 execCommand: function(action, value) { 5858 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments }); 5859 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) { 5860 this.STOP_EXEC_COMMAND = false; 5861 return false; 5862 } 5863 this._lastCommand = action; 5864 this._setMarkupType(action); 5865 if (this.browser.ie) { 5866 this._getWindow().focus(); 5867 } 5868 var exec = true; 5869 5870 if (this.get('limitCommands')) { 5871 if (!this.toolbar.getButtonByValue(action)) { 5872 exec = false; 5873 } 5874 } 5875 5876 this.editorDirty = true; 5877 5878 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) { 5879 var retValue = this['cmd_' + action.toLowerCase()](value); 5880 exec = retValue[0]; 5881 if (retValue[1]) { 5882 action = retValue[1]; 5883 } 5884 if (retValue[2]) { 5885 value = retValue[2]; 5886 } 5887 } 5888 if (exec) { 5889 try { 5890 this._getDoc().execCommand(action, false, value); 5891 } catch(e) { 5892 } 5893 } else { 5894 } 5895 this.on('afterExecCommand', function() { 5896 this.unsubscribeAll('afterExecCommand'); 5897 this.nodeChange(); 5898 }, this, true); 5899 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 5900 5901 }, 5902 /* {{{ Command Overrides */ 5903 5904 /** 5905 * @method cmd_bold 5906 * @param value Value passed from the execCommand method 5907 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used. 5908 */ 5909 cmd_bold: function(value) { 5910 if (!this.browser.webkit) { 5911 var el = this._getSelectedElement(); 5912 if (el && this._isElement(el, 'span') && this._hasSelection()) { 5913 if (el.style.fontWeight == 'bold') { 5914 el.style.fontWeight = ''; 5915 var b = this._getDoc().createElement('b'), 5916 par = el.parentNode; 5917 par.replaceChild(b, el); 5918 b.appendChild(el); 5919 } 5920 } 5921 } 5922 return [true]; 5923 }, 5924 /** 5925 * @method cmd_italic 5926 * @param value Value passed from the execCommand method 5927 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used. 5928 */ 5929 5930 cmd_italic: function(value) { 5931 if (!this.browser.webkit) { 5932 var el = this._getSelectedElement(); 5933 if (el && this._isElement(el, 'span') && this._hasSelection()) { 5934 if (el.style.fontStyle == 'italic') { 5935 el.style.fontStyle = ''; 5936 var i = this._getDoc().createElement('i'), 5937 par = el.parentNode; 5938 par.replaceChild(i, el); 5939 i.appendChild(el); 5940 } 5941 } 5942 } 5943 return [true]; 5944 }, 5945 5946 5947 /** 5948 * @method cmd_underline 5949 * @param value Value passed from the execCommand method 5950 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used. 5951 */ 5952 cmd_underline: function(value) { 5953 if (!this.browser.webkit) { 5954 var el = this._getSelectedElement(); 5955 if (el && this._isElement(el, 'span')) { 5956 if (el.style.textDecoration == 'underline') { 5957 el.style.textDecoration = 'none'; 5958 } else { 5959 el.style.textDecoration = 'underline'; 5960 } 5961 return [false]; 5962 } 5963 } 5964 return [true]; 5965 }, 5966 /** 5967 * @method cmd_backcolor 5968 * @param value Value passed from the execCommand method 5969 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used. 5970 */ 5971 cmd_backcolor: function(value) { 5972 var exec = true, 5973 el = this._getSelectedElement(), 5974 action = 'backcolor'; 5975 5976 if (this.browser.gecko || this.browser.opera) { 5977 this._setEditorStyle(true); 5978 action = 'hilitecolor'; 5979 } 5980 5981 if (!this._isElement(el, 'body') && !this._hasSelection()) { 5982 el.style.backgroundColor = value; 5983 this._selectNode(el); 5984 exec = false; 5985 } else { 5986 if (this.get('insert')) { 5987 el = this._createInsertElement({ backgroundColor: value }); 5988 } else { 5989 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily }); 5990 this._selectNode(this.currentElement[0]); 5991 } 5992 exec = false; 5993 } 5994 5995 return [exec, action]; 5996 }, 5997 /** 5998 * @method cmd_forecolor 5999 * @param value Value passed from the execCommand method 6000 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used. 6001 */ 6002 cmd_forecolor: function(value) { 6003 var exec = true, 6004 el = this._getSelectedElement(); 6005 6006 if (!this._isElement(el, 'body') && !this._hasSelection()) { 6007 Dom.setStyle(el, 'color', value); 6008 this._selectNode(el); 6009 exec = false; 6010 } else { 6011 if (this.get('insert')) { 6012 el = this._createInsertElement({ color: value }); 6013 } else { 6014 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor }); 6015 this._selectNode(this.currentElement[0]); 6016 } 6017 exec = false; 6018 } 6019 return [exec]; 6020 }, 6021 /** 6022 * @method cmd_unlink 6023 * @param value Value passed from the execCommand method 6024 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used. 6025 */ 6026 cmd_unlink: function(value) { 6027 this._swapEl(this.currentElement[0], 'span', function(el) { 6028 el.className = 'yui-non'; 6029 }); 6030 return [false]; 6031 }, 6032 /** 6033 * @method cmd_createlink 6034 * @param value Value passed from the execCommand method 6035 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used. 6036 */ 6037 cmd_createlink: function(value) { 6038 var el = this._getSelectedElement(), _a = null; 6039 if (this._hasParent(el, 'a')) { 6040 this.currentElement[0] = this._hasParent(el, 'a'); 6041 } else if (this._isElement(el, 'li')) { 6042 _a = this._getDoc().createElement('a'); 6043 _a.innerHTML = el.innerHTML; 6044 el.innerHTML = ''; 6045 el.appendChild(_a); 6046 this.currentElement[0] = _a; 6047 } else if (!this._isElement(el, 'a')) { 6048 this._createCurrentElement('a'); 6049 _a = this._swapEl(this.currentElement[0], 'a'); 6050 this.currentElement[0] = _a; 6051 } else { 6052 this.currentElement[0] = el; 6053 } 6054 return [false]; 6055 }, 6056 /** 6057 * @method cmd_insertimage 6058 * @param value Value passed from the execCommand method 6059 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used. 6060 */ 6061 cmd_insertimage: function(value) { 6062 var exec = true, _img = null, action = 'insertimage', 6063 el = this._getSelectedElement(); 6064 6065 if (value === '') { 6066 value = this.get('blankimage'); 6067 } 6068 6069 /* 6070 * @knownissue Safari Cursor Position 6071 * @browser Safari 2.x 6072 * @description The issue here is that we have no way of knowing where the cursor position is 6073 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6074 */ 6075 6076 if (this._isElement(el, 'img')) { 6077 this.currentElement[0] = el; 6078 exec = false; 6079 } else { 6080 if (this._getDoc().queryCommandEnabled(action)) { 6081 this._getDoc().execCommand(action, false, value); 6082 var imgs = this._getDoc().getElementsByTagName('img'); 6083 for (var i = 0; i < imgs.length; i++) { 6084 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) { 6085 YAHOO.util.Dom.addClass(imgs[i], 'yui-img'); 6086 this.currentElement[0] = imgs[i]; 6087 } 6088 } 6089 exec = false; 6090 } else { 6091 if (el == this._getDoc().body) { 6092 _img = this._getDoc().createElement('img'); 6093 _img.setAttribute('src', value); 6094 YAHOO.util.Dom.addClass(_img, 'yui-img'); 6095 this._getDoc().body.appendChild(_img); 6096 } else { 6097 this._createCurrentElement('img'); 6098 _img = this._getDoc().createElement('img'); 6099 _img.setAttribute('src', value); 6100 YAHOO.util.Dom.addClass(_img, 'yui-img'); 6101 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]); 6102 } 6103 this.currentElement[0] = _img; 6104 exec = false; 6105 } 6106 } 6107 return [exec]; 6108 }, 6109 /** 6110 * @method cmd_inserthtml 6111 * @param value Value passed from the execCommand method 6112 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used. 6113 */ 6114 cmd_inserthtml: function(value) { 6115 var exec = true, action = 'inserthtml', _span = null, _range = null; 6116 /* 6117 * @knownissue Safari cursor position 6118 * @browser Safari 2.x 6119 * @description The issue here is that we have no way of knowing where the cursor position is 6120 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6121 */ 6122 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) { 6123 this._createCurrentElement('img'); 6124 _span = this._getDoc().createElement('span'); 6125 _span.innerHTML = value; 6126 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]); 6127 exec = false; 6128 } else if (this.browser.ie) { 6129 _range = this._getRange(); 6130 if (_range.item) { 6131 _range.item(0).outerHTML = value; 6132 } else { 6133 _range.pasteHTML(value); 6134 } 6135 exec = false; 6136 } 6137 return [exec]; 6138 }, 6139 /** 6140 * @method cmd_list 6141 * @param tag The tag of the list you want to create (eg, ul or ol) 6142 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods. 6143 */ 6144 cmd_list: function(tag) { 6145 var exec = true, list = null, li = 0, el = null, str = '', 6146 selEl = this._getSelectedElement(), action = 'insertorderedlist'; 6147 if (tag == 'ul') { 6148 action = 'insertunorderedlist'; 6149 } 6150 /* 6151 * @knownissue Safari 2.+ doesn't support ordered and unordered lists 6152 * @browser Safari 2.x 6153 * The issue with this workaround is that when applied to a set of text 6154 * that has BR's in it, Safari may or may not pick up the individual items as 6155 * list items. This is fixed in WebKit (Safari 3) 6156 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code 6157 */ 6158 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) { 6159 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) { 6160 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) { 6161 el = selEl.parentNode; 6162 list = this._getDoc().createElement('span'); 6163 YAHOO.util.Dom.addClass(list, 'yui-non'); 6164 str = ''; 6165 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div'); 6166 for (li = 0; li < lis.length; li++) { 6167 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>'; 6168 } 6169 list.innerHTML = str; 6170 this.currentElement[0] = el; 6171 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]); 6172 } else { 6173 this._createCurrentElement(tag.toLowerCase()); 6174 list = this._getDoc().createElement(tag); 6175 for (li = 0; li < this.currentElement.length; li++) { 6176 var newli = this._getDoc().createElement('li'); 6177 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> '; 6178 list.appendChild(newli); 6179 if (li > 0) { 6180 this.currentElement[li].parentNode.removeChild(this.currentElement[li]); 6181 } 6182 } 6183 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'), 6184 items = list.firstChild.innerHTML.split(b_tag), i, item; 6185 if (items.length > 0) { 6186 list.innerHTML = ''; 6187 for (i = 0; i < items.length; i++) { 6188 item = this._getDoc().createElement('li'); 6189 item.innerHTML = items[i]; 6190 list.appendChild(item); 6191 } 6192 } 6193 6194 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]); 6195 this.currentElement[0] = list; 6196 var _h = this.currentElement[0].firstChild; 6197 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0]; 6198 if (this.browser.webkit) { 6199 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length); 6200 } 6201 } 6202 exec = false; 6203 } else { 6204 el = this._getSelectedElement(); 6205 if (this._isElement(el, 'li') && this._isElement(el.parentNode, tag) || (this.browser.ie && this._isElement(this._getRange().parentElement, 'li')) || (this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { //we are in a list.. 6206 if (this.browser.ie) { 6207 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { 6208 el = el.getElementsByTagName('li')[0]; 6209 } 6210 str = ''; 6211 var lis2 = el.parentNode.getElementsByTagName('li'); 6212 for (var j = 0; j < lis2.length; j++) { 6213 str += lis2[j].innerHTML + '<br>'; 6214 } 6215 var newEl = this._getDoc().createElement('span'); 6216 newEl.innerHTML = str; 6217 el.parentNode.parentNode.replaceChild(newEl, el.parentNode); 6218 } else { 6219 this.nodeChange(); 6220 this._getDoc().execCommand(action, '', el.parentNode); 6221 this.nodeChange(); 6222 } 6223 exec = false; 6224 } 6225 if (this.browser.opera) { 6226 var self = this; 6227 window.setTimeout(function() { 6228 var liso = self._getDoc().getElementsByTagName('li'); 6229 for (var i = 0; i < liso.length; i++) { 6230 if (liso[i].innerHTML.toLowerCase() == '<br>') { 6231 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode); 6232 } 6233 } 6234 },30); 6235 } 6236 if (this.browser.ie && exec) { 6237 var html = ''; 6238 if (this._getRange().html) { 6239 html = '<li>' + this._getRange().html+ '</li>'; 6240 } else { 6241 var t = this._getRange().text.split('\n'); 6242 if (t.length > 1) { 6243 html = ''; 6244 for (var ie = 0; ie < t.length; ie++) { 6245 html += '<li>' + t[ie] + '</li>'; 6246 } 6247 } else { 6248 var txt = this._getRange().text; 6249 if (txt === '') { 6250 html = '<li id="new_list_item">' + txt + '</li>'; 6251 } else { 6252 html = '<li>' + txt + '</li>'; 6253 } 6254 } 6255 } 6256 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>'); 6257 var new_item = this._getDoc().getElementById('new_list_item'); 6258 if (new_item) { 6259 var range = this._getDoc().body.createTextRange(); 6260 range.moveToElementText(new_item); 6261 range.collapse(false); 6262 range.select(); 6263 new_item.id = ''; 6264 } 6265 exec = false; 6266 } 6267 } 6268 return exec; 6269 }, 6270 /** 6271 * @method cmd_insertorderedlist 6272 * @param value Value passed from the execCommand method 6273 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used. 6274 */ 6275 cmd_insertorderedlist: function(value) { 6276 return [this.cmd_list('ol')]; 6277 }, 6278 /** 6279 * @method cmd_insertunorderedlist 6280 * @param value Value passed from the execCommand method 6281 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used. 6282 */ 6283 cmd_insertunorderedlist: function(value) { 6284 return [this.cmd_list('ul')]; 6285 }, 6286 /** 6287 * @method cmd_fontname 6288 * @param value Value passed from the execCommand method 6289 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used. 6290 */ 6291 cmd_fontname: function(value) { 6292 var exec = true, 6293 selEl = this._getSelectedElement(); 6294 6295 this.currentFont = value; 6296 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) { 6297 YAHOO.util.Dom.setStyle(selEl, 'font-family', value); 6298 exec = false; 6299 } else if (this.get('insert') && !this._hasSelection()) { 6300 var el = this._createInsertElement({ fontFamily: value }); 6301 exec = false; 6302 } 6303 return [exec]; 6304 }, 6305 /** 6306 * @method cmd_fontsize 6307 * @param value Value passed from the execCommand method 6308 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used. 6309 */ 6310 cmd_fontsize: function(value) { 6311 var el = null, go = true; 6312 el = this._getSelectedElement(); 6313 if (this.browser.webkit) { 6314 if (this.currentElement[0]) { 6315 if (el == this.currentElement[0]) { 6316 go = false; 6317 YAHOO.util.Dom.setStyle(el, 'fontSize', value); 6318 this._selectNode(el); 6319 this.currentElement[0] = el; 6320 } 6321 } 6322 } 6323 if (go) { 6324 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) { 6325 el = this._getSelectedElement(); 6326 YAHOO.util.Dom.setStyle(el, 'fontSize', value); 6327 if (this.get('insert') && this.browser.ie) { 6328 var r = this._getRange(); 6329 r.collapse(false); 6330 r.select(); 6331 } else { 6332 this._selectNode(el); 6333 } 6334 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) { 6335 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value); 6336 } else { 6337 if (this.get('insert') && !this._hasSelection()) { 6338 el = this._createInsertElement({ fontSize: value }); 6339 this.currentElement[0] = el; 6340 this._selectNode(this.currentElement[0]); 6341 } else { 6342 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor }); 6343 this._selectNode(this.currentElement[0]); 6344 } 6345 } 6346 } 6347 return [false]; 6348 }, 6349 /* }}} */ 6350 /** 6351 * @private 6352 * @method _swapEl 6353 * @param {HTMLElement} el The element to swap with 6354 * @param {String} tagName The tagname of the element that you wish to create 6355 * @param {Function} callback (optional) A function to run on the element after it is created, but before it is replaced. An element reference is passed to this function. 6356 * @description This function will create a new element in the DOM and populate it with the contents of another element. Then it will assume it's place. 6357 */ 6358 _swapEl: function(el, tagName, callback) { 6359 var _el = this._getDoc().createElement(tagName); 6360 if (el) { 6361 _el.innerHTML = el.innerHTML; 6362 } 6363 if (typeof callback == 'function') { 6364 callback.call(this, _el); 6365 } 6366 if (el) { 6367 el.parentNode.replaceChild(_el, el); 6368 } 6369 return _el; 6370 }, 6371 /** 6372 * @private 6373 * @method _createInsertElement 6374 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing. 6375 * @param {Object} css (optional) Object literal containing styles to apply to the new element. 6376 * @return {HTMLElement} 6377 */ 6378 _createInsertElement: function(css) { 6379 this._createCurrentElement('span', css); 6380 var el = this.currentElement[0]; 6381 if (this.browser.webkit) { 6382 //Little Safari Hackery here.. 6383 el.innerHTML = '<span class="yui-non"> </span>'; 6384 el = el.firstChild; 6385 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length); 6386 } else if (this.browser.ie || this.browser.opera) { 6387 el.innerHTML = ' '; 6388 } 6389 this.focus(); 6390 this._selectNode(el, true); 6391 return el; 6392 }, 6393 /** 6394 * @private 6395 * @method _createCurrentElement 6396 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create 6397 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element. 6398 * @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. 6399 * It will then search the document for an element with the font-family set to <strong>yui-tmp</strong> and replace that with another span that has other information in it, then assign the new span to the 6400 * <code>this.currentElement</code> array, so we now have element references to the elements that were just modified. At this point we can use standard DOM manipulation to change them as we see fit. 6401 */ 6402 _createCurrentElement: function(tagName, tagStyle) { 6403 tagName = ((tagName) ? tagName : 'a'); 6404 var tar = null, 6405 el = [], 6406 _doc = this._getDoc(); 6407 6408 if (this.currentFont) { 6409 if (!tagStyle) { 6410 tagStyle = {}; 6411 } 6412 tagStyle.fontFamily = this.currentFont; 6413 this.currentFont = null; 6414 } 6415 this.currentElement = []; 6416 6417 var _elCreate = function(tagName, tagStyle) { 6418 var el = null; 6419 tagName = ((tagName) ? tagName : 'span'); 6420 tagName = tagName.toLowerCase(); 6421 switch (tagName) { 6422 case 'h1': 6423 case 'h2': 6424 case 'h3': 6425 case 'h4': 6426 case 'h5': 6427 case 'h6': 6428 el = _doc.createElement(tagName); 6429 break; 6430 default: 6431 el = _doc.createElement(tagName); 6432 if (tagName === 'span') { 6433 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName); 6434 YAHOO.util.Dom.addClass(el, 'yui-tag'); 6435 el.setAttribute('tag', tagName); 6436 } 6437 6438 for (var k in tagStyle) { 6439 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) { 6440 el.style[k] = tagStyle[k]; 6441 } 6442 } 6443 break; 6444 } 6445 return el; 6446 }; 6447 6448 if (!this._hasSelection()) { 6449 if (this._getDoc().queryCommandEnabled('insertimage')) { 6450 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img'); 6451 var imgs = this._getDoc().getElementsByTagName('img'); 6452 for (var j = 0; j < imgs.length; j++) { 6453 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') { 6454 el = _elCreate(tagName, tagStyle); 6455 imgs[j].parentNode.replaceChild(el, imgs[j]); 6456 this.currentElement[this.currentElement.length] = el; 6457 } 6458 } 6459 } else { 6460 if (this.currentEvent) { 6461 tar = YAHOO.util.Event.getTarget(this.currentEvent); 6462 } else { 6463 //For Safari.. 6464 tar = this._getDoc().body; 6465 } 6466 } 6467 if (tar) { 6468 /* 6469 * @knownissue Safari Cursor Position 6470 * @browser Safari 2.x 6471 * @description The issue here is that we have no way of knowing where the cursor position is 6472 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6473 */ 6474 el = _elCreate(tagName, tagStyle); 6475 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) { 6476 if (this._isElement(tar, 'html')) { 6477 tar = this._getDoc().body; 6478 } 6479 tar.appendChild(el); 6480 } else if (tar.nextSibling) { 6481 tar.parentNode.insertBefore(el, tar.nextSibling); 6482 } else { 6483 tar.parentNode.appendChild(el); 6484 } 6485 //this.currentElement = el; 6486 this.currentElement[this.currentElement.length] = el; 6487 this.currentEvent = null; 6488 if (this.browser.webkit) { 6489 //Force Safari to focus the new element 6490 this._getSelection().setBaseAndExtent(el, 0, el, 0); 6491 if (this.browser.webkit3) { 6492 this._getSelection().collapseToStart(); 6493 } else { 6494 this._getSelection().collapse(true); 6495 } 6496 } 6497 } 6498 } else { 6499 //Force CSS Styling for this action... 6500 this._setEditorStyle(true); 6501 this._getDoc().execCommand('fontname', false, 'yui-tmp'); 6502 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u']; 6503 6504 if (!this._isElement(this._getSelectedElement(), 'body')) { 6505 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName); 6506 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName); 6507 } 6508 for (var _els = 0; _els < __els.length; _els++) { 6509 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]); 6510 for (var e = 0; e < _tmp1.length; e++) { 6511 _tmp[_tmp.length] = _tmp1[e]; 6512 } 6513 } 6514 6515 6516 for (var i = 0; i < _tmp.length; i++) { 6517 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) { 6518 if (tagName !== 'span') { 6519 el = _elCreate(tagName, tagStyle); 6520 } else { 6521 el = _elCreate(_tmp[i].tagName, tagStyle); 6522 } 6523 el.innerHTML = _tmp[i].innerHTML; 6524 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) { 6525 var fc = _tmp[i].getElementsByTagName('li')[0]; 6526 _tmp[i].style.fontFamily = 'inherit'; 6527 fc.style.fontFamily = 'inherit'; 6528 el.innerHTML = fc.innerHTML; 6529 fc.innerHTML = ''; 6530 fc.appendChild(el); 6531 this.currentElement[this.currentElement.length] = el; 6532 } else if (this._isElement(_tmp[i], 'li')) { 6533 _tmp[i].innerHTML = ''; 6534 _tmp[i].appendChild(el); 6535 _tmp[i].style.fontFamily = 'inherit'; 6536 this.currentElement[this.currentElement.length] = el; 6537 } else { 6538 if (_tmp[i].parentNode) { 6539 _tmp[i].parentNode.replaceChild(el, _tmp[i]); 6540 this.currentElement[this.currentElement.length] = el; 6541 this.currentEvent = null; 6542 if (this.browser.webkit) { 6543 //Force Safari to focus the new element 6544 this._getSelection().setBaseAndExtent(el, 0, el, 0); 6545 if (this.browser.webkit3) { 6546 this._getSelection().collapseToStart(); 6547 } else { 6548 this._getSelection().collapse(true); 6549 } 6550 } 6551 if (this.browser.ie && tagStyle && tagStyle.fontSize) { 6552 this._getSelection().empty(); 6553 } 6554 if (this.browser.gecko) { 6555 this._getSelection().collapseToStart(); 6556 } 6557 } 6558 } 6559 } 6560 } 6561 var len = this.currentElement.length; 6562 for (var o = 0; o < len; o++) { 6563 if ((o + 1) != len) { //Skip the last one in the list 6564 if (this.currentElement[o] && this.currentElement[o].nextSibling) { 6565 if (this._isElement(this.currentElement[o], 'br')) { 6566 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling; 6567 } 6568 } 6569 } 6570 } 6571 } 6572 }, 6573 /** 6574 * @method saveHTML 6575 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea. 6576 * @return String 6577 */ 6578 saveHTML: function() { 6579 var html = this.cleanHTML(); 6580 if (this._textarea) { 6581 this.get('element').value = html; 6582 } else { 6583 this.get('element').innerHTML = html; 6584 } 6585 if (this.get('saveEl') !== this.get('element')) { 6586 var out = this.get('saveEl'); 6587 if (Lang.isString(out)) { 6588 out = Dom.get(out); 6589 } 6590 if (out) { 6591 if (out.tagName.toLowerCase() === 'textarea') { 6592 out.value = html; 6593 } else { 6594 out.innerHTML = html; 6595 } 6596 } 6597 } 6598 return html; 6599 }, 6600 /** 6601 * @method setEditorHTML 6602 * @param {String} incomingHTML The html content to load into the editor 6603 * @description Loads HTML into the editors body 6604 */ 6605 setEditorHTML: function(incomingHTML) { 6606 var html = this._cleanIncomingHTML(incomingHTML); 6607 html = html.replace(/RIGHT_BRACKET/gi, '{'); 6608 html = html.replace(/LEFT_BRACKET/gi, '}'); 6609 this._getDoc().body.innerHTML = html; 6610 this.nodeChange(); 6611 }, 6612 /** 6613 * @method getEditorHTML 6614 * @description Gets the unprocessed/unfiltered HTML from the editor 6615 */ 6616 getEditorHTML: function() { 6617 try { 6618 var b = this._getDoc().body; 6619 if (b === null) { 6620 return null; 6621 } 6622 return this._getDoc().body.innerHTML; 6623 } catch (e) { 6624 return ''; 6625 } 6626 }, 6627 /** 6628 * @method show 6629 * @description This method needs to be called if the Editor was hidden (like in a TabView or Panel). It is used to reset the editor after being in a container that was set to display none. 6630 */ 6631 show: function() { 6632 if (this.browser.gecko) { 6633 this._setDesignMode('on'); 6634 this.focus(); 6635 } 6636 if (this.browser.webkit) { 6637 var self = this; 6638 window.setTimeout(function() { 6639 self._setInitialContent.call(self); 6640 }, 10); 6641 } 6642 //Adding this will close all other Editor window's when showing this one. 6643 if (this.currentWindow) { 6644 this.closeWindow(); 6645 } 6646 //Put the iframe back in place 6647 this.get('iframe').setStyle('position', 'static'); 6648 this.get('iframe').setStyle('left', ''); 6649 }, 6650 /** 6651 * @method hide 6652 * @description This method needs to be called if the Editor is to be hidden (like in a TabView or Panel). It should be called to clear timeouts and close open editor windows. 6653 */ 6654 hide: function() { 6655 //Adding this will close all other Editor window's. 6656 if (this.currentWindow) { 6657 this.closeWindow(); 6658 } 6659 if (this._fixNodesTimer) { 6660 clearTimeout(this._fixNodesTimer); 6661 this._fixNodesTimer = null; 6662 } 6663 if (this._nodeChangeTimer) { 6664 clearTimeout(this._nodeChangeTimer); 6665 this._nodeChangeTimer = null; 6666 } 6667 this._lastNodeChange = 0; 6668 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements. 6669 this.get('iframe').setStyle('position', 'absolute'); 6670 this.get('iframe').setStyle('left', '-9999px'); 6671 }, 6672 /** 6673 * @method _cleanIncomingHTML 6674 * @param {String} html The unfiltered HTML 6675 * @description Process the HTML with a few regexes to clean it up and stabilize the input 6676 * @return {String} The filtered HTML 6677 */ 6678 _cleanIncomingHTML: function(html) { 6679 html = html.replace(/{/gi, 'RIGHT_BRACKET'); 6680 html = html.replace(/}/gi, 'LEFT_BRACKET'); 6681 6682 html = html.replace(/<strong([^>]*)>/gi, '<b$1>'); 6683 html = html.replace(/<\/strong>/gi, '</b>'); 6684 6685 //replace embed before em check 6686 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>'); 6687 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>'); 6688 6689 html = html.replace(/<em([^>]*)>/gi, '<i$1>'); 6690 html = html.replace(/<\/em>/gi, '</i>'); 6691 html = html.replace(/_moz_dirty=""/gi, ''); 6692 6693 //Put embed tags back in.. 6694 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>'); 6695 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>'); 6696 if (this.get('plainText')) { 6697 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>'); 6698 html = html.replace(/ /gi, ' '); //Replace all double spaces 6699 html = html.replace(/\t/gi, ' '); //Replace all tabs 6700 } 6701 //Removing Script Tags from the Editor 6702 html = html.replace(/<script([^>]*)>/gi, '<bad>'); 6703 html = html.replace(/<\/script([^>]*)>/gi, '</bad>'); 6704 html = html.replace(/<script([^>]*)>/gi, '<bad>'); 6705 html = html.replace(/<\/script([^>]*)>/gi, '</bad>'); 6706 //Replace the line feeds 6707 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>'); 6708 6709 //Remove Bad HTML elements (used to be script nodes) 6710 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), ''); 6711 //Replace the lines feeds 6712 html = html.replace(/<YUI_LF>/g, '\n'); 6713 return html; 6714 }, 6715 /** 6716 * @method cleanHTML 6717 * @param {String} html The unfiltered HTML 6718 * @description Process the HTML with a few regexes to clean it up and stabilize the output 6719 * @return {String} The filtered HTML 6720 */ 6721 cleanHTML: function(html) { 6722 //Start Filtering Output 6723 //Begin RegExs.. 6724 if (!html) { 6725 html = this.getEditorHTML(); 6726 } 6727 var markup = this.get('markup'); 6728 //Make some backups... 6729 html = this.pre_filter_linebreaks(html, markup); 6730 6731 //Filter MS Word 6732 html = this.filter_msword(html); 6733 6734 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>'); 6735 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>'); 6736 6737 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>'); 6738 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>'); 6739 6740 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>'); 6741 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>'); 6742 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>'); 6743 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>'); 6744 6745 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>'); 6746 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>'); 6747 6748 //Convert b and i tags to strong and em tags 6749 if ((markup == 'semantic') || (markup == 'xhtml')) { 6750 //html = html.replace(/<i(\s+[^>]*)?>/gi, "<em$1>"); 6751 html = html.replace(/<i([^>]*)>/gi, "<em$1>"); 6752 html = html.replace(/<\/i>/gi, '</em>'); 6753 //html = html.replace(/<b(\s+[^>]*)?>/gi, "<strong$1>"); 6754 html = html.replace(/<b([^>]*)>/gi, "<strong$1>"); 6755 html = html.replace(/<\/b>/gi, '</strong>'); 6756 } 6757 6758 html = html.replace(/_moz_dirty=""/gi, ''); 6759 6760 //normalize strikethrough 6761 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"'); 6762 html = html.replace(/\/strike>/gi, '/span>'); 6763 6764 6765 //Case Changing 6766 if (this.browser.ie) { 6767 html = html.replace(/text-decoration/gi, 'text-decoration'); 6768 html = html.replace(/font-weight/gi, 'font-weight'); 6769 html = html.replace(/_width="([^>]*)"/gi, ''); 6770 html = html.replace(/_height="([^>]*)"/gi, ''); 6771 //Cleanup Image URL's 6772 var url = this._baseHREF.replace(/\//gi, '\\/'), 6773 re = new RegExp('src="' + url, 'gi'); 6774 html = html.replace(re, 'src="'); 6775 } 6776 html = html.replace(/<font/gi, '<font'); 6777 html = html.replace(/<\/font>/gi, '</font>'); 6778 html = html.replace(/<span/gi, '<span'); 6779 html = html.replace(/<\/span>/gi, '</span>'); 6780 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) { 6781 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>'); 6782 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"'); 6783 if (this.browser.webkit) { 6784 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>'); 6785 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>'); 6786 } 6787 html = html.replace(/\/u>/gi, '/span>'); 6788 if (markup == 'css') { 6789 html = html.replace(/<em([^>]*)>/gi, '<i$1>'); 6790 html = html.replace(/<\/em>/gi, '</i>'); 6791 html = html.replace(/<strong([^>]*)>/gi, '<b$1>'); 6792 html = html.replace(/<\/strong>/gi, '</b>'); 6793 html = html.replace(/<b/gi, '<span style="font-weight: bold;"'); 6794 html = html.replace(/\/b>/gi, '/span>'); 6795 html = html.replace(/<i/gi, '<span style="font-style: italic;"'); 6796 html = html.replace(/\/i>/gi, '/span>'); 6797 } 6798 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single 6799 } else { 6800 html = html.replace(/<u/gi, '<u'); 6801 html = html.replace(/\/u>/gi, '/u>'); 6802 } 6803 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>'); 6804 html = html.replace(/\/ol>/gi, '/ol>'); 6805 html = html.replace(/<li/gi, '<li'); 6806 html = html.replace(/\/li>/gi, '/li>'); 6807 html = this.filter_safari(html); 6808 6809 html = this.filter_internals(html); 6810 6811 html = this.filter_all_rgb(html); 6812 6813 //Replace our backups with the real thing 6814 html = this.post_filter_linebreaks(html, markup); 6815 6816 if (markup == 'xhtml') { 6817 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />'); 6818 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />'); 6819 } else { 6820 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>'); 6821 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>'); 6822 } 6823 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>'); 6824 html = html.replace(/<\/YUI_UL>/g, '<\/ul>'); 6825 6826 html = this.filter_invalid_lists(html); 6827 6828 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>'); 6829 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>'); 6830 6831 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>'); 6832 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>'); 6833 6834 //This should fix &'s in URL's 6835 html = html.replace(/ & /gi, ' YUI_AMP '); 6836 html = html.replace(/ &/gi, ' YUI_AMP_F '); 6837 html = html.replace(/& /gi, ' YUI_AMP_R '); 6838 html = html.replace(/&/gi, '&'); 6839 html = html.replace(/ YUI_AMP /gi, ' & '); 6840 html = html.replace(/ YUI_AMP_F /gi, ' &'); 6841 html = html.replace(/ YUI_AMP_R /gi, '& '); 6842 6843 //Trim the output, removing whitespace from the beginning and end 6844 html = YAHOO.lang.trim(html); 6845 6846 if (this.get('removeLineBreaks')) { 6847 html = html.replace(/\n/g, '').replace(/\r/g, ''); 6848 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single 6849 } 6850 6851 for (var v in this.invalidHTML) { 6852 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) { 6853 if (Lang.isObject(v) && v.keepContents) { 6854 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1'); 6855 } else { 6856 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), ''); 6857 } 6858 } 6859 } 6860 6861 /* LATER -- Add DOM manipulation 6862 console.log(html); 6863 var frag = document.createDocumentFragment(); 6864 frag.innerHTML = html; 6865 6866 var ps = frag.getElementsByTagName('p'), 6867 len = ps.length; 6868 for (var i = 0; i < len; i++) { 6869 var ps2 = ps[i].getElementsByTagName('p'); 6870 if (ps2.length) { 6871 6872 } 6873 6874 } 6875 html = frag.innerHTML; 6876 console.log(html); 6877 */ 6878 6879 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html }); 6880 6881 return html; 6882 }, 6883 /** 6884 * @method filter_msword 6885 * @param String html The HTML string to filter 6886 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config 6887 */ 6888 filter_msword: function(html) { 6889 if (!this.get('filterWord')) { 6890 return html; 6891 } 6892 //Remove the ms o: tags 6893 html = html.replace(/<o:p>\s*<\/o:p>/g, ''); 6894 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' '); 6895 6896 //Remove the ms w: tags 6897 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, ''); 6898 6899 //Remove mso-? styles. 6900 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, ''); 6901 6902 //Remove more bogus MS styles. 6903 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, ''); 6904 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\""); 6905 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, ''); 6906 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\""); 6907 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\""); 6908 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ); 6909 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, ''); 6910 html = html.replace( /\s*tab-stops:[^"]*/gi, ''); 6911 6912 //Remove XML declarations 6913 html = html.replace(/<\\?\?xml[^>]*>/gi, ''); 6914 6915 //Remove lang 6916 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3"); 6917 6918 //Remove language tags 6919 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3"); 6920 6921 //Remove onmouseover and onmouseout events (from MS Word comments effect) 6922 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3"); 6923 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3"); 6924 6925 return html; 6926 }, 6927 /** 6928 * @method filter_invalid_lists 6929 * @param String html The HTML string to filter 6930 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li> 6931 */ 6932 filter_invalid_lists: function(html) { 6933 html = html.replace(/<\/li>\n/gi, '</li>'); 6934 6935 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>'); 6936 html = html.replace(/<\/ol>/gi, '</ol></li>'); 6937 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>"); 6938 6939 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>'); 6940 html = html.replace(/<\/ul>/gi, '</ul></li>'); 6941 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>"); 6942 6943 html = html.replace(/<\/li>/gi, "</li>"); 6944 html = html.replace(/<\/ol>/gi, "</ol>"); 6945 html = html.replace(/<ol>/gi, "<ol>"); 6946 html = html.replace(/<ul>/gi, "<ul>"); 6947 return html; 6948 }, 6949 /** 6950 * @method filter_safari 6951 * @param String html The HTML string to filter 6952 * @description Filters strings specific to Safari 6953 * @return String 6954 */ 6955 filter_safari: function(html) { 6956 if (this.browser.webkit) { 6957 //<span class="Apple-tab-span" style="white-space:pre"> </span> 6958 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' '); 6959 html = html.replace(/Apple-style-span/gi, ''); 6960 html = html.replace(/style="line-height: normal;"/gi, ''); 6961 html = html.replace(/yui-wk-div/gi, ''); 6962 html = html.replace(/yui-wk-p/gi, ''); 6963 6964 6965 //Remove bogus LI's 6966 html = html.replace(/<li><\/li>/gi, ''); 6967 html = html.replace(/<li> <\/li>/gi, ''); 6968 html = html.replace(/<li> <\/li>/gi, ''); 6969 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break 6970 if (this.get('ptags')) { 6971 html = html.replace(/<div([^>]*)>/g, '<p$1>'); 6972 html = html.replace(/<\/div>/gi, '</p>'); 6973 } else { 6974 //html = html.replace(/<div>/gi, '<br>'); 6975 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>'); 6976 html = html.replace(/<\/div>/gi, ''); 6977 } 6978 } 6979 return html; 6980 }, 6981 /** 6982 * @method filter_internals 6983 * @param String html The HTML string to filter 6984 * @description Filters internal RTE strings and bogus attrs we don't want 6985 * @return String 6986 */ 6987 filter_internals: function(html) { 6988 html = html.replace(/\r/g, ''); 6989 //Fix stuff we don't want 6990 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, ''); 6991 //Fix last BR in LI 6992 html = html.replace(/<YUI_BR><\/li>/gi, '</li>'); 6993 6994 html = html.replace(/yui-tag-span/gi, ''); 6995 html = html.replace(/yui-tag/gi, ''); 6996 html = html.replace(/yui-non/gi, ''); 6997 html = html.replace(/yui-img/gi, ''); 6998 html = html.replace(/ tag="span"/gi, ''); 6999 html = html.replace(/ class=""/gi, ''); 7000 html = html.replace(/ style=""/gi, ''); 7001 html = html.replace(/ class=" "/gi, ''); 7002 html = html.replace(/ class=" "/gi, ''); 7003 html = html.replace(/ target=""/gi, ''); 7004 html = html.replace(/ title=""/gi, ''); 7005 7006 if (this.browser.ie) { 7007 html = html.replace(/ class= /gi, ''); 7008 html = html.replace(/ class= >/gi, ''); 7009 } 7010 7011 return html; 7012 }, 7013 /** 7014 * @method filter_all_rgb 7015 * @param String str The HTML string to filter 7016 * @description Converts all RGB color strings found in passed string to a hex color, example: style="color: rgb(0, 255, 0)" converts to style="color: #00ff00" 7017 * @return String 7018 */ 7019 filter_all_rgb: function(str) { 7020 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi"); 7021 var arr = str.match(exp); 7022 if (Lang.isArray(arr)) { 7023 for (var i = 0; i < arr.length; i++) { 7024 var color = this.filter_rgb(arr[i]); 7025 str = str.replace(arr[i].toString(), color); 7026 } 7027 } 7028 7029 return str; 7030 }, 7031 /** 7032 * @method filter_rgb 7033 * @param String css The CSS string containing rgb(#,#,#); 7034 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00 7035 * @return String 7036 */ 7037 filter_rgb: function(css) { 7038 if (css.toLowerCase().indexOf('rgb') != -1) { 7039 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi"); 7040 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(','); 7041 7042 if (rgb.length == 5) { 7043 var r = parseInt(rgb[1], 10).toString(16); 7044 var g = parseInt(rgb[2], 10).toString(16); 7045 var b = parseInt(rgb[3], 10).toString(16); 7046 7047 r = r.length == 1 ? '0' + r : r; 7048 g = g.length == 1 ? '0' + g : g; 7049 b = b.length == 1 ? '0' + b : b; 7050 7051 css = "#" + r + g + b; 7052 } 7053 } 7054 return css; 7055 }, 7056 /** 7057 * @method pre_filter_linebreaks 7058 * @param String html The HTML to filter 7059 * @param String markup The markup type to filter to 7060 * @description HTML Pre Filter 7061 * @return String 7062 */ 7063 pre_filter_linebreaks: function(html, markup) { 7064 if (this.browser.webkit) { 7065 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>'); 7066 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>'); 7067 } 7068 html = html.replace(/<br>/gi, '<YUI_BR>'); 7069 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>'); 7070 html = html.replace(/<br\/>/gi, '<YUI_BR>'); 7071 html = html.replace(/<br \/>/gi, '<YUI_BR>'); 7072 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>'); 7073 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>'); 7074 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>'); 7075 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>'); 7076 //Fix last BR 7077 html = html.replace(/<YUI_BR>$/, ''); 7078 //Fix last BR in P 7079 html = html.replace(/<YUI_BR><\/p>/g, '</p>'); 7080 if (this.browser.ie) { 7081 html = html.replace(/ /g, '\t'); 7082 } 7083 return html; 7084 }, 7085 /** 7086 * @method post_filter_linebreaks 7087 * @param String html The HTML to filter 7088 * @param String markup The markup type to filter to 7089 * @description HTML Pre Filter 7090 * @return String 7091 */ 7092 post_filter_linebreaks: function(html, markup) { 7093 if (markup == 'xhtml') { 7094 html = html.replace(/<YUI_BR>/g, '<br />'); 7095 } else { 7096 html = html.replace(/<YUI_BR>/g, '<br>'); 7097 } 7098 return html; 7099 }, 7100 /** 7101 * @method clearEditorDoc 7102 * @description Clear the doc of the Editor 7103 */ 7104 clearEditorDoc: function() { 7105 this._getDoc().body.innerHTML = ' '; 7106 }, 7107 /** 7108 * @method openWindow 7109 * @description Override Method for Advanced Editor 7110 */ 7111 openWindow: function(win) { 7112 }, 7113 /** 7114 * @method moveWindow 7115 * @description Override Method for Advanced Editor 7116 */ 7117 moveWindow: function() { 7118 }, 7119 /** 7120 * @private 7121 * @method _closeWindow 7122 * @description Override Method for Advanced Editor 7123 */ 7124 _closeWindow: function() { 7125 }, 7126 /** 7127 * @method closeWindow 7128 * @description Override Method for Advanced Editor 7129 */ 7130 closeWindow: function() { 7131 //this.unsubscribeAll('afterExecCommand'); 7132 this.toolbar.resetAllButtons(); 7133 this.focus(); 7134 }, 7135 /** 7136 * @method destroy 7137 * @description Destroys the editor, all of it's elements and objects. 7138 * @return {Boolean} 7139 */ 7140 destroy: function() { 7141 if (this._nodeChangeDelayTimer) { 7142 clearTimeout(this._nodeChangeDelayTimer); 7143 } 7144 this.hide(); 7145 7146 if (this.resize) { 7147 this.resize.destroy(); 7148 } 7149 if (this.dd) { 7150 this.dd.unreg(); 7151 } 7152 if (this.get('panel')) { 7153 this.get('panel').destroy(); 7154 } 7155 this.saveHTML(); 7156 this.toolbar.destroy(); 7157 this.setStyle('visibility', 'visible'); 7158 this.setStyle('position', 'static'); 7159 this.setStyle('top', ''); 7160 this.setStyle('left', ''); 7161 var textArea = this.get('element'); 7162 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element')); 7163 this.get('element_cont').get('element').innerHTML = ''; 7164 this.set('handleSubmit', false); //Remove the submit handler 7165 return true; 7166 }, 7167 /** 7168 * @method toString 7169 * @description Returns a string representing the editor. 7170 * @return {String} 7171 */ 7172 toString: function() { 7173 var str = 'SimpleEditor'; 7174 if (this.get && this.get('element_cont')) { 7175 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : '')); 7176 } 7177 return str; 7178 } 7179 }); 7180 7181 /** 7182 * @event toolbarLoaded 7183 * @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. 7184 * @type YAHOO.util.CustomEvent 7185 */ 7186 /** 7187 * @event cleanHTML 7188 * @description Event is fired after the cleanHTML method is called. 7189 * @type YAHOO.util.CustomEvent 7190 */ 7191 /** 7192 * @event afterRender 7193 * @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. 7194 * @type YAHOO.util.CustomEvent 7195 */ 7196 /** 7197 * @event editorContentLoaded 7198 * @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. 7199 * @type YAHOO.util.CustomEvent 7200 */ 7201 /** 7202 * @event beforeNodeChange 7203 * @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. 7204 * @type YAHOO.util.CustomEvent 7205 */ 7206 /** 7207 * @event afterNodeChange 7208 * @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. 7209 * @type YAHOO.util.CustomEvent 7210 */ 7211 /** 7212 * @event beforeExecCommand 7213 * @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. 7214 * @type YAHOO.util.CustomEvent 7215 */ 7216 /** 7217 * @event afterExecCommand 7218 * @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. 7219 * @type YAHOO.util.CustomEvent 7220 */ 7221 /** 7222 * @event editorMouseUp 7223 * @param {Event} ev The DOM Event that occured 7224 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7225 * @type YAHOO.util.CustomEvent 7226 */ 7227 /** 7228 * @event editorMouseDown 7229 * @param {Event} ev The DOM Event that occured 7230 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7231 * @type YAHOO.util.CustomEvent 7232 */ 7233 /** 7234 * @event editorDoubleClick 7235 * @param {Event} ev The DOM Event that occured 7236 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7237 * @type YAHOO.util.CustomEvent 7238 */ 7239 /** 7240 * @event editorClick 7241 * @param {Event} ev The DOM Event that occured 7242 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7243 * @type YAHOO.util.CustomEvent 7244 */ 7245 /** 7246 * @event editorKeyUp 7247 * @param {Event} ev The DOM Event that occured 7248 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7249 * @type YAHOO.util.CustomEvent 7250 */ 7251 /** 7252 * @event editorKeyPress 7253 * @param {Event} ev The DOM Event that occured 7254 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7255 * @type YAHOO.util.CustomEvent 7256 */ 7257 /** 7258 * @event editorKeyDown 7259 * @param {Event} ev The DOM Event that occured 7260 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7261 * @type YAHOO.util.CustomEvent 7262 */ 7263 /** 7264 * @event beforeEditorMouseUp 7265 * @param {Event} ev The DOM Event that occured 7266 * @description Fires before editor event, returning false will stop the internal processing. 7267 * @type YAHOO.util.CustomEvent 7268 */ 7269 /** 7270 * @event beforeEditorMouseDown 7271 * @param {Event} ev The DOM Event that occured 7272 * @description Fires before editor event, returning false will stop the internal processing. 7273 * @type YAHOO.util.CustomEvent 7274 */ 7275 /** 7276 * @event beforeEditorDoubleClick 7277 * @param {Event} ev The DOM Event that occured 7278 * @description Fires before editor event, returning false will stop the internal processing. 7279 * @type YAHOO.util.CustomEvent 7280 */ 7281 /** 7282 * @event beforeEditorClick 7283 * @param {Event} ev The DOM Event that occured 7284 * @description Fires before editor event, returning false will stop the internal processing. 7285 * @type YAHOO.util.CustomEvent 7286 */ 7287 /** 7288 * @event beforeEditorKeyUp 7289 * @param {Event} ev The DOM Event that occured 7290 * @description Fires before editor event, returning false will stop the internal processing. 7291 * @type YAHOO.util.CustomEvent 7292 */ 7293 /** 7294 * @event beforeEditorKeyPress 7295 * @param {Event} ev The DOM Event that occured 7296 * @description Fires before editor event, returning false will stop the internal processing. 7297 * @type YAHOO.util.CustomEvent 7298 */ 7299 /** 7300 * @event beforeEditorKeyDown 7301 * @param {Event} ev The DOM Event that occured 7302 * @description Fires before editor event, returning false will stop the internal processing. 7303 * @type YAHOO.util.CustomEvent 7304 */ 7305 7306 /** 7307 * @event editorWindowFocus 7308 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event. 7309 * @type YAHOO.util.CustomEvent 7310 */ 7311 /** 7312 * @event editorWindowBlur 7313 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event. 7314 * @type YAHOO.util.CustomEvent 7315 */ 7316 7317 7318 /** 7319 * @description Singleton object used to track the open window objects and panels across the various open editors 7320 * @class EditorInfo 7321 * @static 7322 */ 7323 YAHOO.widget.EditorInfo = { 7324 /** 7325 * @private 7326 * @property _instances 7327 * @description A reference to all editors on the page. 7328 * @type Object 7329 */ 7330 _instances: {}, 7331 /** 7332 * @private 7333 * @property blankImage 7334 * @description A reference to the blankImage url 7335 * @type String 7336 */ 7337 blankImage: '', 7338 /** 7339 * @private 7340 * @property window 7341 * @description A reference to the currently open window object in any editor on the page. 7342 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> 7343 */ 7344 window: {}, 7345 /** 7346 * @private 7347 * @property panel 7348 * @description A reference to the currently open panel in any editor on the page. 7349 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a> 7350 */ 7351 panel: null, 7352 /** 7353 * @method getEditorById 7354 * @description Returns a reference to the Editor object associated with the given textarea 7355 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of 7356 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a> 7357 */ 7358 getEditorById: function(id) { 7359 if (!YAHOO.lang.isString(id)) { 7360 //Not a string, assume a node Reference 7361 id = id.id; 7362 } 7363 if (this._instances[id]) { 7364 return this._instances[id]; 7365 } 7366 return false; 7367 }, 7368 /** 7369 * @method saveAll 7370 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved. 7371 * @param {HTMLElement} form The form to check if this Editor instance belongs to 7372 */ 7373 saveAll: function(form) { 7374 var i, e, items = YAHOO.widget.EditorInfo._instances; 7375 if (form) { 7376 for (i in items) { 7377 if (Lang.hasOwnProperty(items, i)) { 7378 e = items[i]; 7379 if (e.get('element').form && (e.get('element').form == form)) { 7380 e.saveHTML(); 7381 } 7382 } 7383 } 7384 } else { 7385 for (i in items) { 7386 if (Lang.hasOwnProperty(items, i)) { 7387 items[i].saveHTML(); 7388 } 7389 } 7390 } 7391 }, 7392 /** 7393 * @method toString 7394 * @description Returns a string representing the EditorInfo. 7395 * @return {String} 7396 */ 7397 toString: function() { 7398 var len = 0; 7399 for (var i in this._instances) { 7400 if (Lang.hasOwnProperty(this._instances, i)) { 7401 len++; 7402 } 7403 } 7404 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')'; 7405 } 7406 }; 7407 7408 7409 7410 7411 })(); 7412 YAHOO.register("simpleeditor", YAHOO.widget.SimpleEditor, {version: "2.9.0", build: "2800"}); 7413 7414 }, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-skin-sam-simpleeditor", "yui2-event", "yui2-element"], "optional": ["yui2-containercore", "yui2-dragdrop", "yui2-skin-sam-button", "yui2-skin-sam-menu", "yui2-menu", "yui2-button", "yui2-animation"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |