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