[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('yui2-simpleeditor', function(Y) { Y.use('yui2-editor'); }, '3.3.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"]}); 2 YUI.add('yui2-editor', function(Y) { 3 var YAHOO = Y.YUI2; 4 /* 5 Copyright (c) 2011, Yahoo! Inc. All rights reserved. 6 Code licensed under the BSD License: 7 http://developer.yahoo.com/yui/license.html 8 version: 2.9.0 9 */ 10 (function() { 11 var Dom = YAHOO.util.Dom, 12 Event = YAHOO.util.Event, 13 Lang = YAHOO.lang; 14 /** 15 * @module editor 16 * @description <p>Creates a rich custom Toolbar Button. Primarily used with the Rich Text Editor's Toolbar</p> 17 * @class ToolbarButtonAdvanced 18 * @namespace YAHOO.widget 19 * @requires yahoo, dom, element, event, container_core, menu, button 20 * 21 * Provides a toolbar button based on the button and menu widgets. 22 * @constructor 23 * @class ToolbarButtonAdvanced 24 * @param {String/HTMLElement} el The element to turn into a button. 25 * @param {Object} attrs Object liternal containing configuration parameters. 26 */ 27 if (YAHOO.widget.Button) { 28 YAHOO.widget.ToolbarButtonAdvanced = YAHOO.widget.Button; 29 /** 30 * @property buttonType 31 * @private 32 * @description Tells if the Button is a Rich Button or a Simple Button 33 */ 34 YAHOO.widget.ToolbarButtonAdvanced.prototype.buttonType = 'rich'; 35 /** 36 * @method checkValue 37 * @param {String} value The value of the option that we want to mark as selected 38 * @description Select an option by value 39 */ 40 YAHOO.widget.ToolbarButtonAdvanced.prototype.checkValue = function(value) { 41 var _menuItems = this.getMenu().getItems(); 42 if (_menuItems.length === 0) { 43 this.getMenu()._onBeforeShow(); 44 _menuItems = this.getMenu().getItems(); 45 } 46 for (var i = 0; i < _menuItems.length; i++) { 47 _menuItems[i].cfg.setProperty('checked', false); 48 if (_menuItems[i].value == value) { 49 _menuItems[i].cfg.setProperty('checked', true); 50 } 51 } 52 }; 53 } else { 54 YAHOO.widget.ToolbarButtonAdvanced = function() {}; 55 } 56 57 58 /** 59 * @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> 60 * @class ToolbarButton 61 * @namespace YAHOO.widget 62 * @requires yahoo, dom, element, event 63 * @extends YAHOO.util.Element 64 * 65 * 66 * @constructor 67 * @param {String/HTMLElement} el The element to turn into a button. 68 * @param {Object} attrs Object liternal containing configuration parameters. 69 */ 70 71 YAHOO.widget.ToolbarButton = function(el, attrs) { 72 73 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) { 74 attrs = el; 75 } 76 var local_attrs = (attrs || {}); 77 78 var oConfig = { 79 element: null, 80 attributes: local_attrs 81 }; 82 83 if (!oConfig.attributes.type) { 84 oConfig.attributes.type = 'push'; 85 } 86 87 oConfig.element = document.createElement('span'); 88 oConfig.element.setAttribute('unselectable', 'on'); 89 oConfig.element.className = 'yui-button yui-' + oConfig.attributes.type + '-button'; 90 oConfig.element.innerHTML = '<span class="first-child"><a href="#">LABEL</a></span>'; 91 oConfig.element.firstChild.firstChild.tabIndex = '-1'; 92 oConfig.attributes.id = (oConfig.attributes.id || Dom.generateId()); 93 oConfig.element.id = oConfig.attributes.id; 94 95 YAHOO.widget.ToolbarButton.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 96 }; 97 98 YAHOO.extend(YAHOO.widget.ToolbarButton, YAHOO.util.Element, { 99 /** 100 * @property buttonType 101 * @private 102 * @description Tells if the Button is a Rich Button or a Simple Button 103 */ 104 buttonType: 'normal', 105 /** 106 * @method _handleMouseOver 107 * @private 108 * @description Adds classes to the button elements on mouseover (hover) 109 */ 110 _handleMouseOver: function() { 111 if (!this.get('disabled')) { 112 this.addClass('yui-button-hover'); 113 this.addClass('yui-' + this.get('type') + '-button-hover'); 114 } 115 }, 116 /** 117 * @method _handleMouseOut 118 * @private 119 * @description Removes classes from the button elements on mouseout (hover) 120 */ 121 _handleMouseOut: function() { 122 this.removeClass('yui-button-hover'); 123 this.removeClass('yui-' + this.get('type') + '-button-hover'); 124 }, 125 /** 126 * @method checkValue 127 * @param {String} value The value of the option that we want to mark as selected 128 * @description Select an option by value 129 */ 130 checkValue: function(value) { 131 if (this.get('type') == 'menu') { 132 var opts = this._button.options; 133 if (opts) { 134 for (var i = 0; i < opts.length; i++) { 135 if (opts[i].value == value) { 136 opts.selectedIndex = i; 137 } 138 } 139 } 140 } 141 }, 142 /** 143 * @method init 144 * @description The ToolbarButton class's initialization method 145 */ 146 init: function(p_oElement, p_oAttributes) { 147 YAHOO.widget.ToolbarButton.superclass.init.call(this, p_oElement, p_oAttributes); 148 149 this.on('mouseover', this._handleMouseOver, this, true); 150 this.on('mouseout', this._handleMouseOut, this, true); 151 this.on('click', function(ev) { 152 Event.stopEvent(ev); 153 return false; 154 }, this, true); 155 }, 156 /** 157 * @method initAttributes 158 * @description Initializes all of the configuration attributes used to create 159 * the toolbar. 160 * @param {Object} attr Object literal specifying a set of 161 * configuration attributes used to create the toolbar. 162 */ 163 initAttributes: function(attr) { 164 YAHOO.widget.ToolbarButton.superclass.initAttributes.call(this, attr); 165 /** 166 * @attribute value 167 * @description The value of the button 168 * @type String 169 */ 170 this.setAttributeConfig('value', { 171 value: attr.value 172 }); 173 /** 174 * @attribute menu 175 * @description The menu attribute, see YAHOO.widget.Button 176 * @type Object 177 */ 178 this.setAttributeConfig('menu', { 179 value: attr.menu || false 180 }); 181 /** 182 * @attribute type 183 * @description The type of button to create: push, menu, color, select, spin 184 * @type String 185 */ 186 this.setAttributeConfig('type', { 187 value: attr.type, 188 writeOnce: true, 189 method: function(type) { 190 var el, opt; 191 if (!this._button) { 192 this._button = this.get('element').getElementsByTagName('a')[0]; 193 } 194 switch (type) { 195 case 'select': 196 case 'menu': 197 el = document.createElement('select'); 198 el.id = this.get('id'); 199 var menu = this.get('menu'); 200 for (var i = 0; i < menu.length; i++) { 201 opt = document.createElement('option'); 202 opt.innerHTML = menu[i].text; 203 opt.value = menu[i].value; 204 if (menu[i].checked) { 205 opt.selected = true; 206 } 207 el.appendChild(opt); 208 } 209 this._button.parentNode.replaceChild(el, this._button); 210 Event.on(el, 'change', this._handleSelect, this, true); 211 this._button = el; 212 break; 213 } 214 } 215 }); 216 217 /** 218 * @attribute disabled 219 * @description Set the button into a disabled state 220 * @type String 221 */ 222 this.setAttributeConfig('disabled', { 223 value: attr.disabled || false, 224 method: function(disabled) { 225 if (disabled) { 226 this.addClass('yui-button-disabled'); 227 this.addClass('yui-' + this.get('type') + '-button-disabled'); 228 } else { 229 this.removeClass('yui-button-disabled'); 230 this.removeClass('yui-' + this.get('type') + '-button-disabled'); 231 } 232 if ((this.get('type') == 'menu') || (this.get('type') == 'select')) { 233 this._button.disabled = disabled; 234 } 235 } 236 }); 237 238 /** 239 * @attribute label 240 * @description The text label for the button 241 * @type String 242 */ 243 this.setAttributeConfig('label', { 244 value: attr.label, 245 method: function(label) { 246 if (!this._button) { 247 this._button = this.get('element').getElementsByTagName('a')[0]; 248 } 249 if (this.get('type') == 'push') { 250 this._button.innerHTML = label; 251 } 252 } 253 }); 254 255 /** 256 * @attribute title 257 * @description The title of the button 258 * @type String 259 */ 260 this.setAttributeConfig('title', { 261 value: attr.title 262 }); 263 264 /** 265 * @config container 266 * @description The container that the button is rendered to, handled by Toolbar 267 * @type String 268 */ 269 this.setAttributeConfig('container', { 270 value: null, 271 writeOnce: true, 272 method: function(cont) { 273 this.appendTo(cont); 274 } 275 }); 276 277 }, 278 /** 279 * @private 280 * @method _handleSelect 281 * @description The event fired when a change event gets fired on a select element 282 * @param {Event} ev The change event. 283 */ 284 _handleSelect: function(ev) { 285 var tar = Event.getTarget(ev); 286 var value = tar.options[tar.selectedIndex].value; 287 this.fireEvent('change', {type: 'change', value: value }); 288 }, 289 /** 290 * @method getMenu 291 * @description A stub function to mimic YAHOO.widget.Button's getMenu method 292 */ 293 getMenu: function() { 294 return this.get('menu'); 295 }, 296 /** 297 * @method destroy 298 * @description Destroy the button 299 */ 300 destroy: function() { 301 Event.purgeElement(this.get('element'), true); 302 this.get('element').parentNode.removeChild(this.get('element')); 303 //Brutal Object Destroy 304 for (var i in this) { 305 if (Lang.hasOwnProperty(this, i)) { 306 this[i] = null; 307 } 308 } 309 }, 310 /** 311 * @method fireEvent 312 * @description Overridden fireEvent method to prevent DOM events from firing if the button is disabled. 313 */ 314 fireEvent: function(p_sType, p_aArgs) { 315 // Disabled buttons should not respond to DOM events 316 if (this.DOM_EVENTS[p_sType] && this.get('disabled')) { 317 Event.stopEvent(p_aArgs); 318 return; 319 } 320 321 YAHOO.widget.ToolbarButton.superclass.fireEvent.call(this, p_sType, p_aArgs); 322 }, 323 /** 324 * @method toString 325 * @description Returns a string representing the toolbar. 326 * @return {String} 327 */ 328 toString: function() { 329 return 'ToolbarButton (' + this.get('id') + ')'; 330 } 331 332 }); 333 })(); 334 /** 335 * @module editor 336 * @description <p>Creates a rich Toolbar widget based on Button. Primarily used with the Rich Text Editor</p> 337 * @namespace YAHOO.widget 338 * @requires yahoo, dom, element, event, toolbarbutton 339 * @optional container_core, dragdrop 340 */ 341 (function() { 342 var Dom = YAHOO.util.Dom, 343 Event = YAHOO.util.Event, 344 Lang = YAHOO.lang; 345 346 var getButton = function(id) { 347 var button = id; 348 if (Lang.isString(id)) { 349 button = this.getButtonById(id); 350 } 351 if (Lang.isNumber(id)) { 352 button = this.getButtonByIndex(id); 353 } 354 if ((!(button instanceof YAHOO.widget.ToolbarButton)) && (!(button instanceof YAHOO.widget.ToolbarButtonAdvanced))) { 355 button = this.getButtonByValue(id); 356 } 357 if ((button instanceof YAHOO.widget.ToolbarButton) || (button instanceof YAHOO.widget.ToolbarButtonAdvanced)) { 358 return button; 359 } 360 return false; 361 }; 362 363 /** 364 * Provides a rich toolbar widget based on the button and menu widgets 365 * @constructor 366 * @class Toolbar 367 * @extends YAHOO.util.Element 368 * @param {String/HTMLElement} el The element to turn into a toolbar. 369 * @param {Object} attrs Object liternal containing configuration parameters. 370 */ 371 YAHOO.widget.Toolbar = function(el, attrs) { 372 373 if (Lang.isObject(arguments[0]) && !Dom.get(el).nodeType) { 374 attrs = el; 375 } 376 var local_attrs = {}; 377 if (attrs) { 378 Lang.augmentObject(local_attrs, attrs); //Break the config reference 379 } 380 381 382 var oConfig = { 383 element: null, 384 attributes: local_attrs 385 }; 386 387 388 if (Lang.isString(el) && Dom.get(el)) { 389 oConfig.element = Dom.get(el); 390 } else if (Lang.isObject(el) && Dom.get(el) && Dom.get(el).nodeType) { 391 oConfig.element = Dom.get(el); 392 } 393 394 395 if (!oConfig.element) { 396 oConfig.element = document.createElement('DIV'); 397 oConfig.element.id = Dom.generateId(); 398 399 if (local_attrs.container && Dom.get(local_attrs.container)) { 400 Dom.get(local_attrs.container).appendChild(oConfig.element); 401 } 402 } 403 404 405 if (!oConfig.element.id) { 406 oConfig.element.id = ((Lang.isString(el)) ? el : Dom.generateId()); 407 } 408 409 var fs = document.createElement('fieldset'); 410 var lg = document.createElement('legend'); 411 lg.innerHTML = 'Toolbar'; 412 fs.appendChild(lg); 413 414 var cont = document.createElement('DIV'); 415 oConfig.attributes.cont = cont; 416 Dom.addClass(cont, 'yui-toolbar-subcont'); 417 fs.appendChild(cont); 418 oConfig.element.appendChild(fs); 419 420 oConfig.element.tabIndex = -1; 421 422 423 oConfig.attributes.element = oConfig.element; 424 oConfig.attributes.id = oConfig.element.id; 425 426 this._configuredButtons = []; 427 428 YAHOO.widget.Toolbar.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 429 430 }; 431 432 YAHOO.extend(YAHOO.widget.Toolbar, YAHOO.util.Element, { 433 /** 434 * @protected 435 * @property _configuredButtons 436 * @type Array 437 */ 438 _configuredButtons: null, 439 /** 440 * @method _addMenuClasses 441 * @private 442 * @description This method is called from Menu's renderEvent to add a few more classes to the menu items 443 * @param {String} ev The event that fired. 444 * @param {Array} na Array of event information. 445 * @param {Object} o Button config object. 446 */ 447 _addMenuClasses: function(ev, na, o) { 448 Dom.addClass(this.element, 'yui-toolbar-' + o.get('value') + '-menu'); 449 if (Dom.hasClass(o._button.parentNode.parentNode, 'yui-toolbar-select')) { 450 Dom.addClass(this.element, 'yui-toolbar-select-menu'); 451 } 452 var items = this.getItems(); 453 for (var i = 0; i < items.length; i++) { 454 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())); 455 Dom.addClass(items[i].element, 'yui-toolbar-' + o.get('value') + '-' + ((items[i].value) ? items[i].value.replace(/ /g, '-') : items[i]._oText.nodeValue.replace(/ /g, '-'))); 456 } 457 }, 458 /** 459 * @property buttonType 460 * @description The default button to use 461 * @type Object 462 */ 463 buttonType: YAHOO.widget.ToolbarButton, 464 /** 465 * @property dd 466 * @description The DragDrop instance associated with the Toolbar 467 * @type Object 468 */ 469 dd: null, 470 /** 471 * @property _colorData 472 * @description Object reference containing colors hex and text values. 473 * @type Object 474 */ 475 _colorData: { 476 /* {{{ _colorData */ 477 '#111111': 'Obsidian', 478 '#2D2D2D': 'Dark Gray', 479 '#434343': 'Shale', 480 '#5B5B5B': 'Flint', 481 '#737373': 'Gray', 482 '#8B8B8B': 'Concrete', 483 '#A2A2A2': 'Gray', 484 '#B9B9B9': 'Titanium', 485 '#000000': 'Black', 486 '#D0D0D0': 'Light Gray', 487 '#E6E6E6': 'Silver', 488 '#FFFFFF': 'White', 489 '#BFBF00': 'Pumpkin', 490 '#FFFF00': 'Yellow', 491 '#FFFF40': 'Banana', 492 '#FFFF80': 'Pale Yellow', 493 '#FFFFBF': 'Butter', 494 '#525330': 'Raw Siena', 495 '#898A49': 'Mildew', 496 '#AEA945': 'Olive', 497 '#7F7F00': 'Paprika', 498 '#C3BE71': 'Earth', 499 '#E0DCAA': 'Khaki', 500 '#FCFAE1': 'Cream', 501 '#60BF00': 'Cactus', 502 '#80FF00': 'Chartreuse', 503 '#A0FF40': 'Green', 504 '#C0FF80': 'Pale Lime', 505 '#DFFFBF': 'Light Mint', 506 '#3B5738': 'Green', 507 '#668F5A': 'Lime Gray', 508 '#7F9757': 'Yellow', 509 '#407F00': 'Clover', 510 '#8A9B55': 'Pistachio', 511 '#B7C296': 'Light Jade', 512 '#E6EBD5': 'Breakwater', 513 '#00BF00': 'Spring Frost', 514 '#00FF80': 'Pastel Green', 515 '#40FFA0': 'Light Emerald', 516 '#80FFC0': 'Sea Foam', 517 '#BFFFDF': 'Sea Mist', 518 '#033D21': 'Dark Forrest', 519 '#438059': 'Moss', 520 '#7FA37C': 'Medium Green', 521 '#007F40': 'Pine', 522 '#8DAE94': 'Yellow Gray Green', 523 '#ACC6B5': 'Aqua Lung', 524 '#DDEBE2': 'Sea Vapor', 525 '#00BFBF': 'Fog', 526 '#00FFFF': 'Cyan', 527 '#40FFFF': 'Turquoise Blue', 528 '#80FFFF': 'Light Aqua', 529 '#BFFFFF': 'Pale Cyan', 530 '#033D3D': 'Dark Teal', 531 '#347D7E': 'Gray Turquoise', 532 '#609A9F': 'Green Blue', 533 '#007F7F': 'Seaweed', 534 '#96BDC4': 'Green Gray', 535 '#B5D1D7': 'Soapstone', 536 '#E2F1F4': 'Light Turquoise', 537 '#0060BF': 'Summer Sky', 538 '#0080FF': 'Sky Blue', 539 '#40A0FF': 'Electric Blue', 540 '#80C0FF': 'Light Azure', 541 '#BFDFFF': 'Ice Blue', 542 '#1B2C48': 'Navy', 543 '#385376': 'Biscay', 544 '#57708F': 'Dusty Blue', 545 '#00407F': 'Sea Blue', 546 '#7792AC': 'Sky Blue Gray', 547 '#A8BED1': 'Morning Sky', 548 '#DEEBF6': 'Vapor', 549 '#0000BF': 'Deep Blue', 550 '#0000FF': 'Blue', 551 '#4040FF': 'Cerulean Blue', 552 '#8080FF': 'Evening Blue', 553 '#BFBFFF': 'Light Blue', 554 '#212143': 'Deep Indigo', 555 '#373E68': 'Sea Blue', 556 '#444F75': 'Night Blue', 557 '#00007F': 'Indigo Blue', 558 '#585E82': 'Dockside', 559 '#8687A4': 'Blue Gray', 560 '#D2D1E1': 'Light Blue Gray', 561 '#6000BF': 'Neon Violet', 562 '#8000FF': 'Blue Violet', 563 '#A040FF': 'Violet Purple', 564 '#C080FF': 'Violet Dusk', 565 '#DFBFFF': 'Pale Lavender', 566 '#302449': 'Cool Shale', 567 '#54466F': 'Dark Indigo', 568 '#655A7F': 'Dark Violet', 569 '#40007F': 'Violet', 570 '#726284': 'Smoky Violet', 571 '#9E8FA9': 'Slate Gray', 572 '#DCD1DF': 'Violet White', 573 '#BF00BF': 'Royal Violet', 574 '#FF00FF': 'Fuchsia', 575 '#FF40FF': 'Magenta', 576 '#FF80FF': 'Orchid', 577 '#FFBFFF': 'Pale Magenta', 578 '#4A234A': 'Dark Purple', 579 '#794A72': 'Medium Purple', 580 '#936386': 'Cool Granite', 581 '#7F007F': 'Purple', 582 '#9D7292': 'Purple Moon', 583 '#C0A0B6': 'Pale Purple', 584 '#ECDAE5': 'Pink Cloud', 585 '#BF005F': 'Hot Pink', 586 '#FF007F': 'Deep Pink', 587 '#FF409F': 'Grape', 588 '#FF80BF': 'Electric Pink', 589 '#FFBFDF': 'Pink', 590 '#451528': 'Purple Red', 591 '#823857': 'Purple Dino', 592 '#A94A76': 'Purple Gray', 593 '#7F003F': 'Rose', 594 '#BC6F95': 'Antique Mauve', 595 '#D8A5BB': 'Cool Marble', 596 '#F7DDE9': 'Pink Granite', 597 '#C00000': 'Apple', 598 '#FF0000': 'Fire Truck', 599 '#FF4040': 'Pale Red', 600 '#FF8080': 'Salmon', 601 '#FFC0C0': 'Warm Pink', 602 '#441415': 'Sepia', 603 '#82393C': 'Rust', 604 '#AA4D4E': 'Brick', 605 '#800000': 'Brick Red', 606 '#BC6E6E': 'Mauve', 607 '#D8A3A4': 'Shrimp Pink', 608 '#F8DDDD': 'Shell Pink', 609 '#BF5F00': 'Dark Orange', 610 '#FF7F00': 'Orange', 611 '#FF9F40': 'Grapefruit', 612 '#FFBF80': 'Canteloupe', 613 '#FFDFBF': 'Wax', 614 '#482C1B': 'Dark Brick', 615 '#855A40': 'Dirt', 616 '#B27C51': 'Tan', 617 '#7F3F00': 'Nutmeg', 618 '#C49B71': 'Mustard', 619 '#E1C4A8': 'Pale Tan', 620 '#FDEEE0': 'Marble' 621 /* }}} */ 622 }, 623 /** 624 * @property _colorPicker 625 * @description The HTML Element containing the colorPicker 626 * @type HTMLElement 627 */ 628 _colorPicker: null, 629 /** 630 * @property STR_COLLAPSE 631 * @description String for Toolbar Collapse Button 632 * @type String 633 */ 634 STR_COLLAPSE: 'Collapse Toolbar', 635 /** 636 * @property STR_EXPAND 637 * @description String for Toolbar Collapse Button - Expand 638 * @type String 639 */ 640 STR_EXPAND: 'Expand Toolbar', 641 /** 642 * @property STR_SPIN_LABEL 643 * @description String for spinbutton dynamic label. Note the {VALUE} will be replaced with YAHOO.lang.substitute 644 * @type String 645 */ 646 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.', 647 /** 648 * @property STR_SPIN_UP 649 * @description String for spinbutton up 650 * @type String 651 */ 652 STR_SPIN_UP: 'Click to increase the value of this input', 653 /** 654 * @property STR_SPIN_DOWN 655 * @description String for spinbutton down 656 * @type String 657 */ 658 STR_SPIN_DOWN: 'Click to decrease the value of this input', 659 /** 660 * @property _titlebar 661 * @description Object reference to the titlebar 662 * @type HTMLElement 663 */ 664 _titlebar: null, 665 /** 666 * @property browser 667 * @description Standard browser detection 668 * @type Object 669 */ 670 browser: YAHOO.env.ua, 671 /** 672 * @protected 673 * @property _buttonList 674 * @description Internal property list of current buttons in the toolbar 675 * @type Array 676 */ 677 _buttonList: null, 678 /** 679 * @protected 680 * @property _buttonGroupList 681 * @description Internal property list of current button groups in the toolbar 682 * @type Array 683 */ 684 _buttonGroupList: null, 685 /** 686 * @protected 687 * @property _sep 688 * @description Internal reference to the separator HTML Element for cloning 689 * @type HTMLElement 690 */ 691 _sep: null, 692 /** 693 * @protected 694 * @property _sepCount 695 * @description Internal refernce for counting separators, so we can give them a useful class name for styling 696 * @type Number 697 */ 698 _sepCount: null, 699 /** 700 * @protected 701 * @property draghandle 702 * @type HTMLElement 703 */ 704 _dragHandle: null, 705 /** 706 * @protected 707 * @property _toolbarConfigs 708 * @type Object 709 */ 710 _toolbarConfigs: { 711 renderer: true 712 }, 713 /** 714 * @protected 715 * @property CLASS_CONTAINER 716 * @description Default CSS class to apply to the toolbar container element 717 * @type String 718 */ 719 CLASS_CONTAINER: 'yui-toolbar-container', 720 /** 721 * @protected 722 * @property CLASS_DRAGHANDLE 723 * @description Default CSS class to apply to the toolbar's drag handle element 724 * @type String 725 */ 726 CLASS_DRAGHANDLE: 'yui-toolbar-draghandle', 727 /** 728 * @protected 729 * @property CLASS_SEPARATOR 730 * @description Default CSS class to apply to all separators in the toolbar 731 * @type String 732 */ 733 CLASS_SEPARATOR: 'yui-toolbar-separator', 734 /** 735 * @protected 736 * @property CLASS_DISABLED 737 * @description Default CSS class to apply when the toolbar is disabled 738 * @type String 739 */ 740 CLASS_DISABLED: 'yui-toolbar-disabled', 741 /** 742 * @protected 743 * @property CLASS_PREFIX 744 * @description Default prefix for dynamically created class names 745 * @type String 746 */ 747 CLASS_PREFIX: 'yui-toolbar', 748 /** 749 * @method init 750 * @description The Toolbar class's initialization method 751 */ 752 init: function(p_oElement, p_oAttributes) { 753 YAHOO.widget.Toolbar.superclass.init.call(this, p_oElement, p_oAttributes); 754 }, 755 /** 756 * @method initAttributes 757 * @description Initializes all of the configuration attributes used to create 758 * the toolbar. 759 * @param {Object} attr Object literal specifying a set of 760 * configuration attributes used to create the toolbar. 761 */ 762 initAttributes: function(attr) { 763 YAHOO.widget.Toolbar.superclass.initAttributes.call(this, attr); 764 this.addClass(this.CLASS_CONTAINER); 765 766 /** 767 * @attribute buttonType 768 * @description The buttonType to use (advanced or basic) 769 * @type String 770 */ 771 this.setAttributeConfig('buttonType', { 772 value: attr.buttonType || 'basic', 773 writeOnce: true, 774 validator: function(type) { 775 switch (type) { 776 case 'advanced': 777 case 'basic': 778 return true; 779 } 780 return false; 781 }, 782 method: function(type) { 783 if (type == 'advanced') { 784 if (YAHOO.widget.Button) { 785 this.buttonType = YAHOO.widget.ToolbarButtonAdvanced; 786 } else { 787 this.buttonType = YAHOO.widget.ToolbarButton; 788 } 789 } else { 790 this.buttonType = YAHOO.widget.ToolbarButton; 791 } 792 } 793 }); 794 795 796 /** 797 * @attribute buttons 798 * @description Object specifying the buttons to include in the toolbar 799 * Example: 800 * <code><pre> 801 * { 802 * { id: 'b3', type: 'button', label: 'Underline', value: 'underline' }, 803 * { type: 'separator' }, 804 * { id: 'b4', type: 'menu', label: 'Align', value: 'align', 805 * menu: [ 806 * { text: "Left", value: 'alignleft' }, 807 * { text: "Center", value: 'aligncenter' }, 808 * { text: "Right", value: 'alignright' } 809 * ] 810 * } 811 * } 812 * </pre></code> 813 * @type Array 814 */ 815 816 this.setAttributeConfig('buttons', { 817 value: [], 818 writeOnce: true, 819 method: function(data) { 820 var i, button, buttons, len, b; 821 for (i in data) { 822 if (Lang.hasOwnProperty(data, i)) { 823 if (data[i].type == 'separator') { 824 this.addSeparator(); 825 } else if (data[i].group !== undefined) { 826 buttons = this.addButtonGroup(data[i]); 827 if (buttons) { 828 len = buttons.length; 829 for(b = 0; b < len; b++) { 830 if (buttons[b]) { 831 this._configuredButtons[this._configuredButtons.length] = buttons[b].id; 832 } 833 } 834 } 835 836 } else { 837 button = this.addButton(data[i]); 838 if (button) { 839 this._configuredButtons[this._configuredButtons.length] = button.id; 840 } 841 } 842 } 843 } 844 } 845 }); 846 847 /** 848 * @attribute disabled 849 * @description Boolean indicating if the toolbar should be disabled. It will also disable the draggable attribute if it is on. 850 * @default false 851 * @type Boolean 852 */ 853 this.setAttributeConfig('disabled', { 854 value: false, 855 method: function(disabled) { 856 if (this.get('disabled') === disabled) { 857 return false; 858 } 859 if (disabled) { 860 this.addClass(this.CLASS_DISABLED); 861 this.set('draggable', false); 862 this.disableAllButtons(); 863 } else { 864 this.removeClass(this.CLASS_DISABLED); 865 if (this._configs.draggable._initialConfig.value) { 866 //Draggable by default, set it back 867 this.set('draggable', true); 868 } 869 this.resetAllButtons(); 870 } 871 } 872 }); 873 874 /** 875 * @config cont 876 * @description The container for the toolbar. 877 * @type HTMLElement 878 */ 879 this.setAttributeConfig('cont', { 880 value: attr.cont, 881 readOnly: true 882 }); 883 884 885 /** 886 * @attribute grouplabels 887 * @description Boolean indicating if the toolbar should show the group label's text string. 888 * @default true 889 * @type Boolean 890 */ 891 this.setAttributeConfig('grouplabels', { 892 value: ((attr.grouplabels === false) ? false : true), 893 method: function(grouplabels) { 894 if (grouplabels) { 895 Dom.removeClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels')); 896 } else { 897 Dom.addClass(this.get('cont'), (this.CLASS_PREFIX + '-nogrouplabels')); 898 } 899 } 900 }); 901 /** 902 * @attribute titlebar 903 * @description Boolean indicating if the toolbar should have a titlebar. If 904 * passed a string, it will use that as the titlebar text 905 * @default false 906 * @type Boolean or String 907 */ 908 this.setAttributeConfig('titlebar', { 909 value: false, 910 method: function(titlebar) { 911 if (titlebar) { 912 if (this._titlebar && this._titlebar.parentNode) { 913 this._titlebar.parentNode.removeChild(this._titlebar); 914 } 915 this._titlebar = document.createElement('DIV'); 916 this._titlebar.tabIndex = '-1'; 917 Event.on(this._titlebar, 'focus', function() { 918 this._handleFocus(); 919 }, this, true); 920 Dom.addClass(this._titlebar, this.CLASS_PREFIX + '-titlebar'); 921 if (Lang.isString(titlebar)) { 922 var h2 = document.createElement('h2'); 923 h2.tabIndex = '-1'; 924 h2.innerHTML = '<a href="#" tabIndex="0">' + titlebar + '</a>'; 925 this._titlebar.appendChild(h2); 926 Event.on(h2.firstChild, 'click', function(ev) { 927 Event.stopEvent(ev); 928 }); 929 Event.on([h2, h2.firstChild], 'focus', function() { 930 this._handleFocus(); 931 }, this, true); 932 } 933 if (this.get('firstChild')) { 934 this.insertBefore(this._titlebar, this.get('firstChild')); 935 } else { 936 this.appendChild(this._titlebar); 937 } 938 if (this.get('collapse')) { 939 this.set('collapse', true); 940 } 941 } else if (this._titlebar) { 942 if (this._titlebar && this._titlebar.parentNode) { 943 this._titlebar.parentNode.removeChild(this._titlebar); 944 } 945 } 946 } 947 }); 948 949 950 /** 951 * @attribute collapse 952 * @description Boolean indicating if the the titlebar should have a collapse button. 953 * The collapse button will not remove the toolbar, it will minimize it to the titlebar 954 * @default false 955 * @type Boolean 956 */ 957 this.setAttributeConfig('collapse', { 958 value: false, 959 method: function(collapse) { 960 if (this._titlebar) { 961 var collapseEl = null; 962 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 963 if (collapse) { 964 if (el.length > 0) { 965 //There is already a collapse button 966 return true; 967 } 968 collapseEl = document.createElement('SPAN'); 969 collapseEl.innerHTML = 'X'; 970 collapseEl.title = this.STR_COLLAPSE; 971 972 Dom.addClass(collapseEl, 'collapse'); 973 this._titlebar.appendChild(collapseEl); 974 Event.addListener(collapseEl, 'click', function() { 975 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) { 976 this.collapse(false); //Expand Toolbar 977 } else { 978 this.collapse(); //Collapse Toolbar 979 } 980 }, this, true); 981 } else { 982 collapseEl = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 983 if (collapseEl[0]) { 984 if (Dom.hasClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed')) { 985 //We are closed, reopen the titlebar.. 986 this.collapse(false); //Expand Toolbar 987 } 988 collapseEl[0].parentNode.removeChild(collapseEl[0]); 989 } 990 } 991 } 992 } 993 }); 994 995 /** 996 * @attribute draggable 997 * @description Boolean indicating if the toolbar should be draggable. 998 * @default false 999 * @type Boolean 1000 */ 1001 1002 this.setAttributeConfig('draggable', { 1003 value: (attr.draggable || false), 1004 method: function(draggable) { 1005 if (draggable && !this.get('titlebar')) { 1006 if (!this._dragHandle) { 1007 this._dragHandle = document.createElement('SPAN'); 1008 this._dragHandle.innerHTML = '|'; 1009 this._dragHandle.setAttribute('title', 'Click to drag the toolbar'); 1010 this._dragHandle.id = this.get('id') + '_draghandle'; 1011 Dom.addClass(this._dragHandle, this.CLASS_DRAGHANDLE); 1012 if (this.get('cont').hasChildNodes()) { 1013 this.get('cont').insertBefore(this._dragHandle, this.get('cont').firstChild); 1014 } else { 1015 this.get('cont').appendChild(this._dragHandle); 1016 } 1017 this.dd = new YAHOO.util.DD(this.get('id')); 1018 this.dd.setHandleElId(this._dragHandle.id); 1019 1020 } 1021 } else { 1022 if (this._dragHandle) { 1023 this._dragHandle.parentNode.removeChild(this._dragHandle); 1024 this._dragHandle = null; 1025 this.dd = null; 1026 } 1027 } 1028 if (this._titlebar) { 1029 if (draggable) { 1030 this.dd = new YAHOO.util.DD(this.get('id')); 1031 this.dd.setHandleElId(this._titlebar); 1032 Dom.addClass(this._titlebar, 'draggable'); 1033 } else { 1034 Dom.removeClass(this._titlebar, 'draggable'); 1035 if (this.dd) { 1036 this.dd.unreg(); 1037 this.dd = null; 1038 } 1039 } 1040 } 1041 }, 1042 validator: function(value) { 1043 var ret = true; 1044 if (!YAHOO.util.DD) { 1045 ret = false; 1046 } 1047 return ret; 1048 } 1049 }); 1050 1051 }, 1052 /** 1053 * @method addButtonGroup 1054 * @description Add a new button group to the toolbar. (uses addButton) 1055 * @param {Object} oGroup Object literal reference to the Groups Config (contains an array of button configs as well as the group label) 1056 */ 1057 addButtonGroup: function(oGroup) { 1058 if (!this.get('element')) { 1059 this._queue[this._queue.length] = ['addButtonGroup', arguments]; 1060 return false; 1061 } 1062 1063 if (!this.hasClass(this.CLASS_PREFIX + '-grouped')) { 1064 this.addClass(this.CLASS_PREFIX + '-grouped'); 1065 } 1066 var div = document.createElement('DIV'); 1067 Dom.addClass(div, this.CLASS_PREFIX + '-group'); 1068 Dom.addClass(div, this.CLASS_PREFIX + '-group-' + oGroup.group); 1069 if (oGroup.label) { 1070 var label = document.createElement('h3'); 1071 label.innerHTML = oGroup.label; 1072 div.appendChild(label); 1073 } 1074 if (!this.get('grouplabels')) { 1075 Dom.addClass(this.get('cont'), this.CLASS_PREFIX, '-nogrouplabels'); 1076 } 1077 1078 this.get('cont').appendChild(div); 1079 1080 //For accessibility, let's put all of the group buttons in an Unordered List 1081 var ul = document.createElement('ul'); 1082 div.appendChild(ul); 1083 1084 if (!this._buttonGroupList) { 1085 this._buttonGroupList = {}; 1086 } 1087 1088 this._buttonGroupList[oGroup.group] = ul; 1089 1090 //An array of the button ids added to this group 1091 //This is used for destruction later... 1092 var addedButtons = [], 1093 button; 1094 1095 1096 for (var i = 0; i < oGroup.buttons.length; i++) { 1097 var li = document.createElement('li'); 1098 li.className = this.CLASS_PREFIX + '-groupitem'; 1099 ul.appendChild(li); 1100 if ((oGroup.buttons[i].type !== undefined) && oGroup.buttons[i].type == 'separator') { 1101 this.addSeparator(li); 1102 } else { 1103 oGroup.buttons[i].container = li; 1104 button = this.addButton(oGroup.buttons[i]); 1105 if (button) { 1106 addedButtons[addedButtons.length] = button.id; 1107 } 1108 } 1109 } 1110 return addedButtons; 1111 }, 1112 /** 1113 * @method addButtonToGroup 1114 * @description Add a new button to a toolbar group. Buttons supported: 1115 * push, split, menu, select, color, spin 1116 * @param {Object} oButton Object literal reference to the Button's Config 1117 * @param {String} group The Group identifier passed into the initial config 1118 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1119 */ 1120 addButtonToGroup: function(oButton, group, after) { 1121 var groupCont = this._buttonGroupList[group], 1122 li = document.createElement('li'); 1123 1124 li.className = this.CLASS_PREFIX + '-groupitem'; 1125 oButton.container = li; 1126 this.addButton(oButton, after); 1127 groupCont.appendChild(li); 1128 }, 1129 /** 1130 * @method addButton 1131 * @description Add a new button to the toolbar. Buttons supported: 1132 * push, split, menu, select, color, spin 1133 * @param {Object} oButton Object literal reference to the Button's Config 1134 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1135 */ 1136 addButton: function(oButton, after) { 1137 if (!this.get('element')) { 1138 this._queue[this._queue.length] = ['addButton', arguments]; 1139 return false; 1140 } 1141 if (!this._buttonList) { 1142 this._buttonList = []; 1143 } 1144 if (!oButton.container) { 1145 oButton.container = this.get('cont'); 1146 } 1147 1148 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) { 1149 if (Lang.isArray(oButton.menu)) { 1150 for (var i in oButton.menu) { 1151 if (Lang.hasOwnProperty(oButton.menu, i)) { 1152 var funcObject = { 1153 fn: function(ev, x, oMenu) { 1154 if (!oButton.menucmd) { 1155 oButton.menucmd = oButton.value; 1156 } 1157 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue); 1158 }, 1159 scope: this 1160 }; 1161 oButton.menu[i].onclick = funcObject; 1162 } 1163 } 1164 } 1165 } 1166 var _oButton = {}, skip = false; 1167 for (var o in oButton) { 1168 if (Lang.hasOwnProperty(oButton, o)) { 1169 if (!this._toolbarConfigs[o]) { 1170 _oButton[o] = oButton[o]; 1171 } 1172 } 1173 } 1174 if (oButton.type == 'select') { 1175 _oButton.type = 'menu'; 1176 } 1177 if (oButton.type == 'spin') { 1178 _oButton.type = 'push'; 1179 } 1180 if (_oButton.type == 'color') { 1181 if (YAHOO.widget.Overlay) { 1182 _oButton = this._makeColorButton(_oButton); 1183 } else { 1184 skip = true; 1185 } 1186 } 1187 if (_oButton.menu) { 1188 if ((YAHOO.widget.Overlay) && (oButton.menu instanceof YAHOO.widget.Overlay)) { 1189 oButton.menu.showEvent.subscribe(function() { 1190 this._button = _oButton; 1191 }); 1192 } else { 1193 for (var m = 0; m < _oButton.menu.length; m++) { 1194 if (!_oButton.menu[m].value) { 1195 _oButton.menu[m].value = _oButton.menu[m].text; 1196 } 1197 } 1198 if (this.browser.webkit) { 1199 _oButton.focusmenu = false; 1200 } 1201 } 1202 } 1203 if (skip) { 1204 oButton = false; 1205 } else { 1206 //Add to .get('buttons') manually 1207 this._configs.buttons.value[this._configs.buttons.value.length] = oButton; 1208 1209 var tmp = new this.buttonType(_oButton); 1210 tmp.get('element').tabIndex = '-1'; 1211 tmp.get('element').setAttribute('role', 'button'); 1212 tmp._selected = true; 1213 1214 if (this.get('disabled')) { 1215 //Toolbar is disabled, disable the new button too! 1216 tmp.set('disabled', true); 1217 } 1218 if (!oButton.id) { 1219 oButton.id = tmp.get('id'); 1220 } 1221 1222 if (after) { 1223 var el = tmp.get('element'); 1224 var nextSib = null; 1225 if (after.get) { 1226 nextSib = after.get('element').nextSibling; 1227 } else if (after.nextSibling) { 1228 nextSib = after.nextSibling; 1229 } 1230 if (nextSib) { 1231 nextSib.parentNode.insertBefore(el, nextSib); 1232 } 1233 } 1234 tmp.addClass(this.CLASS_PREFIX + '-' + tmp.get('value')); 1235 1236 var icon = document.createElement('span'); 1237 icon.className = this.CLASS_PREFIX + '-icon'; 1238 tmp.get('element').insertBefore(icon, tmp.get('firstChild')); 1239 if (tmp._button.tagName.toLowerCase() == 'button') { 1240 tmp.get('element').setAttribute('unselectable', 'on'); 1241 //Replace the Button HTML Element with an a href if it exists 1242 var a = document.createElement('a'); 1243 a.innerHTML = tmp._button.innerHTML; 1244 a.href = '#'; 1245 a.tabIndex = '-1'; 1246 Event.on(a, 'click', function(ev) { 1247 Event.stopEvent(ev); 1248 }); 1249 tmp._button.parentNode.replaceChild(a, tmp._button); 1250 tmp._button = a; 1251 } 1252 1253 if (oButton.type == 'select') { 1254 if (tmp._button.tagName.toLowerCase() == 'select') { 1255 icon.parentNode.removeChild(icon); 1256 var iel = tmp._button, 1257 parEl = tmp.get('element'); 1258 parEl.parentNode.replaceChild(iel, parEl); 1259 //The 'element' value is currently the orphaned element 1260 //In order for "destroy" to execute we need to get('element') to reference the correct node. 1261 //I'm not sure if there is a direct approach to setting this value. 1262 tmp._configs.element.value = iel; 1263 } else { 1264 //Don't put a class on it if it's a real select element 1265 tmp.addClass(this.CLASS_PREFIX + '-select'); 1266 } 1267 } 1268 if (oButton.type == 'spin') { 1269 if (!Lang.isArray(oButton.range)) { 1270 oButton.range = [ 10, 100 ]; 1271 } 1272 this._makeSpinButton(tmp, oButton); 1273 } 1274 tmp.get('element').setAttribute('title', tmp.get('label')); 1275 if (oButton.type != 'spin') { 1276 if ((YAHOO.widget.Overlay) && (_oButton.menu instanceof YAHOO.widget.Overlay)) { 1277 var showPicker = function(ev) { 1278 var exec = true; 1279 if (ev.keyCode && (ev.keyCode == 9)) { 1280 exec = false; 1281 } 1282 if (exec) { 1283 if (this._colorPicker) { 1284 this._colorPicker._button = oButton.value; 1285 } 1286 var menuEL = tmp.getMenu().element; 1287 if (Dom.getStyle(menuEL, 'visibility') == 'hidden') { 1288 tmp.getMenu().show(); 1289 } else { 1290 tmp.getMenu().hide(); 1291 } 1292 } 1293 YAHOO.util.Event.stopEvent(ev); 1294 }; 1295 tmp.on('mousedown', showPicker, oButton, this); 1296 tmp.on('keydown', showPicker, oButton, this); 1297 1298 } else if ((oButton.type != 'menu') && (oButton.type != 'select')) { 1299 tmp.on('keypress', this._buttonClick, oButton, this); 1300 tmp.on('mousedown', function(ev) { 1301 YAHOO.util.Event.stopEvent(ev); 1302 this._buttonClick(ev, oButton); 1303 }, oButton, this); 1304 tmp.on('click', function(ev) { 1305 YAHOO.util.Event.stopEvent(ev); 1306 }); 1307 } else { 1308 //Stop the mousedown event so we can trap the selection in the editor! 1309 tmp.on('mousedown', function(ev) { 1310 //YAHOO.util.Event.stopEvent(ev); 1311 }); 1312 tmp.on('click', function(ev) { 1313 //YAHOO.util.Event.stopEvent(ev); 1314 }); 1315 tmp.on('change', function(ev) { 1316 if (!ev.target) { 1317 if (!oButton.menucmd) { 1318 oButton.menucmd = oButton.value; 1319 } 1320 oButton.value = ev.value; 1321 this._buttonClick(ev, oButton); 1322 } 1323 }, this, true); 1324 1325 var self = this; 1326 //Hijack the mousedown event in the menu and make it fire a button click.. 1327 tmp.on('appendTo', function() { 1328 var tmp = this; 1329 if (tmp.getMenu() && tmp.getMenu().mouseDownEvent) { 1330 tmp.getMenu().mouseDownEvent.subscribe(function(ev, args) { 1331 var oMenu = args[1]; 1332 YAHOO.util.Event.stopEvent(args[0]); 1333 tmp._onMenuClick(args[0], tmp); 1334 if (!oButton.menucmd) { 1335 oButton.menucmd = oButton.value; 1336 } 1337 oButton.value = ((oMenu.value) ? oMenu.value : oMenu._oText.nodeValue); 1338 self._buttonClick.call(self, args[1], oButton); 1339 tmp._hideMenu(); 1340 return false; 1341 }); 1342 tmp.getMenu().clickEvent.subscribe(function(ev, args) { 1343 YAHOO.util.Event.stopEvent(args[0]); 1344 }); 1345 tmp.getMenu().mouseUpEvent.subscribe(function(ev, args) { 1346 YAHOO.util.Event.stopEvent(args[0]); 1347 }); 1348 } 1349 }); 1350 1351 } 1352 } else { 1353 //Stop the mousedown event so we can trap the selection in the editor! 1354 tmp.on('mousedown', function(ev) { 1355 YAHOO.util.Event.stopEvent(ev); 1356 }); 1357 tmp.on('click', function(ev) { 1358 YAHOO.util.Event.stopEvent(ev); 1359 }); 1360 } 1361 if (this.browser.ie) { 1362 /* 1363 //Add a couple of new events for IE 1364 tmp.DOM_EVENTS.focusin = true; 1365 tmp.DOM_EVENTS.focusout = true; 1366 1367 //Stop them so we don't loose focus in the Editor 1368 tmp.on('focusin', function(ev) { 1369 YAHOO.util.Event.stopEvent(ev); 1370 }, oButton, this); 1371 1372 tmp.on('focusout', function(ev) { 1373 YAHOO.util.Event.stopEvent(ev); 1374 }, oButton, this); 1375 tmp.on('click', function(ev) { 1376 YAHOO.util.Event.stopEvent(ev); 1377 }, oButton, this); 1378 */ 1379 } 1380 if (this.browser.webkit) { 1381 //This will keep the document from gaining focus and the editor from loosing it.. 1382 //Forcefully remove the focus calls in button! 1383 tmp.hasFocus = function() { 1384 return true; 1385 }; 1386 } 1387 this._buttonList[this._buttonList.length] = tmp; 1388 if ((oButton.type == 'menu') || (oButton.type == 'split') || (oButton.type == 'select')) { 1389 if (Lang.isArray(oButton.menu)) { 1390 var menu = tmp.getMenu(); 1391 if (menu && menu.renderEvent) { 1392 menu.renderEvent.subscribe(this._addMenuClasses, tmp); 1393 if (oButton.renderer) { 1394 menu.renderEvent.subscribe(oButton.renderer, tmp); 1395 } 1396 } 1397 } 1398 } 1399 } 1400 return oButton; 1401 }, 1402 /** 1403 * @method addSeparator 1404 * @description Add a new button separator to the toolbar. 1405 * @param {HTMLElement} cont Optional HTML element to insert this button into. 1406 * @param {HTMLElement} after Optional HTML element to insert this button after in the DOM. 1407 */ 1408 addSeparator: function(cont, after) { 1409 if (!this.get('element')) { 1410 this._queue[this._queue.length] = ['addSeparator', arguments]; 1411 return false; 1412 } 1413 var sepCont = ((cont) ? cont : this.get('cont')); 1414 if (!this.get('element')) { 1415 this._queue[this._queue.length] = ['addSeparator', arguments]; 1416 return false; 1417 } 1418 if (this._sepCount === null) { 1419 this._sepCount = 0; 1420 } 1421 if (!this._sep) { 1422 this._sep = document.createElement('SPAN'); 1423 Dom.addClass(this._sep, this.CLASS_SEPARATOR); 1424 this._sep.innerHTML = '|'; 1425 } 1426 var _sep = this._sep.cloneNode(true); 1427 this._sepCount++; 1428 Dom.addClass(_sep, this.CLASS_SEPARATOR + '-' + this._sepCount); 1429 if (after) { 1430 var nextSib = null; 1431 if (after.get) { 1432 nextSib = after.get('element').nextSibling; 1433 } else if (after.nextSibling) { 1434 nextSib = after.nextSibling; 1435 } else { 1436 nextSib = after; 1437 } 1438 if (nextSib) { 1439 if (nextSib == after) { 1440 nextSib.parentNode.appendChild(_sep); 1441 } else { 1442 nextSib.parentNode.insertBefore(_sep, nextSib); 1443 } 1444 } 1445 } else { 1446 sepCont.appendChild(_sep); 1447 } 1448 return _sep; 1449 }, 1450 /** 1451 * @method _createColorPicker 1452 * @private 1453 * @description Creates the core DOM reference to the color picker menu item. 1454 * @param {String} id the id of the toolbar to prefix this DOM container with. 1455 */ 1456 _createColorPicker: function(id) { 1457 if (Dom.get(id + '_colors')) { 1458 Dom.get(id + '_colors').parentNode.removeChild(Dom.get(id + '_colors')); 1459 } 1460 var picker = document.createElement('div'); 1461 picker.className = 'yui-toolbar-colors'; 1462 picker.id = id + '_colors'; 1463 picker.style.display = 'none'; 1464 Event.on(window, 'load', function() { 1465 document.body.appendChild(picker); 1466 }, this, true); 1467 1468 this._colorPicker = picker; 1469 1470 var html = ''; 1471 for (var i in this._colorData) { 1472 if (Lang.hasOwnProperty(this._colorData, i)) { 1473 html += '<a style="background-color: ' + i + '" href="#">' + i.replace('#', '') + '</a>'; 1474 } 1475 } 1476 html += '<span><em>X</em><strong></strong></span>'; 1477 window.setTimeout(function() { 1478 picker.innerHTML = html; 1479 }, 0); 1480 1481 Event.on(picker, 'mouseover', function(ev) { 1482 var picker = this._colorPicker; 1483 var em = picker.getElementsByTagName('em')[0]; 1484 var strong = picker.getElementsByTagName('strong')[0]; 1485 var tar = Event.getTarget(ev); 1486 if (tar.tagName.toLowerCase() == 'a') { 1487 em.style.backgroundColor = tar.style.backgroundColor; 1488 strong.innerHTML = this._colorData['#' + tar.innerHTML] + '<br>' + tar.innerHTML; 1489 } 1490 }, this, true); 1491 Event.on(picker, 'focus', function(ev) { 1492 Event.stopEvent(ev); 1493 }); 1494 Event.on(picker, 'click', function(ev) { 1495 Event.stopEvent(ev); 1496 }); 1497 Event.on(picker, 'mousedown', function(ev) { 1498 Event.stopEvent(ev); 1499 var tar = Event.getTarget(ev); 1500 if (tar.tagName.toLowerCase() == 'a') { 1501 var retVal = this.fireEvent('colorPickerClicked', { type: 'colorPickerClicked', target: this, button: this._colorPicker._button, color: tar.innerHTML, colorName: this._colorData['#' + tar.innerHTML] } ); 1502 if (retVal !== false) { 1503 var info = { 1504 color: tar.innerHTML, 1505 colorName: this._colorData['#' + tar.innerHTML], 1506 value: this._colorPicker._button 1507 }; 1508 1509 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info }); 1510 } 1511 this.getButtonByValue(this._colorPicker._button).getMenu().hide(); 1512 } 1513 }, this, true); 1514 }, 1515 /** 1516 * @method _resetColorPicker 1517 * @private 1518 * @description Clears the currently selected color or mouseover color in the color picker. 1519 */ 1520 _resetColorPicker: function() { 1521 var em = this._colorPicker.getElementsByTagName('em')[0]; 1522 var strong = this._colorPicker.getElementsByTagName('strong')[0]; 1523 em.style.backgroundColor = 'transparent'; 1524 strong.innerHTML = ''; 1525 }, 1526 /** 1527 * @method _makeColorButton 1528 * @private 1529 * @description Called to turn a "color" button into a menu button with an Overlay for the menu. 1530 * @param {Object} _oButton <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference 1531 */ 1532 _makeColorButton: function(_oButton) { 1533 if (!this._colorPicker) { 1534 this._createColorPicker(this.get('id')); 1535 } 1536 _oButton.type = 'color'; 1537 _oButton.menu = new YAHOO.widget.Overlay(this.get('id') + '_' + _oButton.value + '_menu', { visible: false, position: 'absolute', iframe: true }); 1538 _oButton.menu.setBody(''); 1539 _oButton.menu.render(this.get('cont')); 1540 Dom.addClass(_oButton.menu.element, 'yui-button-menu'); 1541 Dom.addClass(_oButton.menu.element, 'yui-color-button-menu'); 1542 _oButton.menu.beforeShowEvent.subscribe(function() { 1543 _oButton.menu.cfg.setProperty('zindex', 5); //Re Adjust the overlays zIndex.. not sure why. 1544 _oButton.menu.cfg.setProperty('context', [this.getButtonById(_oButton.id).get('element'), 'tl', 'bl']); //Re Adjust the overlay.. not sure why. 1545 //Move the DOM reference of the color picker to the Overlay that we are about to show. 1546 this._resetColorPicker(); 1547 var _p = this._colorPicker; 1548 if (_p.parentNode) { 1549 _p.parentNode.removeChild(_p); 1550 } 1551 _oButton.menu.setBody(''); 1552 _oButton.menu.appendToBody(_p); 1553 this._colorPicker.style.display = 'block'; 1554 }, this, true); 1555 return _oButton; 1556 }, 1557 /** 1558 * @private 1559 * @method _makeSpinButton 1560 * @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. 1561 * @param {Object} _button <a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> reference 1562 * @param {Object} oButton Object literal containing the buttons initial config 1563 */ 1564 _makeSpinButton: function(_button, oButton) { 1565 _button.addClass(this.CLASS_PREFIX + '-spinbutton'); 1566 var self = this, 1567 _par = _button._button.parentNode.parentNode, //parentNode of Button Element for appending child 1568 range = oButton.range, 1569 _b1 = document.createElement('a'), 1570 _b2 = document.createElement('a'); 1571 _b1.href = '#'; 1572 _b2.href = '#'; 1573 _b1.tabIndex = '-1'; 1574 _b2.tabIndex = '-1'; 1575 1576 //Setup the up and down arrows 1577 _b1.className = 'up'; 1578 _b1.title = this.STR_SPIN_UP; 1579 _b1.innerHTML = this.STR_SPIN_UP; 1580 _b2.className = 'down'; 1581 _b2.title = this.STR_SPIN_DOWN; 1582 _b2.innerHTML = this.STR_SPIN_DOWN; 1583 1584 //Append them to the container 1585 _par.appendChild(_b1); 1586 _par.appendChild(_b2); 1587 1588 var label = YAHOO.lang.substitute(this.STR_SPIN_LABEL, { VALUE: _button.get('label') }); 1589 _button.set('title', label); 1590 1591 var cleanVal = function(value) { 1592 value = ((value < range[0]) ? range[0] : value); 1593 value = ((value > range[1]) ? range[1] : value); 1594 return value; 1595 }; 1596 1597 var br = this.browser; 1598 var tbar = false; 1599 var strLabel = this.STR_SPIN_LABEL; 1600 if (this._titlebar && this._titlebar.firstChild) { 1601 tbar = this._titlebar.firstChild; 1602 } 1603 1604 var _intUp = function(ev) { 1605 YAHOO.util.Event.stopEvent(ev); 1606 if (!_button.get('disabled') && (ev.keyCode != 9)) { 1607 var value = parseInt(_button.get('label'), 10); 1608 value++; 1609 value = cleanVal(value); 1610 _button.set('label', ''+value); 1611 var label = YAHOO.lang.substitute(strLabel, { VALUE: _button.get('label') }); 1612 _button.set('title', label); 1613 if (!br.webkit && tbar) { 1614 //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 1615 //_button.focus(); 1616 } 1617 self._buttonClick(ev, oButton); 1618 } 1619 }; 1620 1621 var _intDown = function(ev) { 1622 YAHOO.util.Event.stopEvent(ev); 1623 if (!_button.get('disabled') && (ev.keyCode != 9)) { 1624 var value = parseInt(_button.get('label'), 10); 1625 value--; 1626 value = cleanVal(value); 1627 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 _intKeyUp = function(ev) { 1640 if (ev.keyCode == 38) { 1641 _intUp(ev); 1642 } else if (ev.keyCode == 40) { 1643 _intDown(ev); 1644 } else if (ev.keyCode == 107 && ev.shiftKey) { //Plus Key 1645 _intUp(ev); 1646 } else if (ev.keyCode == 109 && ev.shiftKey) { //Minus Key 1647 _intDown(ev); 1648 } 1649 }; 1650 1651 //Handle arrow keys.. 1652 _button.on('keydown', _intKeyUp, this, true); 1653 1654 //Listen for the click on the up button and act on it 1655 //Listen for the click on the down button and act on it 1656 Event.on(_b1, 'mousedown',function(ev) { 1657 Event.stopEvent(ev); 1658 }, this, true); 1659 Event.on(_b2, 'mousedown', function(ev) { 1660 Event.stopEvent(ev); 1661 }, this, true); 1662 Event.on(_b1, 'click', _intUp, this, true); 1663 Event.on(_b2, 'click', _intDown, this, true); 1664 }, 1665 /** 1666 * @protected 1667 * @method _buttonClick 1668 * @description Click handler for all buttons in the toolbar. 1669 * @param {String} ev The event that was passed in. 1670 * @param {Object} info Object literal of information about the button that was clicked. 1671 */ 1672 _buttonClick: function(ev, info) { 1673 var doEvent = true; 1674 1675 if (ev && ev.type == 'keypress') { 1676 if (ev.keyCode == 9) { 1677 doEvent = false; 1678 } else if ((ev.keyCode === 13) || (ev.keyCode === 0) || (ev.keyCode === 32)) { 1679 } else { 1680 doEvent = false; 1681 } 1682 } 1683 1684 if (doEvent) { 1685 var fireNextEvent = true, 1686 retValue = false; 1687 1688 info.isSelected = this.isSelected(info.id); 1689 1690 if (info.value) { 1691 retValue = this.fireEvent(info.value + 'Click', { type: info.value + 'Click', target: this.get('element'), button: info }); 1692 if (retValue === false) { 1693 fireNextEvent = false; 1694 } 1695 } 1696 1697 if (info.menucmd && fireNextEvent) { 1698 retValue = this.fireEvent(info.menucmd + 'Click', { type: info.menucmd + 'Click', target: this.get('element'), button: info }); 1699 if (retValue === false) { 1700 fireNextEvent = false; 1701 } 1702 } 1703 if (fireNextEvent) { 1704 this.fireEvent('buttonClick', { type: 'buttonClick', target: this.get('element'), button: info }); 1705 } 1706 1707 if (info.type == 'select') { 1708 var button = this.getButtonById(info.id); 1709 if (button.buttonType == 'rich') { 1710 var txt = info.value; 1711 for (var i = 0; i < info.menu.length; i++) { 1712 if (info.menu[i].value == info.value) { 1713 txt = info.menu[i].text; 1714 break; 1715 } 1716 } 1717 button.set('label', '<span class="yui-toolbar-' + info.menucmd + '-' + (info.value).replace(/ /g, '-').toLowerCase() + '">' + txt + '</span>'); 1718 var _items = button.getMenu().getItems(); 1719 for (var m = 0; m < _items.length; m++) { 1720 if (_items[m].value.toLowerCase() == info.value.toLowerCase()) { 1721 _items[m].cfg.setProperty('checked', true); 1722 } else { 1723 _items[m].cfg.setProperty('checked', false); 1724 } 1725 } 1726 } 1727 } 1728 if (ev) { 1729 Event.stopEvent(ev); 1730 } 1731 } 1732 }, 1733 /** 1734 * @private 1735 * @property _keyNav 1736 * @description Flag to determine if the arrow nav listeners have been attached 1737 * @type Boolean 1738 */ 1739 _keyNav: null, 1740 /** 1741 * @private 1742 * @property _navCounter 1743 * @description Internal counter for walking the buttons in the toolbar with the arrow keys 1744 * @type Number 1745 */ 1746 _navCounter: null, 1747 /** 1748 * @private 1749 * @method _navigateButtons 1750 * @description Handles the navigation/focus of toolbar buttons with the Arrow Keys 1751 * @param {Event} ev The Key Event 1752 */ 1753 _navigateButtons: function(ev) { 1754 switch (ev.keyCode) { 1755 case 37: 1756 case 39: 1757 if (ev.keyCode == 37) { 1758 this._navCounter--; 1759 } else { 1760 this._navCounter++; 1761 } 1762 if (this._navCounter > (this._buttonList.length - 1)) { 1763 this._navCounter = 0; 1764 } 1765 if (this._navCounter < 0) { 1766 this._navCounter = (this._buttonList.length - 1); 1767 } 1768 if (this._buttonList[this._navCounter]) { 1769 var el = this._buttonList[this._navCounter].get('element'); 1770 if (this.browser.ie) { 1771 el = this._buttonList[this._navCounter].get('element').getElementsByTagName('a')[0]; 1772 } 1773 if (this._buttonList[this._navCounter].get('disabled')) { 1774 this._navigateButtons(ev); 1775 } else { 1776 el.focus(); 1777 } 1778 } 1779 break; 1780 } 1781 }, 1782 /** 1783 * @private 1784 * @method _handleFocus 1785 * @description Sets up the listeners for the arrow key navigation 1786 */ 1787 _handleFocus: function() { 1788 if (!this._keyNav) { 1789 var ev = 'keypress'; 1790 if (this.browser.ie) { 1791 ev = 'keydown'; 1792 } 1793 Event.on(this.get('element'), ev, this._navigateButtons, this, true); 1794 this._keyNav = true; 1795 this._navCounter = -1; 1796 } 1797 }, 1798 /** 1799 * @method getButtonById 1800 * @description Gets a button instance from the toolbar by is Dom id. 1801 * @param {String} id The Dom id to query for. 1802 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>} 1803 */ 1804 getButtonById: function(id) { 1805 var len = this._buttonList.length; 1806 for (var i = 0; i < len; i++) { 1807 if (this._buttonList[i] && this._buttonList[i].get('id') == id) { 1808 return this._buttonList[i]; 1809 } 1810 } 1811 return false; 1812 }, 1813 /** 1814 * @method getButtonByValue 1815 * @description Gets a button instance or a menuitem instance from the toolbar by it's value. 1816 * @param {String} value The button value to query for. 1817 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a> or <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a>} 1818 */ 1819 getButtonByValue: function(value) { 1820 var _buttons = this.get('buttons'); 1821 if (!_buttons) { 1822 return false; 1823 } 1824 var len = _buttons.length; 1825 for (var i = 0; i < len; i++) { 1826 if (_buttons[i].group !== undefined) { 1827 for (var m = 0; m < _buttons[i].buttons.length; m++) { 1828 if ((_buttons[i].buttons[m].value == value) || (_buttons[i].buttons[m].menucmd == value)) { 1829 return this.getButtonById(_buttons[i].buttons[m].id); 1830 } 1831 if (_buttons[i].buttons[m].menu) { //Menu Button, loop through the values 1832 for (var s = 0; s < _buttons[i].buttons[m].menu.length; s++) { 1833 if (_buttons[i].buttons[m].menu[s].value == value) { 1834 return this.getButtonById(_buttons[i].buttons[m].id); 1835 } 1836 } 1837 } 1838 } 1839 } else { 1840 if ((_buttons[i].value == value) || (_buttons[i].menucmd == value)) { 1841 return this.getButtonById(_buttons[i].id); 1842 } 1843 if (_buttons[i].menu) { //Menu Button, loop through the values 1844 for (var j = 0; j < _buttons[i].menu.length; j++) { 1845 if (_buttons[i].menu[j].value == value) { 1846 return this.getButtonById(_buttons[i].id); 1847 } 1848 } 1849 } 1850 } 1851 } 1852 return false; 1853 }, 1854 /** 1855 * @method getButtonByIndex 1856 * @description Gets a button instance from the toolbar by is index in _buttonList. 1857 * @param {Number} index The index of the button in _buttonList. 1858 * @return {<a href="YAHOO.widget.ToolbarButton.html">YAHOO.widget.ToolbarButton</a>} 1859 */ 1860 getButtonByIndex: function(index) { 1861 if (this._buttonList[index]) { 1862 return this._buttonList[index]; 1863 } else { 1864 return false; 1865 } 1866 }, 1867 /** 1868 * @method getButtons 1869 * @description Returns an array of buttons in the current toolbar 1870 * @return {Array} 1871 */ 1872 getButtons: function() { 1873 return this._buttonList; 1874 }, 1875 /** 1876 * @method disableButton 1877 * @description Disables a button in the toolbar. 1878 * @param {String/Number} id Disable a button by it's id, index or value. 1879 * @return {Boolean} 1880 */ 1881 disableButton: function(id) { 1882 var button = getButton.call(this, id); 1883 if (button) { 1884 button.set('disabled', true); 1885 } else { 1886 return false; 1887 } 1888 }, 1889 /** 1890 * @method enableButton 1891 * @description Enables a button in the toolbar. 1892 * @param {String/Number} id Enable a button by it's id, index or value. 1893 * @return {Boolean} 1894 */ 1895 enableButton: function(id) { 1896 if (this.get('disabled')) { 1897 return false; 1898 } 1899 var button = getButton.call(this, id); 1900 if (button) { 1901 if (button.get('disabled')) { 1902 button.set('disabled', false); 1903 } 1904 } else { 1905 return false; 1906 } 1907 }, 1908 /** 1909 * @method isSelected 1910 * @description Tells if a button is selected or not. 1911 * @param {String/Number} id A button by it's id, index or value. 1912 * @return {Boolean} 1913 */ 1914 isSelected: function(id) { 1915 var button = getButton.call(this, id); 1916 if (button) { 1917 return button._selected; 1918 } 1919 return false; 1920 }, 1921 /** 1922 * @method selectButton 1923 * @description Selects a button in the toolbar. 1924 * @param {String/Number} id Select a button by it's id, index or value. 1925 * @param {String} value If this is a Menu Button, check this item in the menu 1926 * @return {Boolean} 1927 */ 1928 selectButton: function(id, value) { 1929 var button = getButton.call(this, id); 1930 if (button) { 1931 button.addClass('yui-button-selected'); 1932 button.addClass('yui-button-' + button.get('value') + '-selected'); 1933 button._selected = true; 1934 if (value) { 1935 if (button.buttonType == 'rich') { 1936 var _items = button.getMenu().getItems(); 1937 for (var m = 0; m < _items.length; m++) { 1938 if (_items[m].value == value) { 1939 _items[m].cfg.setProperty('checked', true); 1940 button.set('label', '<span class="yui-toolbar-' + button.get('value') + '-' + (value).replace(/ /g, '-').toLowerCase() + '">' + _items[m]._oText.nodeValue + '</span>'); 1941 } else { 1942 _items[m].cfg.setProperty('checked', false); 1943 } 1944 } 1945 } 1946 } 1947 } else { 1948 return false; 1949 } 1950 }, 1951 /** 1952 * @method deselectButton 1953 * @description Deselects a button in the toolbar. 1954 * @param {String/Number} id Deselect a button by it's id, index or value. 1955 * @return {Boolean} 1956 */ 1957 deselectButton: function(id) { 1958 var button = getButton.call(this, id); 1959 if (button) { 1960 button.removeClass('yui-button-selected'); 1961 button.removeClass('yui-button-' + button.get('value') + '-selected'); 1962 button.removeClass('yui-button-hover'); 1963 button._selected = false; 1964 } else { 1965 return false; 1966 } 1967 }, 1968 /** 1969 * @method deselectAllButtons 1970 * @description Deselects all buttons in the toolbar. 1971 * @return {Boolean} 1972 */ 1973 deselectAllButtons: function() { 1974 var len = this._buttonList.length; 1975 for (var i = 0; i < len; i++) { 1976 this.deselectButton(this._buttonList[i]); 1977 } 1978 }, 1979 /** 1980 * @method disableAllButtons 1981 * @description Disables all buttons in the toolbar. 1982 * @return {Boolean} 1983 */ 1984 disableAllButtons: function() { 1985 if (this.get('disabled')) { 1986 return false; 1987 } 1988 var len = this._buttonList.length; 1989 for (var i = 0; i < len; i++) { 1990 this.disableButton(this._buttonList[i]); 1991 } 1992 }, 1993 /** 1994 * @method enableAllButtons 1995 * @description Enables all buttons in the toolbar. 1996 * @return {Boolean} 1997 */ 1998 enableAllButtons: function() { 1999 if (this.get('disabled')) { 2000 return false; 2001 } 2002 var len = this._buttonList.length; 2003 for (var i = 0; i < len; i++) { 2004 this.enableButton(this._buttonList[i]); 2005 } 2006 }, 2007 /** 2008 * @method resetAllButtons 2009 * @description Resets all buttons to their initial state. 2010 * @param {Object} _ex Except these buttons 2011 * @return {Boolean} 2012 */ 2013 resetAllButtons: function(_ex) { 2014 if (!Lang.isObject(_ex)) { 2015 _ex = {}; 2016 } 2017 if (this.get('disabled') || !this._buttonList) { 2018 return false; 2019 } 2020 var len = this._buttonList.length; 2021 for (var i = 0; i < len; i++) { 2022 var _button = this._buttonList[i]; 2023 if (_button) { 2024 var disabled = _button._configs.disabled._initialConfig.value; 2025 if (_ex[_button.get('id')]) { 2026 this.enableButton(_button); 2027 this.selectButton(_button); 2028 } else { 2029 if (disabled) { 2030 this.disableButton(_button); 2031 } else { 2032 this.enableButton(_button); 2033 } 2034 this.deselectButton(_button); 2035 } 2036 } 2037 } 2038 }, 2039 /** 2040 * @method destroyButton 2041 * @description Destroy a button in the toolbar. 2042 * @param {String/Number} id Destroy a button by it's id or index. 2043 * @return {Boolean} 2044 */ 2045 destroyButton: function(id) { 2046 var button = getButton.call(this, id); 2047 if (button) { 2048 var thisID = button.get('id'), 2049 new_list = [], i = 0, 2050 len = this._buttonList.length; 2051 2052 button.destroy(); 2053 2054 for (i = 0; i < len; i++) { 2055 if (this._buttonList[i].get('id') != thisID) { 2056 new_list[new_list.length]= this._buttonList[i]; 2057 } 2058 } 2059 2060 this._buttonList = new_list; 2061 } else { 2062 return false; 2063 } 2064 }, 2065 /** 2066 * @method destroy 2067 * @description Destroys the toolbar, all of it's elements and objects. 2068 * @return {Boolean} 2069 */ 2070 destroy: function() { 2071 var len = this._configuredButtons.length, j, i, b; 2072 for(b = 0; b < len; b++) { 2073 this.destroyButton(this._configuredButtons[b]); 2074 } 2075 2076 this._configuredButtons = null; 2077 2078 this.get('element').innerHTML = ''; 2079 this.get('element').className = ''; 2080 //Brutal Object Destroy 2081 for (i in this) { 2082 if (Lang.hasOwnProperty(this, i)) { 2083 this[i] = null; 2084 } 2085 } 2086 return true; 2087 }, 2088 /** 2089 * @method collapse 2090 * @description Programatically collapse the toolbar. 2091 * @param {Boolean} collapse True to collapse, false to expand. 2092 */ 2093 collapse: function(collapse) { 2094 var el = Dom.getElementsByClassName('collapse', 'span', this._titlebar); 2095 if (collapse === false) { 2096 Dom.removeClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed'); 2097 if (el[0]) { 2098 Dom.removeClass(el[0], 'collapsed'); 2099 el[0].title = this.STR_COLLAPSE; 2100 } 2101 this.fireEvent('toolbarExpanded', { type: 'toolbarExpanded', target: this }); 2102 } else { 2103 if (el[0]) { 2104 Dom.addClass(el[0], 'collapsed'); 2105 el[0].title = this.STR_EXPAND; 2106 } 2107 Dom.addClass(this.get('cont').parentNode, 'yui-toolbar-container-collapsed'); 2108 this.fireEvent('toolbarCollapsed', { type: 'toolbarCollapsed', target: this }); 2109 } 2110 }, 2111 /** 2112 * @method toString 2113 * @description Returns a string representing the toolbar. 2114 * @return {String} 2115 */ 2116 toString: function() { 2117 return 'Toolbar (#' + this.get('element').id + ') with ' + this._buttonList.length + ' buttons.'; 2118 } 2119 }); 2120 /** 2121 * @event buttonClick 2122 * @param {Object} o The object passed to this handler is the button config used to create the button. 2123 * @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. 2124 * @type YAHOO.util.CustomEvent 2125 */ 2126 /** 2127 * @event valueClick 2128 * @param {Object} o The object passed to this handler is the button config used to create the button. 2129 * @description This is a special dynamic event that is created and dispatched based on the value property 2130 * of the button config. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 2131 * Example: 2132 * <code><pre> 2133 * buttons : [ 2134 * { type: 'button', value: 'test', value: 'testButton' } 2135 * ]</pre> 2136 * </code> 2137 * With the valueClick event you could subscribe to this buttons click event with this: 2138 * tbar.in('testButtonClick', function() { alert('test button clicked'); }) 2139 * @type YAHOO.util.CustomEvent 2140 */ 2141 /** 2142 * @event toolbarExpanded 2143 * @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. 2144 * @type YAHOO.util.CustomEvent 2145 */ 2146 /** 2147 * @event toolbarCollapsed 2148 * @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. 2149 * @type YAHOO.util.CustomEvent 2150 */ 2151 })(); 2152 /** 2153 * @module editor 2154 * @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> 2155 * @namespace YAHOO.widget 2156 * @requires yahoo, dom, element, event, toolbar 2157 * @optional animation, container_core, resize, dragdrop 2158 */ 2159 2160 (function() { 2161 var Dom = YAHOO.util.Dom, 2162 Event = YAHOO.util.Event, 2163 Lang = YAHOO.lang, 2164 Toolbar = YAHOO.widget.Toolbar; 2165 2166 /** 2167 * 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. 2168 * @constructor 2169 * @class SimpleEditor 2170 * @extends YAHOO.util.Element 2171 * @param {String/HTMLElement} el The textarea element to turn into an editor. 2172 * @param {Object} attrs Object liternal containing configuration parameters. 2173 */ 2174 2175 YAHOO.widget.SimpleEditor = function(el, attrs) { 2176 2177 var o = {}; 2178 if (Lang.isObject(el) && (!el.tagName) && !attrs) { 2179 Lang.augmentObject(o, el); //Break the config reference 2180 el = document.createElement('textarea'); 2181 this.DOMReady = true; 2182 if (o.container) { 2183 var c = Dom.get(o.container); 2184 c.appendChild(el); 2185 } else { 2186 document.body.appendChild(el); 2187 } 2188 } else { 2189 if (attrs) { 2190 Lang.augmentObject(o, attrs); //Break the config reference 2191 } 2192 } 2193 2194 var oConfig = { 2195 element: null, 2196 attributes: o 2197 }, id = null; 2198 2199 if (Lang.isString(el)) { 2200 id = el; 2201 } else { 2202 if (oConfig.attributes.id) { 2203 id = oConfig.attributes.id; 2204 } else { 2205 this.DOMReady = true; 2206 id = Dom.generateId(el); 2207 } 2208 } 2209 oConfig.element = el; 2210 2211 var element_cont = document.createElement('DIV'); 2212 oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, { 2213 id: id + '_container' 2214 }); 2215 var div = document.createElement('div'); 2216 Dom.addClass(div, 'first-child'); 2217 oConfig.attributes.element_cont.appendChild(div); 2218 2219 if (!oConfig.attributes.toolbar_cont) { 2220 oConfig.attributes.toolbar_cont = document.createElement('DIV'); 2221 oConfig.attributes.toolbar_cont.id = id + '_toolbar'; 2222 div.appendChild(oConfig.attributes.toolbar_cont); 2223 } 2224 var editorWrapper = document.createElement('DIV'); 2225 div.appendChild(editorWrapper); 2226 oConfig.attributes.editor_wrapper = editorWrapper; 2227 2228 YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes); 2229 }; 2230 2231 2232 YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, { 2233 /** 2234 * @private 2235 * @property _resizeConfig 2236 * @description The default config for the Resize Utility 2237 */ 2238 _resizeConfig: { 2239 handles: ['br'], 2240 autoRatio: true, 2241 status: true, 2242 proxy: true, 2243 useShim: true, 2244 setSize: false 2245 }, 2246 /** 2247 * @private 2248 * @method _setupResize 2249 * @description Creates the Resize instance and binds its events. 2250 */ 2251 _setupResize: function() { 2252 if (!YAHOO.util.DD || !YAHOO.util.Resize) { return false; } 2253 if (this.get('resize')) { 2254 var config = {}; 2255 Lang.augmentObject(config, this._resizeConfig); //Break the config reference 2256 this.resize = new YAHOO.util.Resize(this.get('element_cont').get('element'), config); 2257 this.resize.on('resize', function(args) { 2258 var anim = this.get('animate'); 2259 this.set('animate', false); 2260 this.set('width', args.width + 'px'); 2261 var h = args.height, 2262 th = (this.toolbar.get('element').clientHeight + 2), 2263 dh = 0; 2264 if (this.dompath) { 2265 dh = (this.dompath.clientHeight + 1); //It has a 1px top border.. 2266 } 2267 var newH = (h - th - dh); 2268 this.set('height', newH + 'px'); 2269 this.get('element_cont').setStyle('height', ''); 2270 this.set('animate', anim); 2271 }, this, true); 2272 } 2273 }, 2274 /** 2275 * @property resize 2276 * @description A reference to the Resize object 2277 * @type YAHOO.util.Resize 2278 */ 2279 resize: null, 2280 /** 2281 * @private 2282 * @method _setupDD 2283 * @description Sets up the DD instance used from the 'drag' config option. 2284 */ 2285 _setupDD: function() { 2286 if (!YAHOO.util.DD) { return false; } 2287 if (this.get('drag')) { 2288 var d = this.get('drag'), 2289 dd = YAHOO.util.DD; 2290 if (d === 'proxy') { 2291 dd = YAHOO.util.DDProxy; 2292 } 2293 2294 this.dd = new dd(this.get('element_cont').get('element')); 2295 this.toolbar.addClass('draggable'); 2296 this.dd.setHandleElId(this.toolbar._titlebar); 2297 } 2298 }, 2299 /** 2300 * @property dd 2301 * @description A reference to the DragDrop object. 2302 * @type YAHOO.util.DD/YAHOO.util.DDProxy 2303 */ 2304 dd: null, 2305 /** 2306 * @private 2307 * @property _lastCommand 2308 * @description A cache of the last execCommand (used for Undo/Redo so they don't mark an undo level) 2309 * @type String 2310 */ 2311 _lastCommand: null, 2312 _undoNodeChange: function() {}, 2313 _storeUndo: function() {}, 2314 /** 2315 * @private 2316 * @method _checkKey 2317 * @description Checks a keyMap entry against a key event 2318 * @param {Object} k The _keyMap object 2319 * @param {Event} e The Mouse Event 2320 * @return {Boolean} 2321 */ 2322 _checkKey: function(k, e) { 2323 var ret = false; 2324 if ((e.keyCode === k.key)) { 2325 if (k.mods && (k.mods.length > 0)) { 2326 var val = 0; 2327 for (var i = 0; i < k.mods.length; i++) { 2328 if (this.browser.mac) { 2329 if (k.mods[i] == 'ctrl') { 2330 k.mods[i] = 'meta'; 2331 } 2332 } 2333 if (e[k.mods[i] + 'Key'] === true) { 2334 val++; 2335 } 2336 } 2337 if (val === k.mods.length) { 2338 ret = true; 2339 } 2340 } else { 2341 ret = true; 2342 } 2343 } 2344 return ret; 2345 }, 2346 /** 2347 * @private 2348 * @property _keyMap 2349 * @description Named key maps for various actions in the Editor. Example: <code>CLOSE_WINDOW: { key: 87, mods: ['shift', 'ctrl'] }</code>. 2350 * 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. 2351 * @type {Object/Mixed} 2352 */ 2353 _keyMap: { 2354 SELECT_ALL: { 2355 key: 65, //A key 2356 mods: ['ctrl'] 2357 }, 2358 CLOSE_WINDOW: { 2359 key: 87, //W key 2360 mods: ['shift', 'ctrl'] 2361 }, 2362 FOCUS_TOOLBAR: { 2363 key: 27, 2364 mods: ['shift'] 2365 }, 2366 FOCUS_AFTER: { 2367 key: 27 2368 }, 2369 FONT_SIZE_UP: { 2370 key: 38, 2371 mods: ['shift', 'ctrl'] 2372 }, 2373 FONT_SIZE_DOWN: { 2374 key: 40, 2375 mods: ['shift', 'ctrl'] 2376 }, 2377 CREATE_LINK: { 2378 key: 76, 2379 mods: ['shift', 'ctrl'] 2380 }, 2381 BOLD: { 2382 key: 66, 2383 mods: ['shift', 'ctrl'] 2384 }, 2385 ITALIC: { 2386 key: 73, 2387 mods: ['shift', 'ctrl'] 2388 }, 2389 UNDERLINE: { 2390 key: 85, 2391 mods: ['shift', 'ctrl'] 2392 }, 2393 UNDO: { 2394 key: 90, 2395 mods: ['ctrl'] 2396 }, 2397 REDO: { 2398 key: 90, 2399 mods: ['shift', 'ctrl'] 2400 }, 2401 JUSTIFY_LEFT: { 2402 key: 219, 2403 mods: ['shift', 'ctrl'] 2404 }, 2405 JUSTIFY_CENTER: { 2406 key: 220, 2407 mods: ['shift', 'ctrl'] 2408 }, 2409 JUSTIFY_RIGHT: { 2410 key: 221, 2411 mods: ['shift', 'ctrl'] 2412 } 2413 }, 2414 /** 2415 * @private 2416 * @method _cleanClassName 2417 * @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s. 2418 * @param {String} str The classname to clean up 2419 * @return {String} 2420 */ 2421 _cleanClassName: function(str) { 2422 return str.replace(/ /g, '-').toLowerCase(); 2423 }, 2424 /** 2425 * @property _textarea 2426 * @description Flag to determine if we are using a textarea or an HTML Node. 2427 * @type Boolean 2428 */ 2429 _textarea: null, 2430 /** 2431 * @property _docType 2432 * @description The DOCTYPE to use in the editable container. 2433 * @type String 2434 */ 2435 _docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">', 2436 /** 2437 * @property editorDirty 2438 * @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. 2439 * @type Boolean 2440 */ 2441 editorDirty: null, 2442 /** 2443 * @property _defaultCSS 2444 * @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' } 2445 * @type String 2446 */ 2447 _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; }', 2448 /** 2449 * @property _defaultToolbar 2450 * @private 2451 * @description Default toolbar config. 2452 * @type Object 2453 */ 2454 _defaultToolbar: null, 2455 /** 2456 * @property _lastButton 2457 * @private 2458 * @description The last button pressed, so we don't disable it. 2459 * @type Object 2460 */ 2461 _lastButton: null, 2462 /** 2463 * @property _baseHREF 2464 * @private 2465 * @description The base location of the editable page (this page) so that relative paths for image work. 2466 * @type String 2467 */ 2468 _baseHREF: function() { 2469 var href = document.location.href; 2470 if (href.indexOf('?') !== -1) { //Remove the query string 2471 href = href.substring(0, href.indexOf('?')); 2472 } 2473 href = href.substring(0, href.lastIndexOf('/')) + '/'; 2474 return href; 2475 }(), 2476 /** 2477 * @property _lastImage 2478 * @private 2479 * @description Safari reference for the last image selected (for styling as selected). 2480 * @type HTMLElement 2481 */ 2482 _lastImage: null, 2483 /** 2484 * @property _blankImageLoaded 2485 * @private 2486 * @description Don't load the blank image more than once.. 2487 * @type Boolean 2488 */ 2489 _blankImageLoaded: null, 2490 /** 2491 * @property _fixNodesTimer 2492 * @private 2493 * @description Holder for the fixNodes timer 2494 * @type Date 2495 */ 2496 _fixNodesTimer: null, 2497 /** 2498 * @property _nodeChangeTimer 2499 * @private 2500 * @description Holds a reference to the nodeChange setTimeout call 2501 * @type Number 2502 */ 2503 _nodeChangeTimer: null, 2504 /** 2505 * @property _nodeChangeDelayTimer 2506 * @private 2507 * @description Holds a reference to the nodeChangeDelay setTimeout call 2508 * @type Number 2509 */ 2510 _nodeChangeDelayTimer: null, 2511 /** 2512 * @property _lastNodeChangeEvent 2513 * @private 2514 * @description Flag to determine the last event that fired a node change 2515 * @type Event 2516 */ 2517 _lastNodeChangeEvent: null, 2518 /** 2519 * @property _lastNodeChange 2520 * @private 2521 * @description Flag to determine when the last node change was fired 2522 * @type Date 2523 */ 2524 _lastNodeChange: 0, 2525 /** 2526 * @property _rendered 2527 * @private 2528 * @description Flag to determine if editor has been rendered or not 2529 * @type Boolean 2530 */ 2531 _rendered: null, 2532 /** 2533 * @property DOMReady 2534 * @private 2535 * @description Flag to determine if DOM is ready or not 2536 * @type Boolean 2537 */ 2538 DOMReady: null, 2539 /** 2540 * @property _selection 2541 * @private 2542 * @description Holder for caching iframe selections 2543 * @type Object 2544 */ 2545 _selection: null, 2546 /** 2547 * @property _mask 2548 * @private 2549 * @description DOM Element holder for the editor Mask when disabled 2550 * @type Object 2551 */ 2552 _mask: null, 2553 /** 2554 * @property _showingHiddenElements 2555 * @private 2556 * @description Status of the hidden elements button 2557 * @type Boolean 2558 */ 2559 _showingHiddenElements: null, 2560 /** 2561 * @property currentWindow 2562 * @description A reference to the currently open EditorWindow 2563 * @type Object 2564 */ 2565 currentWindow: null, 2566 /** 2567 * @property currentEvent 2568 * @description A reference to the current editor event 2569 * @type Event 2570 */ 2571 currentEvent: null, 2572 /** 2573 * @property operaEvent 2574 * @private 2575 * @description setTimeout holder for Opera and Image DoubleClick event.. 2576 * @type Object 2577 */ 2578 operaEvent: null, 2579 /** 2580 * @property currentFont 2581 * @description A reference to the last font selected from the Toolbar 2582 * @type HTMLElement 2583 */ 2584 currentFont: null, 2585 /** 2586 * @property currentElement 2587 * @description A reference to the current working element in the editor 2588 * @type Array 2589 */ 2590 currentElement: null, 2591 /** 2592 * @property dompath 2593 * @description A reference to the dompath container for writing the current working dom path to. 2594 * @type HTMLElement 2595 */ 2596 dompath: null, 2597 /** 2598 * @property beforeElement 2599 * @description A reference to the H2 placed before the editor for Accessibilty. 2600 * @type HTMLElement 2601 */ 2602 beforeElement: null, 2603 /** 2604 * @property afterElement 2605 * @description A reference to the H2 placed after the editor for Accessibilty. 2606 * @type HTMLElement 2607 */ 2608 afterElement: null, 2609 /** 2610 * @property invalidHTML 2611 * @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. 2612 * @type Object 2613 */ 2614 invalidHTML: { 2615 form: true, 2616 input: true, 2617 button: true, 2618 select: true, 2619 link: true, 2620 html: true, 2621 body: true, 2622 iframe: true, 2623 script: true, 2624 style: true, 2625 textarea: true 2626 }, 2627 /** 2628 * @property toolbar 2629 * @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance 2630 * @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> 2631 */ 2632 toolbar: null, 2633 /** 2634 * @private 2635 * @property _contentTimer 2636 * @description setTimeout holder for documentReady check 2637 */ 2638 _contentTimer: null, 2639 /** 2640 * @private 2641 * @property _contentTimerMax 2642 * @description The number of times the loaded content should be checked before giving up. Default: 500 2643 */ 2644 _contentTimerMax: 500, 2645 /** 2646 * @private 2647 * @property _contentTimerCounter 2648 * @description Counter to check the number of times the body is polled for before giving up 2649 * @type Number 2650 */ 2651 _contentTimerCounter: 0, 2652 /** 2653 * @private 2654 * @property _disabled 2655 * @description The Toolbar items that should be disabled if there is no selection present in the editor. 2656 * @type Array 2657 */ 2658 _disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ], 2659 /** 2660 * @private 2661 * @property _alwaysDisabled 2662 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor. 2663 * @type Object 2664 */ 2665 _alwaysDisabled: { undo: true, redo: true }, 2666 /** 2667 * @private 2668 * @property _alwaysEnabled 2669 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor. 2670 * @type Object 2671 */ 2672 _alwaysEnabled: { }, 2673 /** 2674 * @private 2675 * @property _semantic 2676 * @description The Toolbar commands that we should attempt to make tags out of instead of using styles. 2677 * @type Object 2678 */ 2679 _semantic: { 'bold': true, 'italic' : true, 'underline' : true }, 2680 /** 2681 * @private 2682 * @property _tag2cmd 2683 * @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button. 2684 * @type Object 2685 */ 2686 _tag2cmd: { 2687 'b': 'bold', 2688 'strong': 'bold', 2689 'i': 'italic', 2690 'em': 'italic', 2691 'u': 'underline', 2692 'sup': 'superscript', 2693 'sub': 'subscript', 2694 'img': 'insertimage', 2695 'a' : 'createlink', 2696 'ul' : 'insertunorderedlist', 2697 'ol' : 'insertorderedlist' 2698 }, 2699 2700 /** 2701 * @private _createIframe 2702 * @description Creates the DOM and YUI Element for the iFrame editor area. 2703 * @param {String} id The string ID to prefix the iframe with 2704 * @return {Object} iFrame object 2705 */ 2706 _createIframe: function() { 2707 var ifrmDom = document.createElement('iframe'); 2708 ifrmDom.id = this.get('id') + '_editor'; 2709 var config = { 2710 border: '0', 2711 frameBorder: '0', 2712 marginWidth: '0', 2713 marginHeight: '0', 2714 leftMargin: '0', 2715 topMargin: '0', 2716 allowTransparency: 'true', 2717 width: '100%' 2718 }; 2719 if (this.get('autoHeight')) { 2720 config.scrolling = 'no'; 2721 } 2722 for (var i in config) { 2723 if (Lang.hasOwnProperty(config, i)) { 2724 ifrmDom.setAttribute(i, config[i]); 2725 } 2726 } 2727 var isrc = 'javascript:;'; 2728 if (this.browser.ie) { 2729 //isrc = 'about:blank'; 2730 //TODO - Check this, I have changed it before.. 2731 isrc = 'javascript:false;'; 2732 } 2733 ifrmDom.setAttribute('src', isrc); 2734 var ifrm = new YAHOO.util.Element(ifrmDom); 2735 ifrm.setStyle('visibility', 'hidden'); 2736 return ifrm; 2737 }, 2738 /** 2739 * @private _isElement 2740 * @description Checks to see if an Element reference is a valid one and has a certain tag type 2741 * @param {HTMLElement} el The element to check 2742 * @param {String} tag The tag that the element needs to be 2743 * @return {Boolean} 2744 */ 2745 _isElement: function(el, tag) { 2746 if (el && el.tagName && (el.tagName.toLowerCase() == tag)) { 2747 return true; 2748 } 2749 if (el && el.getAttribute && (el.getAttribute('tag') == tag)) { 2750 return true; 2751 } 2752 return false; 2753 }, 2754 /** 2755 * @private _hasParent 2756 * @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type 2757 * @param {HTMLElement} el The element to check 2758 * @param {String} tag The tag that the element needs to be 2759 * @return HTMLElement 2760 */ 2761 _hasParent: function(el, tag) { 2762 if (!el || !el.parentNode) { 2763 return false; 2764 } 2765 2766 while (el.parentNode) { 2767 if (this._isElement(el, tag)) { 2768 return el; 2769 } 2770 if (el.parentNode) { 2771 el = el.parentNode; 2772 } else { 2773 return false; 2774 } 2775 } 2776 return false; 2777 }, 2778 /** 2779 * @private 2780 * @method _getDoc 2781 * @description Get the Document of the IFRAME 2782 * @return {Object} 2783 */ 2784 _getDoc: function() { 2785 var value = false; 2786 try { 2787 if (this.get('iframe').get('element').contentWindow.document) { 2788 value = this.get('iframe').get('element').contentWindow.document; 2789 return value; 2790 } 2791 } catch (e) { 2792 return false; 2793 } 2794 }, 2795 /** 2796 * @private 2797 * @method _getWindow 2798 * @description Get the Window of the IFRAME 2799 * @return {Object} 2800 */ 2801 _getWindow: function() { 2802 return this.get('iframe').get('element').contentWindow; 2803 }, 2804 /** 2805 * @method focus 2806 * @description Attempt to set the focus of the iframes window. 2807 */ 2808 focus: function() { 2809 this._getWindow().focus(); 2810 }, 2811 /** 2812 * @private 2813 * @depreciated - This should not be used, moved to this.focus(); 2814 * @method _focusWindow 2815 * @description Attempt to set the focus of the iframes window. 2816 */ 2817 _focusWindow: function() { 2818 this.focus(); 2819 }, 2820 /** 2821 * @private 2822 * @method _hasSelection 2823 * @description Determines if there is a selection in the editor document. 2824 * @return {Boolean} 2825 */ 2826 _hasSelection: function() { 2827 var sel = this._getSelection(); 2828 var range = this._getRange(); 2829 var hasSel = false; 2830 2831 if (!sel || !range) { 2832 return hasSel; 2833 } 2834 2835 //Internet Explorer 2836 if (this.browser.ie) { 2837 if (range.text) { 2838 hasSel = true; 2839 } 2840 if (range.html) { 2841 hasSel = true; 2842 } 2843 } else { 2844 if (this.browser.webkit) { 2845 if (sel+'' !== '') { 2846 hasSel = true; 2847 } 2848 } else { 2849 if (sel && (sel.toString() !== '') && (sel !== undefined)) { 2850 hasSel = true; 2851 } 2852 } 2853 } 2854 return hasSel; 2855 }, 2856 /** 2857 * @private 2858 * @method _getSelection 2859 * @description Handles the different selection objects across the A-Grade list. 2860 * @return {Object} Selection Object 2861 */ 2862 _getSelection: function() { 2863 var _sel = null; 2864 if (this._getDoc() && this._getWindow()) { 2865 if (this._getDoc().selection &&! this.browser.opera) { 2866 _sel = this._getDoc().selection; 2867 } else { 2868 _sel = this._getWindow().getSelection(); 2869 } 2870 //Handle Safari's lack of Selection Object 2871 if (this.browser.webkit) { 2872 if (_sel.baseNode) { 2873 this._selection = {}; 2874 this._selection.baseNode = _sel.baseNode; 2875 this._selection.baseOffset = _sel.baseOffset; 2876 this._selection.extentNode = _sel.extentNode; 2877 this._selection.extentOffset = _sel.extentOffset; 2878 } else if (this._selection !== null) { 2879 _sel = this._getWindow().getSelection(); 2880 _sel.setBaseAndExtent( 2881 this._selection.baseNode, 2882 this._selection.baseOffset, 2883 this._selection.extentNode, 2884 this._selection.extentOffset); 2885 this._selection = null; 2886 } 2887 } 2888 } 2889 return _sel; 2890 }, 2891 /** 2892 * @private 2893 * @method _selectNode 2894 * @description Places the highlight around a given node 2895 * @param {HTMLElement} node The node to select 2896 */ 2897 _selectNode: function(node, collapse) { 2898 if (!node) { 2899 return false; 2900 } 2901 var sel = this._getSelection(), 2902 range = null; 2903 2904 if (this.browser.ie) { 2905 try { //IE freaks out here sometimes.. 2906 range = this._getDoc().body.createTextRange(); 2907 range.moveToElementText(node); 2908 range.select(); 2909 } catch (e) { 2910 } 2911 } else if (this.browser.webkit) { 2912 if (collapse) { 2913 sel.setBaseAndExtent(node, 1, node, node.innerText.length); 2914 } else { 2915 sel.setBaseAndExtent(node, 0, node, node.innerText.length); 2916 } 2917 } else if (this.browser.opera) { 2918 sel = this._getWindow().getSelection(); 2919 range = this._getDoc().createRange(); 2920 range.selectNode(node); 2921 sel.removeAllRanges(); 2922 sel.addRange(range); 2923 } else { 2924 range = this._getDoc().createRange(); 2925 range.selectNodeContents(node); 2926 sel.removeAllRanges(); 2927 sel.addRange(range); 2928 } 2929 //TODO - Check Performance 2930 this.nodeChange(); 2931 }, 2932 /** 2933 * @private 2934 * @method _getRange 2935 * @description Handles the different range objects across the A-Grade list. 2936 * @return {Object} Range Object 2937 */ 2938 _getRange: function() { 2939 var sel = this._getSelection(); 2940 2941 if (sel === null) { 2942 return null; 2943 } 2944 2945 if (this.browser.webkit && !sel.getRangeAt) { 2946 var _range = this._getDoc().createRange(); 2947 try { 2948 _range.setStart(sel.anchorNode, sel.anchorOffset); 2949 _range.setEnd(sel.focusNode, sel.focusOffset); 2950 } catch (e) { 2951 _range = this._getWindow().getSelection()+''; 2952 } 2953 return _range; 2954 } 2955 2956 if (this.browser.ie) { 2957 try { 2958 return sel.createRange(); 2959 } catch (e2) { 2960 return null; 2961 } 2962 } 2963 2964 if (sel.rangeCount > 0) { 2965 return sel.getRangeAt(0); 2966 } 2967 return null; 2968 }, 2969 /** 2970 * @private 2971 * @method _setDesignMode 2972 * @description Sets the designMode property of the iFrame document's body. 2973 * @param {String} state This should be either on or off 2974 */ 2975 _setDesignMode: function(state) { 2976 if (this.get('setDesignMode')) { 2977 try { 2978 this._getDoc().designMode = ((state.toLowerCase() == 'off') ? 'off' : 'on'); 2979 } catch(e) { } 2980 } 2981 }, 2982 /** 2983 * @private 2984 * @method _toggleDesignMode 2985 * @description Toggles the designMode property of the iFrame document on and off. 2986 * @return {String} The state that it was set to. 2987 */ 2988 _toggleDesignMode: function() { 2989 var _dMode = this._getDoc().designMode, 2990 _state = ((_dMode.toLowerCase() == 'on') ? 'off' : 'on'); 2991 this._setDesignMode(_state); 2992 return _state; 2993 }, 2994 /** 2995 * @private 2996 * @property _focused 2997 * @description Holder for trapping focus/blur state and prevent double events 2998 * @type Boolean 2999 */ 3000 _focused: null, 3001 /** 3002 * @private 3003 * @method _handleFocus 3004 * @description Handles the focus of the iframe. Note, this is window focus event, not an Editor focus event. 3005 * @param {Event} e The DOM Event 3006 */ 3007 _handleFocus: function(e) { 3008 if (!this._focused) { 3009 this._focused = true; 3010 this.fireEvent('editorWindowFocus', { type: 'editorWindowFocus', target: this }); 3011 } 3012 }, 3013 /** 3014 * @private 3015 * @method _handleBlur 3016 * @description Handles the blur of the iframe. Note, this is window blur event, not an Editor blur event. 3017 * @param {Event} e The DOM Event 3018 */ 3019 _handleBlur: function(e) { 3020 if (this._focused) { 3021 this._focused = false; 3022 this.fireEvent('editorWindowBlur', { type: 'editorWindowBlur', target: this }); 3023 } 3024 }, 3025 /** 3026 * @private 3027 * @method _initEditorEvents 3028 * @description This method sets up the listeners on the Editors document. 3029 */ 3030 _initEditorEvents: function() { 3031 //Setup Listeners on iFrame 3032 var doc = this._getDoc(), 3033 win = this._getWindow(); 3034 3035 Event.on(doc, 'mouseup', this._handleMouseUp, this, true); 3036 Event.on(doc, 'mousedown', this._handleMouseDown, this, true); 3037 Event.on(doc, 'click', this._handleClick, this, true); 3038 Event.on(doc, 'dblclick', this._handleDoubleClick, this, true); 3039 Event.on(doc, 'keypress', this._handleKeyPress, this, true); 3040 Event.on(doc, 'keyup', this._handleKeyUp, this, true); 3041 Event.on(doc, 'keydown', this._handleKeyDown, this, true); 3042 /* TODO -- Everyone but Opera works here.. 3043 Event.on(doc, 'paste', function() { 3044 }, this, true); 3045 */ 3046 3047 //Focus and blur.. 3048 Event.on(win, 'focus', this._handleFocus, this, true); 3049 Event.on(win, 'blur', this._handleBlur, this, true); 3050 }, 3051 /** 3052 * @private 3053 * @method _removeEditorEvents 3054 * @description This method removes the listeners on the Editors document (for disabling). 3055 */ 3056 _removeEditorEvents: function() { 3057 //Remove Listeners on iFrame 3058 var doc = this._getDoc(), 3059 win = this._getWindow(); 3060 3061 Event.removeListener(doc, 'mouseup', this._handleMouseUp, this, true); 3062 Event.removeListener(doc, 'mousedown', this._handleMouseDown, this, true); 3063 Event.removeListener(doc, 'click', this._handleClick, this, true); 3064 Event.removeListener(doc, 'dblclick', this._handleDoubleClick, this, true); 3065 Event.removeListener(doc, 'keypress', this._handleKeyPress, this, true); 3066 Event.removeListener(doc, 'keyup', this._handleKeyUp, this, true); 3067 Event.removeListener(doc, 'keydown', this._handleKeyDown, this, true); 3068 3069 //Focus and blur.. 3070 Event.removeListener(win, 'focus', this._handleFocus, this, true); 3071 Event.removeListener(win, 'blur', this._handleBlur, this, true); 3072 }, 3073 _fixWebkitDivs: function() { 3074 if (this.browser.webkit) { 3075 var divs = this._getDoc().body.getElementsByTagName('div'); 3076 Dom.addClass(divs, 'yui-wk-div'); 3077 } 3078 }, 3079 /** 3080 * @private 3081 * @method _initEditor 3082 * @param {Boolean} raw Don't add events. 3083 * @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners. 3084 */ 3085 _initEditor: function(raw) { 3086 if (this._editorInit) { 3087 return; 3088 } 3089 this._editorInit = true; 3090 if (this.browser.ie) { 3091 this._getDoc().body.style.margin = '0'; 3092 } 3093 if (!this.get('disabled')) { 3094 this._setDesignMode('on'); 3095 this._contentTimerCounter = 0; 3096 } 3097 if (!this._getDoc().body) { 3098 this._contentTimerCounter = 0; 3099 this._editorInit = false; 3100 this._checkLoaded(); 3101 return false; 3102 } 3103 3104 if (!raw) { 3105 this.toolbar.on('buttonClick', this._handleToolbarClick, this, true); 3106 } 3107 if (!this.get('disabled')) { 3108 this._initEditorEvents(); 3109 this.toolbar.set('disabled', false); 3110 } 3111 3112 if (raw) { 3113 this.fireEvent('editorContentReloaded', { type: 'editorreloaded', target: this }); 3114 } else { 3115 this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this }); 3116 } 3117 this._fixWebkitDivs(); 3118 if (this.get('dompath')) { 3119 var self = this; 3120 setTimeout(function() { 3121 self._writeDomPath.call(self); 3122 self._setupResize.call(self); 3123 }, 150); 3124 } 3125 var br = []; 3126 for (var i in this.browser) { 3127 if (this.browser[i]) { 3128 br.push(i); 3129 } 3130 } 3131 if (this.get('ptags')) { 3132 br.push('ptags'); 3133 } 3134 Dom.addClass(this._getDoc().body, br.join(' ')); 3135 this.nodeChange(true); 3136 }, 3137 /** 3138 * @private 3139 * @method _checkLoaded 3140 * @param {Boolean} raw Don't add events. 3141 * @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor. 3142 */ 3143 _checkLoaded: function(raw) { 3144 this._editorInit = false; 3145 this._contentTimerCounter++; 3146 if (this._contentTimer) { 3147 clearTimeout(this._contentTimer); 3148 } 3149 if (this._contentTimerCounter > this._contentTimerMax) { 3150 return false; 3151 } 3152 var init = false; 3153 try { 3154 if (this._getDoc() && this._getDoc().body) { 3155 if (this.browser.ie) { 3156 if (this._getDoc().body.readyState == 'complete') { 3157 init = true; 3158 } 3159 } else { 3160 if (this._getDoc().body._rteLoaded === true) { 3161 init = true; 3162 } 3163 } 3164 } 3165 } catch (e) { 3166 init = false; 3167 } 3168 3169 if (init === true) { 3170 //The onload event has fired, clean up after ourselves and fire the _initEditor method 3171 this._initEditor(raw); 3172 } else { 3173 var self = this; 3174 this._contentTimer = setTimeout(function() { 3175 self._checkLoaded.call(self, raw); 3176 }, 20); 3177 } 3178 }, 3179 /** 3180 * @private 3181 * @method _setInitialContent 3182 * @param {Boolean} raw Don't add events. 3183 * @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking. 3184 */ 3185 _setInitialContent: function(raw) { 3186 3187 var value = ((this._textarea) ? this.get('element').value : this.get('element').innerHTML), 3188 doc = null; 3189 3190 if (value === '') { 3191 value = '<br>'; 3192 } 3193 3194 var html = Lang.substitute(this.get('html'), { 3195 TITLE: this.STR_TITLE, 3196 CONTENT: this._cleanIncomingHTML(value), 3197 CSS: this.get('css'), 3198 HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'), 3199 EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */') 3200 }), 3201 check = true; 3202 3203 html = html.replace(/RIGHT_BRACKET/gi, '{'); 3204 html = html.replace(/LEFT_BRACKET/gi, '}'); 3205 3206 if (document.compatMode != 'BackCompat') { 3207 html = this._docType + "\n" + html; 3208 } else { 3209 } 3210 3211 if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) { 3212 //Firefox 1.5 doesn't like setting designMode on an document created with a data url 3213 try { 3214 //Adobe AIR Code 3215 if (this.browser.air) { 3216 doc = this._getDoc().implementation.createHTMLDocument(); 3217 var origDoc = this._getDoc(); 3218 origDoc.open(); 3219 origDoc.close(); 3220 doc.open(); 3221 doc.write(html); 3222 doc.close(); 3223 var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true); 3224 origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]); 3225 origDoc.body._rteLoaded = true; 3226 } else { 3227 doc = this._getDoc(); 3228 doc.open(); 3229 doc.write(html); 3230 doc.close(); 3231 } 3232 } catch (e) { 3233 //Safari will only be here if we are hidden 3234 check = false; 3235 } 3236 } else { 3237 //This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality 3238 this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html); 3239 } 3240 this.get('iframe').setStyle('visibility', ''); 3241 if (check) { 3242 this._checkLoaded(raw); 3243 } 3244 }, 3245 /** 3246 * @private 3247 * @method _setMarkupType 3248 * @param {String} action The action to take. Possible values are: css, default or semantic 3249 * @description This method will turn on/off the useCSS execCommand. 3250 */ 3251 _setMarkupType: function(action) { 3252 switch (this.get('markup')) { 3253 case 'css': 3254 this._setEditorStyle(true); 3255 break; 3256 case 'default': 3257 this._setEditorStyle(false); 3258 break; 3259 case 'semantic': 3260 case 'xhtml': 3261 if (this._semantic[action]) { 3262 this._setEditorStyle(false); 3263 } else { 3264 this._setEditorStyle(true); 3265 } 3266 break; 3267 } 3268 }, 3269 /** 3270 * Set the editor to use CSS instead of HTML 3271 * @param {Booleen} stat True/False 3272 */ 3273 _setEditorStyle: function(stat) { 3274 try { 3275 this._getDoc().execCommand('useCSS', false, !stat); 3276 } catch (ex) { 3277 } 3278 }, 3279 /** 3280 * @private 3281 * @method _getSelectedElement 3282 * @description This method will attempt to locate the element that was last interacted with, either via selection, location or event. 3283 * @return {HTMLElement} The currently selected element. 3284 */ 3285 _getSelectedElement: function() { 3286 var doc = this._getDoc(), 3287 range = null, 3288 sel = null, 3289 elm = null, 3290 check = true; 3291 3292 if (this.browser.ie) { 3293 this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event; 3294 range = this._getRange(); 3295 if (range) { 3296 elm = range.item ? range.item(0) : range.parentElement(); 3297 if (this._hasSelection()) { 3298 //TODO 3299 //WTF.. Why can't I get an element reference here?!??! 3300 } 3301 if (elm === doc.body) { 3302 elm = null; 3303 } 3304 } 3305 if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) { 3306 elm = Event.getTarget(this.currentEvent); 3307 } 3308 } else { 3309 sel = this._getSelection(); 3310 range = this._getRange(); 3311 3312 if (!sel || !range) { 3313 return null; 3314 } 3315 //TODO 3316 if (!this._hasSelection() && this.browser.webkit3) { 3317 //check = false; 3318 } 3319 if (this.browser.gecko) { 3320 //Added in 2.6.0 3321 if (range.startContainer) { 3322 if (range.startContainer.nodeType === 3) { 3323 elm = range.startContainer.parentNode; 3324 } else if (range.startContainer.nodeType === 1) { 3325 elm = range.startContainer; 3326 } 3327 //Added in 2.7.0 3328 if (this.currentEvent) { 3329 var tar = Event.getTarget(this.currentEvent); 3330 if (!this._isElement(tar, 'html')) { 3331 if (elm !== tar) { 3332 elm = tar; 3333 } 3334 } 3335 } 3336 } 3337 } 3338 3339 if (check) { 3340 if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) { 3341 if (sel.anchorNode.parentNode) { //next check parentNode 3342 elm = sel.anchorNode.parentNode; 3343 } 3344 if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) { 3345 elm = sel.anchorNode.nextSibling; 3346 } 3347 } 3348 if (this._isElement(elm, 'br')) { 3349 elm = null; 3350 } 3351 if (!elm) { 3352 elm = range.commonAncestorContainer; 3353 if (!range.collapsed) { 3354 if (range.startContainer == range.endContainer) { 3355 if (range.startOffset - range.endOffset < 2) { 3356 if (range.startContainer.hasChildNodes()) { 3357 elm = range.startContainer.childNodes[range.startOffset]; 3358 } 3359 } 3360 } 3361 } 3362 } 3363 } 3364 } 3365 3366 if (this.currentEvent !== null) { 3367 try { 3368 switch (this.currentEvent.type) { 3369 case 'click': 3370 case 'mousedown': 3371 case 'mouseup': 3372 if (this.browser.webkit) { 3373 elm = Event.getTarget(this.currentEvent); 3374 } 3375 break; 3376 default: 3377 //Do nothing 3378 break; 3379 } 3380 } catch (e) { 3381 } 3382 } else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) { 3383 //TODO is this still needed? 3384 //elm = this.currentElement[0]; 3385 } 3386 3387 3388 if (this.browser.opera || this.browser.webkit) { 3389 if (this.currentEvent && !elm) { 3390 elm = YAHOO.util.Event.getTarget(this.currentEvent); 3391 } 3392 } 3393 if (!elm || !elm.tagName) { 3394 elm = doc.body; 3395 } 3396 if (this._isElement(elm, 'html')) { 3397 //Safari sometimes gives us the HTML node back.. 3398 elm = doc.body; 3399 } 3400 if (this._isElement(elm, 'body')) { 3401 //make sure that body means this body not the parent.. 3402 elm = doc.body; 3403 } 3404 if (elm && !elm.parentNode) { //Not in document 3405 elm = doc.body; 3406 } 3407 if (elm === undefined) { 3408 elm = null; 3409 } 3410 return elm; 3411 }, 3412 /** 3413 * @private 3414 * @method _getDomPath 3415 * @description This method will attempt to build the DOM path from the currently selected element. 3416 * @param HTMLElement el The element to start with, if not provided _getSelectedElement is used 3417 * @return {Array} An array of node references that will create the DOM Path. 3418 */ 3419 _getDomPath: function(el) { 3420 if (!el) { 3421 el = this._getSelectedElement(); 3422 } 3423 var domPath = []; 3424 while (el !== null) { 3425 if (el.ownerDocument != this._getDoc()) { 3426 el = null; 3427 break; 3428 } 3429 //Check to see if we get el.nodeName and nodeType 3430 if (el.nodeName && el.nodeType && (el.nodeType == 1)) { 3431 domPath[domPath.length] = el; 3432 } 3433 3434 if (this._isElement(el, 'body')) { 3435 break; 3436 } 3437 3438 el = el.parentNode; 3439 } 3440 if (domPath.length === 0) { 3441 if (this._getDoc() && this._getDoc().body) { 3442 domPath[0] = this._getDoc().body; 3443 } 3444 } 3445 return domPath.reverse(); 3446 }, 3447 /** 3448 * @private 3449 * @method _writeDomPath 3450 * @description Write the current DOM path out to the dompath container below the editor. 3451 */ 3452 _writeDomPath: function() { 3453 var path = this._getDomPath(), 3454 pathArr = [], 3455 classPath = '', 3456 pathStr = ''; 3457 3458 for (var i = 0; i < path.length; i++) { 3459 var tag = path[i].tagName.toLowerCase(); 3460 if ((tag == 'ol') && (path[i].type)) { 3461 tag += ':' + path[i].type; 3462 } 3463 if (Dom.hasClass(path[i], 'yui-tag')) { 3464 tag = path[i].getAttribute('tag'); 3465 } 3466 if ((this.get('markup') == 'semantic') || (this.get('markup') == 'xhtml')) { 3467 switch (tag) { 3468 case 'b': tag = 'strong'; break; 3469 case 'i': tag = 'em'; break; 3470 } 3471 } 3472 if (!Dom.hasClass(path[i], 'yui-non')) { 3473 if (Dom.hasClass(path[i], 'yui-tag')) { 3474 pathStr = tag; 3475 } else { 3476 classPath = ((path[i].className !== '') ? '.' + path[i].className.replace(/ /g, '.') : ''); 3477 if ((classPath.indexOf('yui') != -1) || (classPath.toLowerCase().indexOf('apple-style-span') != -1)) { 3478 classPath = ''; 3479 } 3480 pathStr = tag + ((path[i].id) ? '#' + path[i].id : '') + classPath; 3481 } 3482 switch (tag) { 3483 case 'body': 3484 pathStr = 'body'; 3485 break; 3486 case 'a': 3487 if (path[i].getAttribute('href', 2)) { 3488 pathStr += ':' + path[i].getAttribute('href', 2).replace('mailto:', '').replace('http:/'+'/', '').replace('https:/'+'/', ''); //May need to add others here ftp 3489 } 3490 break; 3491 case 'img': 3492 var h = path[i].height; 3493 var w = path[i].width; 3494 if (path[i].style.height) { 3495 h = parseInt(path[i].style.height, 10); 3496 } 3497 if (path[i].style.width) { 3498 w = parseInt(path[i].style.width, 10); 3499 } 3500 pathStr += '(' + w + 'x' + h + ')'; 3501 break; 3502 } 3503 3504 if (pathStr.length > 10) { 3505 pathStr = '<span title="' + pathStr + '">' + pathStr.substring(0, 10) + '...' + '</span>'; 3506 } else { 3507 pathStr = '<span title="' + pathStr + '">' + pathStr + '</span>'; 3508 } 3509 pathArr[pathArr.length] = pathStr; 3510 } 3511 } 3512 var str = pathArr.join(' ' + this.SEP_DOMPATH + ' '); 3513 //Prevent flickering 3514 if (this.dompath.innerHTML != str) { 3515 this.dompath.innerHTML = str; 3516 } 3517 }, 3518 /** 3519 * @private 3520 * @method _fixNodes 3521 * @description Fix href and imgs as well as remove invalid HTML. 3522 */ 3523 _fixNodes: function() { 3524 try { 3525 var doc = this._getDoc(), 3526 els = []; 3527 3528 for (var v in this.invalidHTML) { 3529 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) { 3530 if (v.toLowerCase() != 'span') { 3531 var tags = doc.body.getElementsByTagName(v); 3532 if (tags.length) { 3533 for (var i = 0; i < tags.length; i++) { 3534 els.push(tags[i]); 3535 } 3536 } 3537 } 3538 } 3539 } 3540 for (var h = 0; h < els.length; h++) { 3541 if (els[h].parentNode) { 3542 if (Lang.isObject(this.invalidHTML[els[h].tagName.toLowerCase()]) && this.invalidHTML[els[h].tagName.toLowerCase()].keepContents) { 3543 this._swapEl(els[h], 'span', function(el) { 3544 el.className = 'yui-non'; 3545 }); 3546 } else { 3547 els[h].parentNode.removeChild(els[h]); 3548 } 3549 } 3550 } 3551 var imgs = this._getDoc().getElementsByTagName('img'); 3552 Dom.addClass(imgs, 'yui-img'); 3553 } catch(e) {} 3554 }, 3555 /** 3556 * @private 3557 * @method _isNonEditable 3558 * @param Event ev The Dom event being checked 3559 * @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. 3560 * 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 3561 * disable and enable the Editor's toolbar based on the noedit state. 3562 * @return Boolean 3563 */ 3564 _isNonEditable: function(ev) { 3565 if (this.get('allowNoEdit')) { 3566 var el = Event.getTarget(ev); 3567 if (this._isElement(el, 'html')) { 3568 el = null; 3569 } 3570 var path = this._getDomPath(el); 3571 for (var i = (path.length - 1); i > -1; i--) { 3572 if (Dom.hasClass(path[i], this.CLASS_NOEDIT)) { 3573 //if (this.toolbar.get('disabled') === false) { 3574 // this.toolbar.set('disabled', true); 3575 //} 3576 try { 3577 this._getDoc().execCommand('enableObjectResizing', false, 'false'); 3578 } catch (e) {} 3579 this.nodeChange(); 3580 Event.stopEvent(ev); 3581 return true; 3582 } 3583 } 3584 //if (this.toolbar.get('disabled') === true) { 3585 //Should only happen once.. 3586 //this.toolbar.set('disabled', false); 3587 try { 3588 this._getDoc().execCommand('enableObjectResizing', false, 'true'); 3589 } catch (e2) {} 3590 //} 3591 } 3592 return false; 3593 }, 3594 /** 3595 * @private 3596 * @method _setCurrentEvent 3597 * @param {Event} ev The event to cache 3598 * @description Sets the current event property 3599 */ 3600 _setCurrentEvent: function(ev) { 3601 this.currentEvent = ev; 3602 }, 3603 /** 3604 * @private 3605 * @method _handleClick 3606 * @param {Event} ev The event we are working on. 3607 * @description Handles all click events inside the iFrame document. 3608 */ 3609 _handleClick: function(ev) { 3610 var ret = this.fireEvent('beforeEditorClick', { type: 'beforeEditorClick', target: this, ev: ev }); 3611 if (ret === false) { 3612 return false; 3613 } 3614 if (this._isNonEditable(ev)) { 3615 return false; 3616 } 3617 this._setCurrentEvent(ev); 3618 if (this.currentWindow) { 3619 this.closeWindow(); 3620 } 3621 if (this.currentWindow) { 3622 this.closeWindow(); 3623 } 3624 if (this.browser.webkit) { 3625 var tar =Event.getTarget(ev); 3626 if (this._isElement(tar, 'a') || this._isElement(tar.parentNode, 'a')) { 3627 Event.stopEvent(ev); 3628 this.nodeChange(); 3629 } 3630 } else { 3631 this.nodeChange(); 3632 } 3633 this.fireEvent('editorClick', { type: 'editorClick', target: this, ev: ev }); 3634 }, 3635 /** 3636 * @private 3637 * @method _handleMouseUp 3638 * @param {Event} ev The event we are working on. 3639 * @description Handles all mouseup events inside the iFrame document. 3640 */ 3641 _handleMouseUp: function(ev) { 3642 var ret = this.fireEvent('beforeEditorMouseUp', { type: 'beforeEditorMouseUp', target: this, ev: ev }); 3643 if (ret === false) { 3644 return false; 3645 } 3646 if (this._isNonEditable(ev)) { 3647 return false; 3648 } 3649 //Don't set current event for mouseup. 3650 //It get's fired after a menu is closed and gives up a bogus event to work with 3651 //this._setCurrentEvent(ev); 3652 var self = this; 3653 if (this.browser.opera) { 3654 /* 3655 * @knownissue Opera appears to stop the MouseDown, Click and DoubleClick events on an image inside of a document with designMode on.. 3656 * @browser Opera 3657 * @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. 3658 */ 3659 var sel = Event.getTarget(ev); 3660 if (this._isElement(sel, 'img')) { 3661 this.nodeChange(); 3662 if (this.operaEvent) { 3663 clearTimeout(this.operaEvent); 3664 this.operaEvent = null; 3665 this._handleDoubleClick(ev); 3666 } else { 3667 this.operaEvent = window.setTimeout(function() { 3668 self.operaEvent = false; 3669 }, 700); 3670 } 3671 } 3672 } 3673 //This will stop Safari from selecting the entire document if you select all the text in the editor 3674 if (this.browser.webkit || this.browser.opera) { 3675 if (this.browser.webkit) { 3676 Event.stopEvent(ev); 3677 } 3678 } 3679 this.nodeChange(); 3680 this.fireEvent('editorMouseUp', { type: 'editorMouseUp', target: this, ev: ev }); 3681 }, 3682 /** 3683 * @private 3684 * @method _handleMouseDown 3685 * @param {Event} ev The event we are working on. 3686 * @description Handles all mousedown events inside the iFrame document. 3687 */ 3688 _handleMouseDown: function(ev) { 3689 var ret = this.fireEvent('beforeEditorMouseDown', { type: 'beforeEditorMouseDown', target: this, ev: ev }); 3690 if (ret === false) { 3691 return false; 3692 } 3693 if (this._isNonEditable(ev)) { 3694 return false; 3695 } 3696 this._setCurrentEvent(ev); 3697 var sel = Event.getTarget(ev); 3698 if (this.browser.webkit && this._hasSelection()) { 3699 var _sel = this._getSelection(); 3700 if (!this.browser.webkit3) { 3701 _sel.collapse(true); 3702 } else { 3703 _sel.collapseToStart(); 3704 } 3705 } 3706 if (this.browser.webkit && this._lastImage) { 3707 Dom.removeClass(this._lastImage, 'selected'); 3708 this._lastImage = null; 3709 } 3710 if (this._isElement(sel, 'img') || this._isElement(sel, 'a')) { 3711 if (this.browser.webkit) { 3712 Event.stopEvent(ev); 3713 if (this._isElement(sel, 'img')) { 3714 Dom.addClass(sel, 'selected'); 3715 this._lastImage = sel; 3716 } 3717 } 3718 if (this.currentWindow) { 3719 this.closeWindow(); 3720 } 3721 this.nodeChange(); 3722 } 3723 this.fireEvent('editorMouseDown', { type: 'editorMouseDown', target: this, ev: ev }); 3724 }, 3725 /** 3726 * @private 3727 * @method _handleDoubleClick 3728 * @param {Event} ev The event we are working on. 3729 * @description Handles all doubleclick events inside the iFrame document. 3730 */ 3731 _handleDoubleClick: function(ev) { 3732 var ret = this.fireEvent('beforeEditorDoubleClick', { type: 'beforeEditorDoubleClick', target: this, ev: ev }); 3733 if (ret === false) { 3734 return false; 3735 } 3736 if (this._isNonEditable(ev)) { 3737 return false; 3738 } 3739 this._setCurrentEvent(ev); 3740 var sel = Event.getTarget(ev); 3741 if (this._isElement(sel, 'img')) { 3742 this.currentElement[0] = sel; 3743 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar }); 3744 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3745 } else if (this._hasParent(sel, 'a')) { //Handle elements inside an a 3746 this.currentElement[0] = this._hasParent(sel, 'a'); 3747 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar }); 3748 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3749 } 3750 this.nodeChange(); 3751 this.fireEvent('editorDoubleClick', { type: 'editorDoubleClick', target: this, ev: ev }); 3752 }, 3753 /** 3754 * @private 3755 * @method _handleKeyUp 3756 * @param {Event} ev The event we are working on. 3757 * @description Handles all keyup events inside the iFrame document. 3758 */ 3759 _handleKeyUp: function(ev) { 3760 var ret = this.fireEvent('beforeEditorKeyUp', { type: 'beforeEditorKeyUp', target: this, ev: ev }); 3761 if (ret === false) { 3762 return false; 3763 } 3764 if (this._isNonEditable(ev)) { 3765 return false; 3766 } 3767 this._storeUndo(); 3768 this._setCurrentEvent(ev); 3769 switch (ev.keyCode) { 3770 case this._keyMap.SELECT_ALL.key: 3771 if (this._checkKey(this._keyMap.SELECT_ALL, ev)) { 3772 this.nodeChange(); 3773 } 3774 break; 3775 case 32: //Space Bar 3776 case 35: //End 3777 case 36: //Home 3778 case 37: //Left Arrow 3779 case 38: //Up Arrow 3780 case 39: //Right Arrow 3781 case 40: //Down Arrow 3782 case 46: //Forward Delete 3783 case 8: //Delete 3784 case this._keyMap.CLOSE_WINDOW.key: //W key if window is open 3785 if ((ev.keyCode == this._keyMap.CLOSE_WINDOW.key) && this.currentWindow) { 3786 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) { 3787 this.closeWindow(); 3788 } 3789 } else { 3790 if (!this.browser.ie) { 3791 if (this._nodeChangeTimer) { 3792 clearTimeout(this._nodeChangeTimer); 3793 } 3794 var self = this; 3795 this._nodeChangeTimer = setTimeout(function() { 3796 self._nodeChangeTimer = null; 3797 self.nodeChange.call(self); 3798 }, 100); 3799 } else { 3800 this.nodeChange(); 3801 } 3802 this.editorDirty = true; 3803 } 3804 break; 3805 } 3806 this.fireEvent('editorKeyUp', { type: 'editorKeyUp', target: this, ev: ev }); 3807 }, 3808 /** 3809 * @private 3810 * @method _handleKeyPress 3811 * @param {Event} ev The event we are working on. 3812 * @description Handles all keypress events inside the iFrame document. 3813 */ 3814 _handleKeyPress: function(ev) { 3815 var ret = this.fireEvent('beforeEditorKeyPress', { type: 'beforeEditorKeyPress', target: this, ev: ev }); 3816 if (ret === false) { 3817 return false; 3818 } 3819 3820 if (this.get('allowNoEdit')) { 3821 //if (ev && ev.keyCode && ((ev.keyCode == 46) || ev.keyCode == 63272)) { 3822 if (ev && ev.keyCode && (ev.keyCode == 63272)) { 3823 //Forward delete key 3824 Event.stopEvent(ev); 3825 } 3826 } 3827 if (this._isNonEditable(ev)) { 3828 return false; 3829 } 3830 this._setCurrentEvent(ev); 3831 this._storeUndo(); 3832 if (this.browser.opera) { 3833 if (ev.keyCode === 13) { 3834 var tar = this._getSelectedElement(); 3835 if (!this._isElement(tar, 'li')) { 3836 this.execCommand('inserthtml', '<br>'); 3837 Event.stopEvent(ev); 3838 } 3839 } 3840 } 3841 if (this.browser.webkit) { 3842 if (!this.browser.webkit3) { 3843 if (ev.keyCode && (ev.keyCode == 122) && (ev.metaKey)) { 3844 //This is CMD + z (for undo) 3845 if (this._hasParent(this._getSelectedElement(), 'li')) { 3846 Event.stopEvent(ev); 3847 } 3848 } 3849 } 3850 this._listFix(ev); 3851 } 3852 this._fixListDupIds(); 3853 this.fireEvent('editorKeyPress', { type: 'editorKeyPress', target: this, ev: ev }); 3854 }, 3855 /** 3856 * @private 3857 * @method _handleKeyDown 3858 * @param {Event} ev The event we are working on. 3859 * @description Handles all keydown events inside the iFrame document. 3860 */ 3861 _handleKeyDown: function(ev) { 3862 var ret = this.fireEvent('beforeEditorKeyDown', { type: 'beforeEditorKeyDown', target: this, ev: ev }); 3863 if (ret === false) { 3864 return false; 3865 } 3866 var tar = null, _range = null; 3867 if (this._isNonEditable(ev)) { 3868 return false; 3869 } 3870 this._setCurrentEvent(ev); 3871 if (this.currentWindow) { 3872 this.closeWindow(); 3873 } 3874 if (this.currentWindow) { 3875 this.closeWindow(); 3876 } 3877 var doExec = false, 3878 action = null, 3879 value = null, 3880 exec = false; 3881 3882 3883 switch (ev.keyCode) { 3884 case this._keyMap.FOCUS_TOOLBAR.key: 3885 if (this._checkKey(this._keyMap.FOCUS_TOOLBAR, ev)) { 3886 var h = this.toolbar.getElementsByTagName('h2')[0]; 3887 if (h && h.firstChild) { 3888 h.firstChild.focus(); 3889 } 3890 } else if (this._checkKey(this._keyMap.FOCUS_AFTER, ev)) { 3891 //Focus After Element - Esc 3892 this.afterElement.focus(); 3893 } 3894 Event.stopEvent(ev); 3895 doExec = false; 3896 break; 3897 //case 76: //L 3898 case this._keyMap.CREATE_LINK.key: //L 3899 if (this._hasSelection()) { 3900 if (this._checkKey(this._keyMap.CREATE_LINK, ev)) { 3901 var makeLink = true; 3902 if (this.get('limitCommands')) { 3903 if (!this.toolbar.getButtonByValue('createlink')) { 3904 makeLink = false; 3905 } 3906 } 3907 if (makeLink) { 3908 this.execCommand('createlink', ''); 3909 this.toolbar.fireEvent('createlinkClick', { type: 'createlinkClick', target: this.toolbar }); 3910 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 3911 doExec = false; 3912 } 3913 } 3914 } 3915 break; 3916 //case 90: //Z 3917 case this._keyMap.UNDO.key: 3918 case this._keyMap.REDO.key: 3919 if (this._checkKey(this._keyMap.REDO, ev)) { 3920 action = 'redo'; 3921 doExec = true; 3922 } else if (this._checkKey(this._keyMap.UNDO, ev)) { 3923 action = 'undo'; 3924 doExec = true; 3925 } 3926 break; 3927 //case 66: //B 3928 case this._keyMap.BOLD.key: 3929 if (this._checkKey(this._keyMap.BOLD, ev)) { 3930 action = 'bold'; 3931 doExec = true; 3932 } 3933 break; 3934 case this._keyMap.FONT_SIZE_UP.key: 3935 case this._keyMap.FONT_SIZE_DOWN.key: 3936 var uk = false, dk = false; 3937 if (this._checkKey(this._keyMap.FONT_SIZE_UP, ev)) { 3938 uk = true; 3939 } 3940 if (this._checkKey(this._keyMap.FONT_SIZE_DOWN, ev)) { 3941 dk = true; 3942 } 3943 if (uk || dk) { 3944 var fs_button = this.toolbar.getButtonByValue('fontsize'), 3945 label = parseInt(fs_button.get('label'), 10), 3946 newValue = (label + 1); 3947 3948 if (dk) { 3949 newValue = (label - 1); 3950 } 3951 3952 action = 'fontsize'; 3953 value = newValue + 'px'; 3954 doExec = true; 3955 } 3956 break; 3957 //case 73: //I 3958 case this._keyMap.ITALIC.key: 3959 if (this._checkKey(this._keyMap.ITALIC, ev)) { 3960 action = 'italic'; 3961 doExec = true; 3962 } 3963 break; 3964 //case 85: //U 3965 case this._keyMap.UNDERLINE.key: 3966 if (this._checkKey(this._keyMap.UNDERLINE, ev)) { 3967 action = 'underline'; 3968 doExec = true; 3969 } 3970 break; 3971 case 9: 3972 if (this.browser.ie) { 3973 //Insert a tab in Internet Explorer 3974 _range = this._getRange(); 3975 tar = this._getSelectedElement(); 3976 if (!this._isElement(tar, 'li')) { 3977 if (_range) { 3978 _range.pasteHTML(' '); 3979 _range.collapse(false); 3980 _range.select(); 3981 } 3982 Event.stopEvent(ev); 3983 } 3984 } 3985 //Firefox 3 code 3986 if (this.browser.gecko > 1.8) { 3987 tar = this._getSelectedElement(); 3988 if (this._isElement(tar, 'li')) { 3989 if (ev.shiftKey) { 3990 this._getDoc().execCommand('outdent', null, ''); 3991 } else { 3992 this._getDoc().execCommand('indent', null, ''); 3993 } 3994 3995 } else if (!this._hasSelection()) { 3996 this.execCommand('inserthtml', ' '); 3997 } 3998 Event.stopEvent(ev); 3999 } 4000 break; 4001 case 13: 4002 var p = null, i = 0; 4003 if (this.get('ptags') && !ev.shiftKey) { 4004 if (this.browser.gecko) { 4005 tar = this._getSelectedElement(); 4006 if (!this._hasParent(tar, 'li')) { 4007 if (this._hasParent(tar, 'p')) { 4008 p = this._getDoc().createElement('p'); 4009 p.innerHTML = ' '; 4010 Dom.insertAfter(p, tar); 4011 this._selectNode(p.firstChild); 4012 } else if (this._isElement(tar, 'body')) { 4013 this.execCommand('insertparagraph', null); 4014 var ps = this._getDoc().body.getElementsByTagName('p'); 4015 for (i = 0; i < ps.length; i++) { 4016 if (ps[i].getAttribute('_moz_dirty') !== null) { 4017 p = this._getDoc().createElement('p'); 4018 p.innerHTML = ' '; 4019 Dom.insertAfter(p, ps[i]); 4020 this._selectNode(p.firstChild); 4021 ps[i].removeAttribute('_moz_dirty'); 4022 } 4023 } 4024 } else { 4025 doExec = true; 4026 action = 'insertparagraph'; 4027 } 4028 Event.stopEvent(ev); 4029 } 4030 } 4031 if (this.browser.webkit) { 4032 tar = this._getSelectedElement(); 4033 if (!this._hasParent(tar, 'li')) { 4034 this.execCommand('insertparagraph', null); 4035 var divs = this._getDoc().body.getElementsByTagName('div'); 4036 for (i = 0; i < divs.length; i++) { 4037 if (!Dom.hasClass(divs[i], 'yui-wk-div')) { 4038 Dom.addClass(divs[i], 'yui-wk-p'); 4039 } 4040 } 4041 Event.stopEvent(ev); 4042 } 4043 } 4044 } else { 4045 if (this.browser.webkit) { 4046 tar = this._getSelectedElement(); 4047 if (!this._hasParent(tar, 'li')) { 4048 if (this.browser.webkit4) { 4049 this.execCommand('insertlinebreak'); 4050 } else { 4051 this.execCommand('inserthtml', '<var id="yui-br"></var>'); 4052 var holder = this._getDoc().getElementById('yui-br'), 4053 br = this._getDoc().createElement('br'), 4054 caret = this._getDoc().createElement('span'); 4055 4056 holder.parentNode.replaceChild(br, holder); 4057 caret.className = 'yui-non'; 4058 caret.innerHTML = ' '; 4059 Dom.insertAfter(caret, br); 4060 this._selectNode(caret); 4061 } 4062 Event.stopEvent(ev); 4063 } 4064 } 4065 if (this.browser.ie) { 4066 //Insert a <br> instead of a <p></p> in Internet Explorer 4067 _range = this._getRange(); 4068 tar = this._getSelectedElement(); 4069 if (!this._isElement(tar, 'li')) { 4070 if (_range) { 4071 _range.pasteHTML('<br>'); 4072 _range.collapse(false); 4073 _range.select(); 4074 } 4075 Event.stopEvent(ev); 4076 } 4077 } 4078 } 4079 break; 4080 } 4081 if (this.browser.ie) { 4082 this._listFix(ev); 4083 } 4084 if (doExec && action) { 4085 this.execCommand(action, value); 4086 Event.stopEvent(ev); 4087 this.nodeChange(); 4088 } 4089 this._storeUndo(); 4090 this.fireEvent('editorKeyDown', { type: 'editorKeyDown', target: this, ev: ev }); 4091 }, 4092 /** 4093 * @private 4094 * @property _fixListRunning 4095 * @type Boolean 4096 * @description Keeps more than one _fixListDupIds from running at the same time. 4097 */ 4098 _fixListRunning: null, 4099 /** 4100 * @private 4101 * @method _fixListDupIds 4102 * @description Some browsers will duplicate the id of an LI when created in designMode. 4103 * This method will fix the duplicate id issue. However it will only preserve the first element 4104 * in the document list with the unique id. 4105 */ 4106 _fixListDupIds: function() { 4107 if (this._fixListRunning) { 4108 return false; 4109 } 4110 if (this._getDoc()) { 4111 this._fixListRunning = true; 4112 var lis = this._getDoc().body.getElementsByTagName('li'), 4113 i = 0, ids = {}; 4114 for (i = 0; i < lis.length; i++) { 4115 if (lis[i].id) { 4116 if (ids[lis[i].id]) { 4117 lis[i].id = ''; 4118 } 4119 ids[lis[i].id] = true; 4120 } 4121 } 4122 this._fixListRunning = false; 4123 } 4124 }, 4125 /** 4126 * @private 4127 * @method _listFix 4128 * @param {Event} ev The event we are working on. 4129 * @description Handles the Enter key, Tab Key and Shift + Tab keys for List Items. 4130 */ 4131 _listFix: function(ev) { 4132 var testLi = null, par = null, preContent = false, range = null; 4133 //Enter Key 4134 if (this.browser.webkit) { 4135 if (ev.keyCode && (ev.keyCode == 13)) { 4136 if (this._hasParent(this._getSelectedElement(), 'li')) { 4137 var tar = this._hasParent(this._getSelectedElement(), 'li'); 4138 if (tar.previousSibling) { 4139 if (tar.firstChild && (tar.firstChild.length == 1)) { 4140 this._selectNode(tar); 4141 } 4142 } 4143 } 4144 } 4145 } 4146 //Shift + Tab Key 4147 if (ev.keyCode && ((!this.browser.webkit3 && (ev.keyCode == 25)) || ((this.browser.webkit3 || !this.browser.webkit) && ((ev.keyCode == 9) && ev.shiftKey)))) { 4148 testLi = this._getSelectedElement(); 4149 if (this._hasParent(testLi, 'li')) { 4150 testLi = this._hasParent(testLi, 'li'); 4151 if (this._hasParent(testLi, 'ul') || this._hasParent(testLi, 'ol')) { 4152 par = this._hasParent(testLi, 'ul'); 4153 if (!par) { 4154 par = this._hasParent(testLi, 'ol'); 4155 } 4156 if (this._isElement(par.previousSibling, 'li')) { 4157 par.removeChild(testLi); 4158 par.parentNode.insertBefore(testLi, par.nextSibling); 4159 if (this.browser.ie) { 4160 range = this._getDoc().body.createTextRange(); 4161 range.moveToElementText(testLi); 4162 range.collapse(false); 4163 range.select(); 4164 } 4165 if (this.browser.webkit) { 4166 this._selectNode(testLi.firstChild); 4167 } 4168 Event.stopEvent(ev); 4169 } 4170 } 4171 } 4172 } 4173 //Tab Key 4174 if (ev.keyCode && ((ev.keyCode == 9) && (!ev.shiftKey))) { 4175 var preLi = this._getSelectedElement(); 4176 if (this._hasParent(preLi, 'li')) { 4177 preContent = this._hasParent(preLi, 'li').innerHTML; 4178 } 4179 if (this.browser.webkit) { 4180 this._getDoc().execCommand('inserttext', false, '\t'); 4181 } 4182 testLi = this._getSelectedElement(); 4183 if (this._hasParent(testLi, 'li')) { 4184 par = this._hasParent(testLi, 'li'); 4185 var newUl = this._getDoc().createElement(par.parentNode.tagName.toLowerCase()); 4186 if (this.browser.webkit) { 4187 var span = Dom.getElementsByClassName('Apple-tab-span', 'span', par); 4188 //Remove the span element that Safari puts in 4189 if (span[0]) { 4190 par.removeChild(span[0]); 4191 par.innerHTML = Lang.trim(par.innerHTML); 4192 //Put the HTML from the LI into this new LI 4193 if (preContent) { 4194 par.innerHTML = '<span class="yui-non">' + preContent + '</span> '; 4195 } else { 4196 par.innerHTML = '<span class="yui-non"> </span> '; 4197 } 4198 } 4199 } else { 4200 if (preContent) { 4201 par.innerHTML = preContent + ' '; 4202 } else { 4203 par.innerHTML = ' '; 4204 } 4205 } 4206 4207 par.parentNode.replaceChild(newUl, par); 4208 newUl.appendChild(par); 4209 if (this.browser.webkit) { 4210 this._getSelection().setBaseAndExtent(par.firstChild, 1, par.firstChild, par.firstChild.innerText.length); 4211 if (!this.browser.webkit3) { 4212 par.parentNode.parentNode.style.display = 'list-item'; 4213 setTimeout(function() { 4214 par.parentNode.parentNode.style.display = 'block'; 4215 }, 1); 4216 } 4217 } else if (this.browser.ie) { 4218 range = this._getDoc().body.createTextRange(); 4219 range.moveToElementText(par); 4220 range.collapse(false); 4221 range.select(); 4222 } else { 4223 this._selectNode(par); 4224 } 4225 Event.stopEvent(ev); 4226 } 4227 if (this.browser.webkit) { 4228 Event.stopEvent(ev); 4229 } 4230 this.nodeChange(); 4231 } 4232 }, 4233 /** 4234 * @method nodeChange 4235 * @param {Boolean} force Optional paramenter to skip the threshold counter 4236 * @description Handles setting up the toolbar buttons, getting the Dom path, fixing nodes. 4237 */ 4238 nodeChange: function(force) { 4239 var NCself = this; 4240 this._storeUndo(); 4241 if (this.get('nodeChangeDelay')) { 4242 this._nodeChangeDelayTimer = window.setTimeout(function() { 4243 NCself._nodeChangeDelayTimer = null; 4244 NCself._nodeChange.apply(NCself, arguments); 4245 }, 0); 4246 } else { 4247 this._nodeChange(); 4248 } 4249 }, 4250 /** 4251 * @private 4252 * @method _nodeChange 4253 * @param {Boolean} force Optional paramenter to skip the threshold counter 4254 * @description Fired from nodeChange in a setTimeout. 4255 */ 4256 _nodeChange: function(force) { 4257 var threshold = parseInt(this.get('nodeChangeThreshold'), 10), 4258 thisNodeChange = Math.round(new Date().getTime() / 1000), 4259 self = this; 4260 4261 if (force === true) { 4262 this._lastNodeChange = 0; 4263 } 4264 4265 if ((this._lastNodeChange + threshold) < thisNodeChange) { 4266 if (this._fixNodesTimer === null) { 4267 this._fixNodesTimer = window.setTimeout(function() { 4268 self._fixNodes.call(self); 4269 self._fixNodesTimer = null; 4270 }, 0); 4271 } 4272 } 4273 this._lastNodeChange = thisNodeChange; 4274 if (this.currentEvent) { 4275 try { 4276 this._lastNodeChangeEvent = this.currentEvent.type; 4277 } catch (e) {} 4278 } 4279 4280 var beforeNodeChange = this.fireEvent('beforeNodeChange', { type: 'beforeNodeChange', target: this }); 4281 if (beforeNodeChange === false) { 4282 return false; 4283 } 4284 if (this.get('dompath')) { 4285 window.setTimeout(function() { 4286 self._writeDomPath.call(self); 4287 }, 0); 4288 } 4289 //Check to see if we are disabled before continuing 4290 if (!this.get('disabled')) { 4291 if (this.STOP_NODE_CHANGE) { 4292 //Reset this var for next action 4293 this.STOP_NODE_CHANGE = false; 4294 return false; 4295 } else { 4296 var sel = this._getSelection(), 4297 range = this._getRange(), 4298 el = this._getSelectedElement(), 4299 fn_button = this.toolbar.getButtonByValue('fontname'), 4300 fs_button = this.toolbar.getButtonByValue('fontsize'), 4301 undo_button = this.toolbar.getButtonByValue('undo'), 4302 redo_button = this.toolbar.getButtonByValue('redo'); 4303 4304 //Handle updating the toolbar with active buttons 4305 var _ex = {}; 4306 if (this._lastButton) { 4307 _ex[this._lastButton.id] = true; 4308 //this._lastButton = null; 4309 } 4310 if (!this._isElement(el, 'body')) { 4311 if (fn_button) { 4312 _ex[fn_button.get('id')] = true; 4313 } 4314 if (fs_button) { 4315 _ex[fs_button.get('id')] = true; 4316 } 4317 } 4318 if (redo_button) { 4319 delete _ex[redo_button.get('id')]; 4320 } 4321 this.toolbar.resetAllButtons(_ex); 4322 4323 //Handle disabled buttons 4324 for (var d = 0; d < this._disabled.length; d++) { 4325 var _button = this.toolbar.getButtonByValue(this._disabled[d]); 4326 if (_button && _button.get) { 4327 if (this._lastButton && (_button.get('id') === this._lastButton.id)) { 4328 //Skip 4329 } else { 4330 if (!this._hasSelection() && !this.get('insert')) { 4331 switch (this._disabled[d]) { 4332 case 'fontname': 4333 case 'fontsize': 4334 break; 4335 default: 4336 //No Selection - disable 4337 this.toolbar.disableButton(_button); 4338 } 4339 } else { 4340 if (!this._alwaysDisabled[this._disabled[d]]) { 4341 this.toolbar.enableButton(_button); 4342 } 4343 } 4344 if (!this._alwaysEnabled[this._disabled[d]]) { 4345 this.toolbar.deselectButton(_button); 4346 } 4347 } 4348 } 4349 } 4350 var path = this._getDomPath(); 4351 var tag = null, cmd = null; 4352 for (var i = 0; i < path.length; i++) { 4353 tag = path[i].tagName.toLowerCase(); 4354 if (path[i].getAttribute('tag')) { 4355 tag = path[i].getAttribute('tag').toLowerCase(); 4356 } 4357 cmd = this._tag2cmd[tag]; 4358 if (cmd === undefined) { 4359 cmd = []; 4360 } 4361 if (!Lang.isArray(cmd)) { 4362 cmd = [cmd]; 4363 } 4364 4365 //Bold and Italic styles 4366 if (path[i].style.fontWeight.toLowerCase() == 'bold') { 4367 cmd[cmd.length] = 'bold'; 4368 } 4369 if (path[i].style.fontStyle.toLowerCase() == 'italic') { 4370 cmd[cmd.length] = 'italic'; 4371 } 4372 if (path[i].style.textDecoration.toLowerCase() == 'underline') { 4373 cmd[cmd.length] = 'underline'; 4374 } 4375 if (path[i].style.textDecoration.toLowerCase() == 'line-through') { 4376 cmd[cmd.length] = 'strikethrough'; 4377 } 4378 if (cmd.length > 0) { 4379 for (var j = 0; j < cmd.length; j++) { 4380 this.toolbar.selectButton(cmd[j]); 4381 this.toolbar.enableButton(cmd[j]); 4382 } 4383 } 4384 //Handle Alignment 4385 switch (path[i].style.textAlign.toLowerCase()) { 4386 case 'left': 4387 case 'right': 4388 case 'center': 4389 case 'justify': 4390 var alignType = path[i].style.textAlign.toLowerCase(); 4391 if (path[i].style.textAlign.toLowerCase() == 'justify') { 4392 alignType = 'full'; 4393 } 4394 this.toolbar.selectButton('justify' + alignType); 4395 this.toolbar.enableButton('justify' + alignType); 4396 break; 4397 } 4398 } 4399 //After for loop 4400 4401 //Reset Font Family and Size to the inital configs 4402 if (fn_button) { 4403 var family = fn_button._configs.label._initialConfig.value; 4404 fn_button.set('label', '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>'); 4405 this._updateMenuChecked('fontname', family); 4406 } 4407 4408 if (fs_button) { 4409 fs_button.set('label', fs_button._configs.label._initialConfig.value); 4410 } 4411 4412 var hd_button = this.toolbar.getButtonByValue('heading'); 4413 if (hd_button) { 4414 hd_button.set('label', hd_button._configs.label._initialConfig.value); 4415 this._updateMenuChecked('heading', 'none'); 4416 } 4417 var img_button = this.toolbar.getButtonByValue('insertimage'); 4418 if (img_button && this.currentWindow && (this.currentWindow.name == 'insertimage')) { 4419 this.toolbar.disableButton(img_button); 4420 } 4421 if (this._lastButton && this._lastButton.isSelected) { 4422 this.toolbar.deselectButton(this._lastButton.id); 4423 } 4424 this._undoNodeChange(); 4425 } 4426 } 4427 4428 this.fireEvent('afterNodeChange', { type: 'afterNodeChange', target: this }); 4429 }, 4430 /** 4431 * @private 4432 * @method _updateMenuChecked 4433 * @param {Object} button The command identifier of the button you want to check 4434 * @param {String} value The value of the menu item you want to check 4435 * @param {<a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>} The Toolbar instance the button belongs to (defaults to this.toolbar) 4436 * @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. 4437 */ 4438 _updateMenuChecked: function(button, value, tbar) { 4439 if (!tbar) { 4440 tbar = this.toolbar; 4441 } 4442 var _button = tbar.getButtonByValue(button); 4443 _button.checkValue(value); 4444 }, 4445 /** 4446 * @private 4447 * @method _handleToolbarClick 4448 * @param {Event} ev The event that triggered the button click 4449 * @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. 4450 */ 4451 _handleToolbarClick: function(ev) { 4452 var value = ''; 4453 var str = ''; 4454 var cmd = ev.button.value; 4455 if (ev.button.menucmd) { 4456 value = cmd; 4457 cmd = ev.button.menucmd; 4458 } 4459 this._lastButton = ev.button; 4460 if (this.STOP_EXEC_COMMAND) { 4461 this.STOP_EXEC_COMMAND = false; 4462 return false; 4463 } else { 4464 this.execCommand(cmd, value); 4465 if (!this.browser.webkit) { 4466 var Fself = this; 4467 setTimeout(function() { 4468 Fself.focus.call(Fself); 4469 }, 5); 4470 } 4471 } 4472 Event.stopEvent(ev); 4473 }, 4474 /** 4475 * @private 4476 * @method _setupAfterElement 4477 * @description Creates the accessibility h2 header and places it after the iframe in the Dom for navigation. 4478 */ 4479 _setupAfterElement: function() { 4480 if (!this.beforeElement) { 4481 this.beforeElement = document.createElement('h2'); 4482 this.beforeElement.className = 'yui-editor-skipheader'; 4483 this.beforeElement.tabIndex = '-1'; 4484 this.beforeElement.innerHTML = this.STR_BEFORE_EDITOR; 4485 this.get('element_cont').get('firstChild').insertBefore(this.beforeElement, this.toolbar.get('nextSibling')); 4486 } 4487 if (!this.afterElement) { 4488 this.afterElement = document.createElement('h2'); 4489 this.afterElement.className = 'yui-editor-skipheader'; 4490 this.afterElement.tabIndex = '-1'; 4491 this.afterElement.innerHTML = this.STR_LEAVE_EDITOR; 4492 this.get('element_cont').get('firstChild').appendChild(this.afterElement); 4493 } 4494 }, 4495 /** 4496 * @private 4497 * @method _disableEditor 4498 * @param {Boolean} disabled Pass true to disable, false to enable 4499 * @description Creates a mask to place over the Editor. 4500 */ 4501 _disableEditor: function(disabled) { 4502 var iframe, par, html, height; 4503 if (!this.get('disabled_iframe')) { 4504 iframe = this._createIframe(); 4505 iframe.set('id', 'disabled_' + this.get('iframe').get('id')); 4506 iframe.setStyle('height', '100%'); 4507 iframe.setStyle('display', 'none'); 4508 iframe.setStyle('visibility', 'visible'); 4509 this.set('disabled_iframe', iframe); 4510 par = this.get('iframe').get('parentNode'); 4511 par.appendChild(iframe.get('element')); 4512 } 4513 if (!iframe) { 4514 iframe = this.get('disabled_iframe'); 4515 } 4516 if (disabled) { 4517 this._orgIframe = this.get('iframe'); 4518 4519 if (this.toolbar) { 4520 this.toolbar.set('disabled', true); 4521 } 4522 4523 html = this.getEditorHTML(); 4524 height = this.get('iframe').get('offsetHeight'); 4525 iframe.setStyle('visibility', ''); 4526 iframe.setStyle('position', ''); 4527 iframe.setStyle('top', ''); 4528 iframe.setStyle('left', ''); 4529 this._orgIframe.setStyle('visibility', 'hidden'); 4530 this._orgIframe.setStyle('position', 'absolute'); 4531 this._orgIframe.setStyle('top', '-99999px'); 4532 this._orgIframe.setStyle('left', '-99999px'); 4533 this.set('iframe', iframe); 4534 this._setInitialContent(true); 4535 4536 if (!this._mask) { 4537 this._mask = document.createElement('DIV'); 4538 Dom.addClass(this._mask, 'yui-editor-masked'); 4539 if (this.browser.ie) { 4540 this._mask.style.height = height + 'px'; 4541 } 4542 this.get('iframe').get('parentNode').appendChild(this._mask); 4543 } 4544 this.on('editorContentReloaded', function() { 4545 this._getDoc().body._rteLoaded = false; 4546 this.setEditorHTML(html); 4547 iframe.setStyle('display', 'block'); 4548 this.unsubscribeAll('editorContentReloaded'); 4549 }); 4550 } else { 4551 if (this._mask) { 4552 this._mask.parentNode.removeChild(this._mask); 4553 this._mask = null; 4554 if (this.toolbar) { 4555 this.toolbar.set('disabled', false); 4556 } 4557 iframe.setStyle('visibility', 'hidden'); 4558 iframe.setStyle('position', 'absolute'); 4559 iframe.setStyle('top', '-99999px'); 4560 iframe.setStyle('left', '-99999px'); 4561 this._orgIframe.setStyle('visibility', ''); 4562 this._orgIframe.setStyle('position', ''); 4563 this._orgIframe.setStyle('top', ''); 4564 this._orgIframe.setStyle('left', ''); 4565 this.set('iframe', this._orgIframe); 4566 4567 this.focus(); 4568 var self = this; 4569 window.setTimeout(function() { 4570 self.nodeChange.call(self); 4571 }, 100); 4572 } 4573 } 4574 }, 4575 /** 4576 * @property SEP_DOMPATH 4577 * @description The value to place in between the Dom path items 4578 * @type String 4579 */ 4580 SEP_DOMPATH: '<', 4581 /** 4582 * @property STR_LEAVE_EDITOR 4583 * @description The accessibility string for the element after the iFrame 4584 * @type String 4585 */ 4586 STR_LEAVE_EDITOR: 'You have left the Rich Text Editor.', 4587 /** 4588 * @property STR_BEFORE_EDITOR 4589 * @description The accessibility string for the element before the iFrame 4590 * @type String 4591 */ 4592 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>', 4593 /** 4594 * @property STR_TITLE 4595 * @description The Title of the HTML document that is created in the iFrame 4596 * @type String 4597 */ 4598 STR_TITLE: 'Rich Text Area.', 4599 /** 4600 * @property STR_IMAGE_HERE 4601 * @description The text to place in the URL textbox when using the blankimage. 4602 * @type String 4603 */ 4604 STR_IMAGE_HERE: 'Image URL Here', 4605 /** 4606 * @property STR_IMAGE_URL 4607 * @description The label string for Image URL 4608 * @type String 4609 */ 4610 STR_IMAGE_URL: 'Image URL', 4611 /** 4612 * @property STR_LINK_URL 4613 * @description The label string for the Link URL. 4614 * @type String 4615 */ 4616 STR_LINK_URL: 'Link URL', 4617 /** 4618 * @protected 4619 * @property STOP_EXEC_COMMAND 4620 * @description Set to true when you want the default execCommand function to not process anything 4621 * @type Boolean 4622 */ 4623 STOP_EXEC_COMMAND: false, 4624 /** 4625 * @protected 4626 * @property STOP_NODE_CHANGE 4627 * @description Set to true when you want the default nodeChange function to not process anything 4628 * @type Boolean 4629 */ 4630 STOP_NODE_CHANGE: false, 4631 /** 4632 * @protected 4633 * @property CLASS_NOEDIT 4634 * @description CSS class applied to elements that are not editable. 4635 * @type String 4636 */ 4637 CLASS_NOEDIT: 'yui-noedit', 4638 /** 4639 * @protected 4640 * @property CLASS_CONTAINER 4641 * @description Default CSS class to apply to the editors container element 4642 * @type String 4643 */ 4644 CLASS_CONTAINER: 'yui-editor-container', 4645 /** 4646 * @protected 4647 * @property CLASS_EDITABLE 4648 * @description Default CSS class to apply to the editors iframe element 4649 * @type String 4650 */ 4651 CLASS_EDITABLE: 'yui-editor-editable', 4652 /** 4653 * @protected 4654 * @property CLASS_EDITABLE_CONT 4655 * @description Default CSS class to apply to the editors iframe's parent element 4656 * @type String 4657 */ 4658 CLASS_EDITABLE_CONT: 'yui-editor-editable-container', 4659 /** 4660 * @protected 4661 * @property CLASS_PREFIX 4662 * @description Default prefix for dynamically created class names 4663 * @type String 4664 */ 4665 CLASS_PREFIX: 'yui-editor', 4666 /** 4667 * @property browser 4668 * @description Standard browser detection 4669 * @type Object 4670 */ 4671 browser: function() { 4672 var br = YAHOO.env.ua; 4673 //Check for webkit3 4674 if (br.webkit >= 420) { 4675 br.webkit3 = br.webkit; 4676 } else { 4677 br.webkit3 = 0; 4678 } 4679 if (br.webkit >= 530) { 4680 br.webkit4 = br.webkit; 4681 } else { 4682 br.webkit4 = 0; 4683 } 4684 br.mac = false; 4685 //Check for Mac 4686 if (navigator.userAgent.indexOf('Macintosh') !== -1) { 4687 br.mac = true; 4688 } 4689 4690 return br; 4691 }(), 4692 /** 4693 * @method init 4694 * @description The Editor class' initialization method 4695 */ 4696 init: function(p_oElement, p_oAttributes) { 4697 4698 if (!this._defaultToolbar) { 4699 this._defaultToolbar = { 4700 collapse: true, 4701 titlebar: 'Text Editing Tools', 4702 draggable: false, 4703 buttons: [ 4704 { group: 'fontstyle', label: 'Font Name and Size', 4705 buttons: [ 4706 { type: 'select', label: 'Arial', value: 'fontname', disabled: true, 4707 menu: [ 4708 { text: 'Arial', checked: true }, 4709 { text: 'Arial Black' }, 4710 { text: 'Comic Sans MS' }, 4711 { text: 'Courier New' }, 4712 { text: 'Lucida Console' }, 4713 { text: 'Tahoma' }, 4714 { text: 'Times New Roman' }, 4715 { text: 'Trebuchet MS' }, 4716 { text: 'Verdana' } 4717 ] 4718 }, 4719 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true } 4720 ] 4721 }, 4722 { type: 'separator' }, 4723 { group: 'textstyle', label: 'Font Style', 4724 buttons: [ 4725 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' }, 4726 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' }, 4727 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' }, 4728 { type: 'push', label: 'Strike Through', value: 'strikethrough' }, 4729 { type: 'separator' }, 4730 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true }, 4731 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true } 4732 4733 ] 4734 }, 4735 { type: 'separator' }, 4736 { group: 'indentlist', label: 'Lists', 4737 buttons: [ 4738 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' }, 4739 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' } 4740 ] 4741 }, 4742 { type: 'separator' }, 4743 { group: 'insertitem', label: 'Insert Item', 4744 buttons: [ 4745 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true }, 4746 { type: 'push', label: 'Insert Image', value: 'insertimage' } 4747 ] 4748 } 4749 ] 4750 }; 4751 } 4752 4753 YAHOO.widget.SimpleEditor.superclass.init.call(this, p_oElement, p_oAttributes); 4754 YAHOO.widget.EditorInfo._instances[this.get('id')] = this; 4755 4756 4757 this.currentElement = []; 4758 this.on('contentReady', function() { 4759 this.DOMReady = true; 4760 this.fireQueue(); 4761 }, this, true); 4762 4763 }, 4764 /** 4765 * @method initAttributes 4766 * @description Initializes all of the configuration attributes used to create 4767 * the editor. 4768 * @param {Object} attr Object literal specifying a set of 4769 * configuration attributes used to create the editor. 4770 */ 4771 initAttributes: function(attr) { 4772 YAHOO.widget.SimpleEditor.superclass.initAttributes.call(this, attr); 4773 var self = this; 4774 4775 /** 4776 * @config setDesignMode 4777 * @description Should the Editor set designMode on the document. Default: true. 4778 * @default true 4779 * @type Boolean 4780 */ 4781 this.setAttributeConfig('setDesignMode', { 4782 value: ((attr.setDesignMode === false) ? false : true) 4783 }); 4784 /** 4785 * @config nodeChangeDelay 4786 * @description Do we wrap the nodeChange method in a timeout for performance. Default: true. 4787 * @default true 4788 * @type Boolean 4789 */ 4790 this.setAttributeConfig('nodeChangeDelay', { 4791 value: ((attr.nodeChangeDelay === false) ? false : true) 4792 }); 4793 /** 4794 * @config maxUndo 4795 * @description The max number of undo levels to store. 4796 * @default 30 4797 * @type Number 4798 */ 4799 this.setAttributeConfig('maxUndo', { 4800 writeOnce: true, 4801 value: attr.maxUndo || 30 4802 }); 4803 4804 /** 4805 * @config ptags 4806 * @description If true, the editor uses <P> tags instead of <br> tags. (Use Shift + Enter to get a <br>) 4807 * @default false 4808 * @type Boolean 4809 */ 4810 this.setAttributeConfig('ptags', { 4811 writeOnce: true, 4812 value: attr.ptags || false 4813 }); 4814 /** 4815 * @config insert 4816 * @description If true, selection is not required for: fontname, fontsize, forecolor, backcolor. 4817 * @default false 4818 * @type Boolean 4819 */ 4820 this.setAttributeConfig('insert', { 4821 writeOnce: true, 4822 value: attr.insert || false, 4823 method: function(insert) { 4824 if (insert) { 4825 var buttons = { 4826 fontname: true, 4827 fontsize: true, 4828 forecolor: true, 4829 backcolor: true 4830 }; 4831 var tmp = this._defaultToolbar.buttons; 4832 for (var i = 0; i < tmp.length; i++) { 4833 if (tmp[i].buttons) { 4834 for (var a = 0; a < tmp[i].buttons.length; a++) { 4835 if (tmp[i].buttons[a].value) { 4836 if (buttons[tmp[i].buttons[a].value]) { 4837 delete tmp[i].buttons[a].disabled; 4838 } 4839 } 4840 } 4841 } 4842 } 4843 } 4844 } 4845 }); 4846 /** 4847 * @config container 4848 * @description Used when dynamically creating the Editor from Javascript with no default textarea. 4849 * We will create one and place it in this container. If no container is passed we will append to document.body. 4850 * @default false 4851 * @type HTMLElement 4852 */ 4853 this.setAttributeConfig('container', { 4854 writeOnce: true, 4855 value: attr.container || false 4856 }); 4857 /** 4858 * @config plainText 4859 * @description Process the inital textarea data as if it was plain text. Accounting for spaces, tabs and line feeds. 4860 * @default false 4861 * @type Boolean 4862 */ 4863 this.setAttributeConfig('plainText', { 4864 writeOnce: true, 4865 value: attr.plainText || false 4866 }); 4867 /** 4868 * @private 4869 * @config iframe 4870 * @description Internal config for holding the iframe element. 4871 * @default null 4872 * @type HTMLElement 4873 */ 4874 this.setAttributeConfig('iframe', { 4875 value: null 4876 }); 4877 /** 4878 * @private 4879 * @config disabled_iframe 4880 * @description Internal config for holding the iframe element used when disabling the Editor. 4881 * @default null 4882 * @type HTMLElement 4883 */ 4884 this.setAttributeConfig('disabled_iframe', { 4885 value: null 4886 }); 4887 /** 4888 * @private 4889 * @depreciated - No longer used, should use this.get('element') 4890 * @config textarea 4891 * @description Internal config for holding the textarea element (replaced with element). 4892 * @default null 4893 * @type HTMLElement 4894 */ 4895 this.setAttributeConfig('textarea', { 4896 value: null, 4897 writeOnce: true 4898 }); 4899 /** 4900 * @config nodeChangeThreshold 4901 * @description The number of seconds that need to be in between nodeChange processing 4902 * @default 3 4903 * @type Number 4904 */ 4905 this.setAttributeConfig('nodeChangeThreshold', { 4906 value: attr.nodeChangeThreshold || 3, 4907 validator: YAHOO.lang.isNumber 4908 }); 4909 /** 4910 * @config allowNoEdit 4911 * @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. 4912 * Such as highlighting an element below and above the content and hitting a toolbar button or a shortcut key. 4913 * @default false 4914 * @type Boolean 4915 */ 4916 this.setAttributeConfig('allowNoEdit', { 4917 value: attr.allowNoEdit || false, 4918 validator: YAHOO.lang.isBoolean 4919 }); 4920 /** 4921 * @config limitCommands 4922 * @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. 4923 * @default false 4924 * @type Boolean 4925 */ 4926 this.setAttributeConfig('limitCommands', { 4927 value: attr.limitCommands || false, 4928 validator: YAHOO.lang.isBoolean 4929 }); 4930 /** 4931 * @config element_cont 4932 * @description Internal config for the editors container 4933 * @default false 4934 * @type HTMLElement 4935 */ 4936 this.setAttributeConfig('element_cont', { 4937 value: attr.element_cont 4938 }); 4939 /** 4940 * @private 4941 * @config editor_wrapper 4942 * @description The outter wrapper for the entire editor. 4943 * @default null 4944 * @type HTMLElement 4945 */ 4946 this.setAttributeConfig('editor_wrapper', { 4947 value: attr.editor_wrapper || null, 4948 writeOnce: true 4949 }); 4950 /** 4951 * @attribute height 4952 * @description The height of the editor iframe container, not including the toolbar.. 4953 * @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 4954 * @type String 4955 */ 4956 this.setAttributeConfig('height', { 4957 value: attr.height || Dom.getStyle(self.get('element'), 'height'), 4958 method: function(height) { 4959 if (this._rendered) { 4960 //We have been rendered, change the height 4961 if (this.get('animate')) { 4962 var anim = new YAHOO.util.Anim(this.get('iframe').get('parentNode'), { 4963 height: { 4964 to: parseInt(height, 10) 4965 } 4966 }, 0.5); 4967 anim.animate(); 4968 } else { 4969 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', height); 4970 } 4971 } 4972 } 4973 }); 4974 /** 4975 * @config autoHeight 4976 * @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. 4977 * @default false 4978 * @type Boolean || Number 4979 */ 4980 this.setAttributeConfig('autoHeight', { 4981 value: attr.autoHeight || false, 4982 method: function(a) { 4983 if (a) { 4984 if (this.get('iframe')) { 4985 this.get('iframe').get('element').setAttribute('scrolling', 'no'); 4986 } 4987 this.on('afterNodeChange', this._handleAutoHeight, this, true); 4988 this.on('editorKeyDown', this._handleAutoHeight, this, true); 4989 this.on('editorKeyPress', this._handleAutoHeight, this, true); 4990 } else { 4991 if (this.get('iframe')) { 4992 this.get('iframe').get('element').setAttribute('scrolling', 'auto'); 4993 } 4994 this.unsubscribe('afterNodeChange', this._handleAutoHeight); 4995 this.unsubscribe('editorKeyDown', this._handleAutoHeight); 4996 this.unsubscribe('editorKeyPress', this._handleAutoHeight); 4997 } 4998 } 4999 }); 5000 /** 5001 * @attribute width 5002 * @description The width of the editor container. 5003 * @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 5004 * @type String 5005 */ 5006 this.setAttributeConfig('width', { 5007 value: attr.width || Dom.getStyle(this.get('element'), 'width'), 5008 method: function(width) { 5009 if (this._rendered) { 5010 //We have been rendered, change the width 5011 if (this.get('animate')) { 5012 var anim = new YAHOO.util.Anim(this.get('element_cont').get('element'), { 5013 width: { 5014 to: parseInt(width, 10) 5015 } 5016 }, 0.5); 5017 anim.animate(); 5018 } else { 5019 this.get('element_cont').setStyle('width', width); 5020 } 5021 } 5022 } 5023 }); 5024 5025 /** 5026 * @attribute blankimage 5027 * @description The URL for the image placeholder to put in when inserting an image. 5028 * @default The yahooapis.com address for the current release + 'assets/blankimage.png' 5029 * @type String 5030 */ 5031 this.setAttributeConfig('blankimage', { 5032 value: attr.blankimage || this._getBlankImage() 5033 }); 5034 /** 5035 * @attribute css 5036 * @description The Base CSS used to format the content of the editor 5037 * @default <code><pre>html { 5038 height: 95%; 5039 } 5040 body { 5041 height: 100%; 5042 padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; 5043 } 5044 a { 5045 color: blue; 5046 text-decoration: underline; 5047 cursor: pointer; 5048 } 5049 .warning-localfile { 5050 border-bottom: 1px dashed red !important; 5051 } 5052 .yui-busy { 5053 cursor: wait !important; 5054 } 5055 img.selected { //Safari image selection 5056 border: 2px dotted #808080; 5057 } 5058 img { 5059 cursor: pointer !important; 5060 border: none; 5061 } 5062 </pre></code> 5063 * @type String 5064 */ 5065 this.setAttributeConfig('css', { 5066 value: attr.css || this._defaultCSS, 5067 writeOnce: true 5068 }); 5069 /** 5070 * @attribute html 5071 * @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) 5072 * @default This HTML requires a few things if you are to override: 5073 <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> 5074 <p><code>onload="document.body._rteLoaded = true;"</code> : the onload statement must be there or the editor will not finish loading.</p> 5075 <code> 5076 <pre> 5077 <html> 5078 <head> 5079 <title>{TITLE}</title> 5080 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 5081 <style> 5082 {CSS} 5083 </style> 5084 <style> 5085 {HIDDEN_CSS} 5086 </style> 5087 <style> 5088 {EXTRA_CSS} 5089 </style> 5090 </head> 5091 <body onload="document.body._rteLoaded = true;"> 5092 {CONTENT} 5093 </body> 5094 </html> 5095 </pre> 5096 </code> 5097 * @type String 5098 */ 5099 this.setAttributeConfig('html', { 5100 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>', 5101 writeOnce: true 5102 }); 5103 5104 /** 5105 * @attribute extracss 5106 * @description Extra user defined css to load after the default SimpleEditor CSS 5107 * @default '' 5108 * @type String 5109 */ 5110 this.setAttributeConfig('extracss', { 5111 value: attr.extracss || '', 5112 writeOnce: true 5113 }); 5114 5115 /** 5116 * @attribute handleSubmit 5117 * @description Config handles if the editor will attach itself to the textareas parent form's submit handler. 5118 If it is set to true, the editor will attempt to attach a submit listener to the textareas parent form. 5119 Then it will trigger the editors save handler and place the new content back into the text area before the form is submitted. 5120 * @default false 5121 * @type Boolean 5122 */ 5123 this.setAttributeConfig('handleSubmit', { 5124 value: attr.handleSubmit || false, 5125 method: function(exec) { 5126 if (this.get('element').form) { 5127 if (!this._formButtons) { 5128 this._formButtons = []; 5129 } 5130 if (exec) { 5131 Event.on(this.get('element').form, 'submit', this._handleFormSubmit, this, true); 5132 var i = this.get('element').form.getElementsByTagName('input'); 5133 for (var s = 0; s < i.length; s++) { 5134 var type = i[s].getAttribute('type'); 5135 if (type && (type.toLowerCase() == 'submit')) { 5136 Event.on(i[s], 'click', this._handleFormButtonClick, this, true); 5137 this._formButtons[this._formButtons.length] = i[s]; 5138 } 5139 } 5140 } else { 5141 Event.removeListener(this.get('element').form, 'submit', this._handleFormSubmit); 5142 if (this._formButtons) { 5143 Event.removeListener(this._formButtons, 'click', this._handleFormButtonClick); 5144 } 5145 } 5146 } 5147 } 5148 }); 5149 /** 5150 * @attribute disabled 5151 * @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. 5152 All Toolbar buttons are also disabled so they cannot be used. 5153 * @default false 5154 * @type Boolean 5155 */ 5156 5157 this.setAttributeConfig('disabled', { 5158 value: false, 5159 method: function(disabled) { 5160 if (this._rendered) { 5161 this._disableEditor(disabled); 5162 } 5163 } 5164 }); 5165 /** 5166 * @config saveEl 5167 * @description When save HTML is called, this element will be updated as well as the source of data. 5168 * @default element 5169 * @type HTMLElement 5170 */ 5171 this.setAttributeConfig('saveEl', { 5172 value: this.get('element') 5173 }); 5174 /** 5175 * @config toolbar_cont 5176 * @description Internal config for the toolbars container 5177 * @default false 5178 * @type Boolean 5179 */ 5180 this.setAttributeConfig('toolbar_cont', { 5181 value: null, 5182 writeOnce: true 5183 }); 5184 /** 5185 * @attribute toolbar 5186 * @description The default toolbar config. 5187 * @type Object 5188 */ 5189 this.setAttributeConfig('toolbar', { 5190 value: attr.toolbar || this._defaultToolbar, 5191 writeOnce: true, 5192 method: function(toolbar) { 5193 if (!toolbar.buttonType) { 5194 toolbar.buttonType = this._defaultToolbar.buttonType; 5195 } 5196 this._defaultToolbar = toolbar; 5197 } 5198 }); 5199 /** 5200 * @attribute animate 5201 * @description Should the editor animate window movements 5202 * @default false unless Animation is found, then true 5203 * @type Boolean 5204 */ 5205 this.setAttributeConfig('animate', { 5206 value: ((attr.animate) ? ((YAHOO.util.Anim) ? true : false) : false), 5207 validator: function(value) { 5208 var ret = true; 5209 if (!YAHOO.util.Anim) { 5210 ret = false; 5211 } 5212 return ret; 5213 } 5214 }); 5215 /** 5216 * @config panel 5217 * @description A reference to the panel we are using for windows. 5218 * @default false 5219 * @type Boolean 5220 */ 5221 this.setAttributeConfig('panel', { 5222 value: null, 5223 writeOnce: true, 5224 validator: function(value) { 5225 var ret = true; 5226 if (!YAHOO.widget.Overlay) { 5227 ret = false; 5228 } 5229 return ret; 5230 } 5231 }); 5232 /** 5233 * @attribute focusAtStart 5234 * @description Should we focus the window when the content is ready? 5235 * @default false 5236 * @type Boolean 5237 */ 5238 this.setAttributeConfig('focusAtStart', { 5239 value: attr.focusAtStart || false, 5240 writeOnce: true, 5241 method: function(fs) { 5242 if (fs) { 5243 this.on('editorContentLoaded', function() { 5244 var self = this; 5245 setTimeout(function() { 5246 self.focus.call(self); 5247 self.editorDirty = false; 5248 }, 400); 5249 }, this, true); 5250 } 5251 } 5252 }); 5253 /** 5254 * @attribute dompath 5255 * @description Toggle the display of the current Dom path below the editor 5256 * @default false 5257 * @type Boolean 5258 */ 5259 this.setAttributeConfig('dompath', { 5260 value: attr.dompath || false, 5261 method: function(dompath) { 5262 if (dompath && !this.dompath) { 5263 this.dompath = document.createElement('DIV'); 5264 this.dompath.id = this.get('id') + '_dompath'; 5265 Dom.addClass(this.dompath, 'dompath'); 5266 this.get('element_cont').get('firstChild').appendChild(this.dompath); 5267 if (this.get('iframe')) { 5268 this._writeDomPath(); 5269 } 5270 } else if (!dompath && this.dompath) { 5271 this.dompath.parentNode.removeChild(this.dompath); 5272 this.dompath = null; 5273 } 5274 } 5275 }); 5276 /** 5277 * @attribute markup 5278 * @description Should we try to adjust the markup for the following types: semantic, css, default or xhtml 5279 * @default "semantic" 5280 * @type String 5281 */ 5282 this.setAttributeConfig('markup', { 5283 value: attr.markup || 'semantic', 5284 validator: function(markup) { 5285 switch (markup.toLowerCase()) { 5286 case 'semantic': 5287 case 'css': 5288 case 'default': 5289 case 'xhtml': 5290 return true; 5291 } 5292 return false; 5293 } 5294 }); 5295 /** 5296 * @attribute removeLineBreaks 5297 * @description Should we remove linebreaks and extra spaces on cleanup 5298 * @default false 5299 * @type Boolean 5300 */ 5301 this.setAttributeConfig('removeLineBreaks', { 5302 value: attr.removeLineBreaks || false, 5303 validator: YAHOO.lang.isBoolean 5304 }); 5305 5306 /** 5307 * @config drag 5308 * @description Set this config to make the Editor draggable, pass 'proxy' to make use YAHOO.util.DDProxy. 5309 * @type {Boolean/String} 5310 */ 5311 this.setAttributeConfig('drag', { 5312 writeOnce: true, 5313 value: attr.drag || false 5314 }); 5315 5316 /** 5317 * @config resize 5318 * @description Set this to true to make the Editor Resizable with YAHOO.util.Resize. The default config is available: myEditor._resizeConfig 5319 * Animation will be ignored while performing this resize to allow for the dynamic change in size of the toolbar. 5320 * @type Boolean 5321 */ 5322 this.setAttributeConfig('resize', { 5323 writeOnce: true, 5324 value: attr.resize || false 5325 }); 5326 5327 /** 5328 * @config filterWord 5329 * @description Attempt to filter out MS Word HTML from the Editor's output. 5330 * @type Boolean 5331 */ 5332 this.setAttributeConfig('filterWord', { 5333 value: attr.filterWord || false, 5334 validator: YAHOO.lang.isBoolean 5335 }); 5336 5337 }, 5338 /** 5339 * @private 5340 * @method _getBlankImage 5341 * @description Retrieves the full url of the image to use as the blank image. 5342 * @return {String} The URL to the blank image 5343 */ 5344 _getBlankImage: function() { 5345 if (!this.DOMReady) { 5346 this._queue[this._queue.length] = ['_getBlankImage', arguments]; 5347 return ''; 5348 } 5349 var img = ''; 5350 if (!this._blankImageLoaded) { 5351 if (YAHOO.widget.EditorInfo.blankImage) { 5352 this.set('blankimage', YAHOO.widget.EditorInfo.blankImage); 5353 this._blankImageLoaded = true; 5354 } else { 5355 var div = document.createElement('div'); 5356 div.style.position = 'absolute'; 5357 div.style.top = '-9999px'; 5358 div.style.left = '-9999px'; 5359 div.className = this.CLASS_PREFIX + '-blankimage'; 5360 document.body.appendChild(div); 5361 img = YAHOO.util.Dom.getStyle(div, 'background-image'); 5362 img = img.replace('url(', '').replace(')', '').replace(/"/g, ''); 5363 //Adobe AIR Code 5364 img = img.replace('app:/', ''); 5365 this.set('blankimage', img); 5366 this._blankImageLoaded = true; 5367 div.parentNode.removeChild(div); 5368 YAHOO.widget.EditorInfo.blankImage = img; 5369 } 5370 } else { 5371 img = this.get('blankimage'); 5372 } 5373 return img; 5374 }, 5375 /** 5376 * @private 5377 * @method _handleAutoHeight 5378 * @description Handles resizing the editor's height based on the content 5379 */ 5380 _handleAutoHeight: function() { 5381 var doc = this._getDoc(), 5382 body = doc.body, 5383 docEl = doc.documentElement; 5384 5385 var height = parseInt(Dom.getStyle(this.get('editor_wrapper'), 'height'), 10); 5386 var newHeight = body.scrollHeight; 5387 if (this.browser.webkit) { 5388 newHeight = docEl.scrollHeight; 5389 } 5390 if (newHeight < parseInt(this.get('height'), 10)) { 5391 newHeight = parseInt(this.get('height'), 10); 5392 } 5393 if ((height != newHeight) && (newHeight >= parseInt(this.get('height'), 10))) { 5394 var anim = this.get('animate'); 5395 this.set('animate', false); 5396 this.set('height', newHeight + 'px'); 5397 this.set('animate', anim); 5398 if (this.browser.ie) { 5399 //Internet Explorer needs this 5400 this.get('iframe').setStyle('height', '99%'); 5401 this.get('iframe').setStyle('zoom', '1'); 5402 var self = this; 5403 window.setTimeout(function() { 5404 self.get('iframe').setStyle('height', '100%'); 5405 }, 1); 5406 } 5407 } 5408 }, 5409 /** 5410 * @private 5411 * @property _formButtons 5412 * @description Array of buttons that are in the Editor's parent form (for handleSubmit) 5413 * @type Array 5414 */ 5415 _formButtons: null, 5416 /** 5417 * @private 5418 * @property _formButtonClicked 5419 * @description The form button that was clicked to submit the form. 5420 * @type HTMLElement 5421 */ 5422 _formButtonClicked: null, 5423 /** 5424 * @private 5425 * @method _handleFormButtonClick 5426 * @description The click listener assigned to each submit button in the Editor's parent form. 5427 * @param {Event} ev The click event 5428 */ 5429 _handleFormButtonClick: function(ev) { 5430 var tar = Event.getTarget(ev); 5431 this._formButtonClicked = tar; 5432 }, 5433 /** 5434 * @private 5435 * @method _handleFormSubmit 5436 * @description Handles the form submission. 5437 * @param {Object} ev The Form Submit Event 5438 */ 5439 _handleFormSubmit: function(ev) { 5440 this.saveHTML(); 5441 5442 var form = this.get('element').form, 5443 tar = this._formButtonClicked || false; 5444 5445 Event.removeListener(form, 'submit', this._handleFormSubmit); 5446 if (YAHOO.env.ua.ie) { 5447 //form.fireEvent("onsubmit"); 5448 if (tar && !tar.disabled) { 5449 tar.click(); 5450 } 5451 } else { // Gecko, Opera, and Safari 5452 if (tar && !tar.disabled) { 5453 tar.click(); 5454 } 5455 var oEvent = document.createEvent("HTMLEvents"); 5456 oEvent.initEvent("submit", true, true); 5457 form.dispatchEvent(oEvent); 5458 if (YAHOO.env.ua.webkit) { 5459 if (YAHOO.lang.isFunction(form.submit)) { 5460 form.submit(); 5461 } 5462 } 5463 } 5464 //2.6.0 5465 //Removed this, not need since removing Safari 2.x 5466 //Event.stopEvent(ev); 5467 }, 5468 /** 5469 * @private 5470 * @method _handleFontSize 5471 * @description Handles the font size button in the toolbar. 5472 * @param {Object} o Object returned from Toolbar's buttonClick Event 5473 */ 5474 _handleFontSize: function(o) { 5475 var button = this.toolbar.getButtonById(o.button.id); 5476 var value = button.get('label') + 'px'; 5477 this.execCommand('fontsize', value); 5478 return false; 5479 }, 5480 /** 5481 * @private 5482 * @description Handles the colorpicker buttons in the toolbar. 5483 * @param {Object} o Object returned from Toolbar's buttonClick Event 5484 */ 5485 _handleColorPicker: function(o) { 5486 var cmd = o.button; 5487 var value = '#' + o.color; 5488 if ((cmd == 'forecolor') || (cmd == 'backcolor')) { 5489 this.execCommand(cmd, value); 5490 } 5491 }, 5492 /** 5493 * @private 5494 * @method _handleAlign 5495 * @description Handles the alignment buttons in the toolbar. 5496 * @param {Object} o Object returned from Toolbar's buttonClick Event 5497 */ 5498 _handleAlign: function(o) { 5499 var cmd = null; 5500 for (var i = 0; i < o.button.menu.length; i++) { 5501 if (o.button.menu[i].value == o.button.value) { 5502 cmd = o.button.menu[i].value; 5503 } 5504 } 5505 var value = this._getSelection(); 5506 5507 this.execCommand(cmd, value); 5508 return false; 5509 }, 5510 /** 5511 * @private 5512 * @method _handleAfterNodeChange 5513 * @description Fires after a nodeChange happens to setup the things that where reset on the node change (button state). 5514 */ 5515 _handleAfterNodeChange: function() { 5516 var path = this._getDomPath(), 5517 elm = null, 5518 family = null, 5519 fontsize = null, 5520 validFont = false, 5521 fn_button = this.toolbar.getButtonByValue('fontname'), 5522 fs_button = this.toolbar.getButtonByValue('fontsize'), 5523 hd_button = this.toolbar.getButtonByValue('heading'); 5524 5525 for (var i = 0; i < path.length; i++) { 5526 elm = path[i]; 5527 5528 var tag = elm.tagName.toLowerCase(); 5529 5530 5531 if (elm.getAttribute('tag')) { 5532 tag = elm.getAttribute('tag'); 5533 } 5534 5535 family = elm.getAttribute('face'); 5536 if (Dom.getStyle(elm, 'font-family')) { 5537 family = Dom.getStyle(elm, 'font-family'); 5538 //Adobe AIR Code 5539 family = family.replace(/'/g, ''); 5540 } 5541 5542 if (tag.substring(0, 1) == 'h') { 5543 if (hd_button) { 5544 for (var h = 0; h < hd_button._configs.menu.value.length; h++) { 5545 if (hd_button._configs.menu.value[h].value.toLowerCase() == tag) { 5546 hd_button.set('label', hd_button._configs.menu.value[h].text); 5547 } 5548 } 5549 this._updateMenuChecked('heading', tag); 5550 } 5551 } 5552 } 5553 5554 if (fn_button) { 5555 for (var b = 0; b < fn_button._configs.menu.value.length; b++) { 5556 if (family && fn_button._configs.menu.value[b].text.toLowerCase() == family.toLowerCase()) { 5557 validFont = true; 5558 family = fn_button._configs.menu.value[b].text; //Put the proper menu name in the button 5559 } 5560 } 5561 if (!validFont) { 5562 family = fn_button._configs.label._initialConfig.value; 5563 } 5564 var familyLabel = '<span class="yui-toolbar-fontname-' + this._cleanClassName(family) + '">' + family + '</span>'; 5565 if (fn_button.get('label') != familyLabel) { 5566 fn_button.set('label', familyLabel); 5567 this._updateMenuChecked('fontname', family); 5568 } 5569 } 5570 5571 if (fs_button) { 5572 fontsize = parseInt(Dom.getStyle(elm, 'fontSize'), 10); 5573 if ((fontsize === null) || isNaN(fontsize)) { 5574 fontsize = fs_button._configs.label._initialConfig.value; 5575 } 5576 fs_button.set('label', ''+fontsize); 5577 } 5578 5579 if (!this._isElement(elm, 'body') && !this._isElement(elm, 'img')) { 5580 this.toolbar.enableButton(fn_button); 5581 this.toolbar.enableButton(fs_button); 5582 this.toolbar.enableButton('forecolor'); 5583 this.toolbar.enableButton('backcolor'); 5584 } 5585 if (this._isElement(elm, 'img')) { 5586 if (YAHOO.widget.Overlay) { 5587 this.toolbar.enableButton('createlink'); 5588 } 5589 } 5590 if (this._hasParent(elm, 'blockquote')) { 5591 this.toolbar.selectButton('indent'); 5592 this.toolbar.disableButton('indent'); 5593 this.toolbar.enableButton('outdent'); 5594 } 5595 if (this._hasParent(elm, 'ol') || this._hasParent(elm, 'ul')) { 5596 this.toolbar.disableButton('indent'); 5597 } 5598 this._lastButton = null; 5599 5600 }, 5601 /** 5602 * @private 5603 * @method _handleInsertImageClick 5604 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked. 5605 */ 5606 _handleInsertImageClick: function() { 5607 if (this.get('limitCommands')) { 5608 if (!this.toolbar.getButtonByValue('insertimage')) { 5609 return false; 5610 } 5611 } 5612 5613 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing 5614 var _handleAEC = function() { 5615 var el = this.currentElement[0], 5616 src = 'http://'; 5617 if (!el) { 5618 el = this._getSelectedElement(); 5619 } 5620 if (el) { 5621 if (el.getAttribute('src')) { 5622 src = el.getAttribute('src', 2); 5623 if (src.indexOf(this.get('blankimage')) != -1) { 5624 src = this.STR_IMAGE_HERE; 5625 } 5626 } 5627 } 5628 var str = prompt(this.STR_IMAGE_URL + ': ', src); 5629 if ((str !== '') && (str !== null)) { 5630 el.setAttribute('src', str); 5631 } else if (str === '') { 5632 el.parentNode.removeChild(el); 5633 this.currentElement = []; 5634 this.nodeChange(); 5635 } else if ((str === null)) { 5636 src = el.getAttribute('src', 2); 5637 if (src.indexOf(this.get('blankimage')) != -1) { 5638 el.parentNode.removeChild(el); 5639 this.currentElement = []; 5640 this.nodeChange(); 5641 } 5642 } 5643 this.closeWindow(); 5644 this.toolbar.set('disabled', false); 5645 this.unsubscribe('afterExecCommand', _handleAEC, this, true); 5646 }; 5647 this.on('afterExecCommand', _handleAEC, this, true); 5648 }, 5649 /** 5650 * @private 5651 * @method _handleInsertImageWindowClose 5652 * @description Handles the closing of the Image Properties Window. 5653 */ 5654 _handleInsertImageWindowClose: function() { 5655 this.nodeChange(); 5656 }, 5657 /** 5658 * @private 5659 * @method _isLocalFile 5660 * @param {String} url THe url/string to check 5661 * @description Checks to see if a string (href or img src) is possibly a local file reference.. 5662 */ 5663 _isLocalFile: function(url) { 5664 if ((url) && (url !== '') && ((url.indexOf('file:/') != -1) || (url.indexOf(':\\') != -1))) { 5665 return true; 5666 } 5667 return false; 5668 }, 5669 /** 5670 * @private 5671 * @method _handleCreateLinkClick 5672 * @description Handles the opening of the Link Properties Window when the Create Link button is clicked or an href is doubleclicked. 5673 */ 5674 _handleCreateLinkClick: function() { 5675 if (this.get('limitCommands')) { 5676 if (!this.toolbar.getButtonByValue('createlink')) { 5677 return false; 5678 } 5679 } 5680 5681 this.toolbar.set('disabled', true); //Disable the toolbar when the prompt is showing 5682 5683 var _handleAEC = function() { 5684 var el = this.currentElement[0], 5685 url = ''; 5686 5687 if (el) { 5688 if (el.getAttribute('href', 2) !== null) { 5689 url = el.getAttribute('href', 2); 5690 } 5691 } 5692 var str = prompt(this.STR_LINK_URL + ': ', url); 5693 if ((str !== '') && (str !== null)) { 5694 var urlValue = str; 5695 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 5696 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 5697 //Found an @ sign, prefix with mailto: 5698 urlValue = 'mailto:' + urlValue; 5699 } else { 5700 /* :// not found adding */ 5701 if (urlValue.substring(0, 1) != '#') { 5702 //urlValue = 'http:/'+'/' + urlValue; 5703 } 5704 } 5705 } 5706 el.setAttribute('href', urlValue); 5707 } else if (str !== null) { 5708 var _span = this._getDoc().createElement('span'); 5709 _span.innerHTML = el.innerHTML; 5710 Dom.addClass(_span, 'yui-non'); 5711 el.parentNode.replaceChild(_span, el); 5712 } 5713 this.closeWindow(); 5714 this.toolbar.set('disabled', false); 5715 this.unsubscribe('afterExecCommand', _handleAEC, this, true); 5716 }; 5717 this.on('afterExecCommand', _handleAEC, this); 5718 5719 }, 5720 /** 5721 * @private 5722 * @method _handleCreateLinkWindowClose 5723 * @description Handles the closing of the Link Properties Window. 5724 */ 5725 _handleCreateLinkWindowClose: function() { 5726 this.nodeChange(); 5727 this.currentElement = []; 5728 }, 5729 /** 5730 * @method render 5731 * @description Calls the private method _render in a setTimeout to allow for other things on the page to continue to load. 5732 */ 5733 render: function() { 5734 if (this._rendered) { 5735 return false; 5736 } 5737 if (!this.DOMReady) { 5738 this._queue[this._queue.length] = ['render', arguments]; 5739 return false; 5740 } 5741 if (this.get('element')) { 5742 if (this.get('element').tagName) { 5743 this._textarea = true; 5744 if (this.get('element').tagName.toLowerCase() !== 'textarea') { 5745 this._textarea = false; 5746 } 5747 } else { 5748 return false; 5749 } 5750 } else { 5751 return false; 5752 } 5753 this._rendered = true; 5754 var self = this; 5755 window.setTimeout(function() { 5756 self._render.call(self); 5757 }, 4); 5758 }, 5759 /** 5760 * @private 5761 * @method _render 5762 * @description Causes the toolbar and the editor to render and replace the textarea. 5763 */ 5764 _render: function() { 5765 var self = this; 5766 this.set('textarea', this.get('element')); 5767 5768 this.get('element_cont').setStyle('display', 'none'); 5769 this.get('element_cont').addClass(this.CLASS_CONTAINER); 5770 5771 this.set('iframe', this._createIframe()); 5772 5773 window.setTimeout(function() { 5774 self._setInitialContent.call(self); 5775 }, 10); 5776 5777 this.get('editor_wrapper').appendChild(this.get('iframe').get('element')); 5778 5779 if (this.get('disabled')) { 5780 this._disableEditor(true); 5781 } 5782 5783 var tbarConf = this.get('toolbar'); 5784 //Create Toolbar instance 5785 if (tbarConf instanceof Toolbar) { 5786 this.toolbar = tbarConf; 5787 //Set the toolbar to disabled until content is loaded 5788 this.toolbar.set('disabled', true); 5789 } else { 5790 //Set the toolbar to disabled until content is loaded 5791 tbarConf.disabled = true; 5792 this.toolbar = new Toolbar(this.get('toolbar_cont'), tbarConf); 5793 } 5794 5795 this.fireEvent('toolbarLoaded', { type: 'toolbarLoaded', target: this.toolbar }); 5796 5797 5798 this.toolbar.on('toolbarCollapsed', function() { 5799 if (this.currentWindow) { 5800 this.moveWindow(); 5801 } 5802 }, this, true); 5803 this.toolbar.on('toolbarExpanded', function() { 5804 if (this.currentWindow) { 5805 this.moveWindow(); 5806 } 5807 }, this, true); 5808 this.toolbar.on('fontsizeClick', this._handleFontSize, this, true); 5809 5810 this.toolbar.on('colorPickerClicked', function(o) { 5811 this._handleColorPicker(o); 5812 return false; //Stop the buttonClick event 5813 }, this, true); 5814 5815 this.toolbar.on('alignClick', this._handleAlign, this, true); 5816 this.on('afterNodeChange', this._handleAfterNodeChange, this, true); 5817 this.toolbar.on('insertimageClick', this._handleInsertImageClick, this, true); 5818 this.on('windowinsertimageClose', this._handleInsertImageWindowClose, this, true); 5819 this.toolbar.on('createlinkClick', this._handleCreateLinkClick, this, true); 5820 this.on('windowcreatelinkClose', this._handleCreateLinkWindowClose, this, true); 5821 5822 5823 //Replace Textarea with editable area 5824 this.get('parentNode').replaceChild(this.get('element_cont').get('element'), this.get('element')); 5825 5826 5827 this.setStyle('visibility', 'hidden'); 5828 this.setStyle('position', 'absolute'); 5829 this.setStyle('top', '-9999px'); 5830 this.setStyle('left', '-9999px'); 5831 this.get('element_cont').appendChild(this.get('element')); 5832 this.get('element_cont').setStyle('display', 'block'); 5833 5834 5835 Dom.addClass(this.get('iframe').get('parentNode'), this.CLASS_EDITABLE_CONT); 5836 this.get('iframe').addClass(this.CLASS_EDITABLE); 5837 5838 //Set height and width of editor container 5839 this.get('element_cont').setStyle('width', this.get('width')); 5840 Dom.setStyle(this.get('iframe').get('parentNode'), 'height', this.get('height')); 5841 5842 this.get('iframe').setStyle('width', '100%'); //WIDTH 5843 this.get('iframe').setStyle('height', '100%'); 5844 5845 this._setupDD(); 5846 5847 window.setTimeout(function() { 5848 self._setupAfterElement.call(self); 5849 }, 0); 5850 this.fireEvent('afterRender', { type: 'afterRender', target: this }); 5851 }, 5852 /** 5853 * @method execCommand 5854 * @param {String} action The "execCommand" action to try to execute (Example: bold, insertimage, inserthtml) 5855 * @param {String} value (optional) The value for a given action such as action: fontname value: 'Verdana' 5856 * @description This method attempts to try and level the differences in the various browsers and their support for execCommand actions 5857 */ 5858 execCommand: function(action, value) { 5859 var beforeExec = this.fireEvent('beforeExecCommand', { type: 'beforeExecCommand', target: this, args: arguments }); 5860 if ((beforeExec === false) || (this.STOP_EXEC_COMMAND)) { 5861 this.STOP_EXEC_COMMAND = false; 5862 return false; 5863 } 5864 this._lastCommand = action; 5865 this._setMarkupType(action); 5866 if (this.browser.ie) { 5867 this._getWindow().focus(); 5868 } 5869 var exec = true; 5870 5871 if (this.get('limitCommands')) { 5872 if (!this.toolbar.getButtonByValue(action)) { 5873 exec = false; 5874 } 5875 } 5876 5877 this.editorDirty = true; 5878 5879 if ((typeof this['cmd_' + action.toLowerCase()] == 'function') && exec) { 5880 var retValue = this['cmd_' + action.toLowerCase()](value); 5881 exec = retValue[0]; 5882 if (retValue[1]) { 5883 action = retValue[1]; 5884 } 5885 if (retValue[2]) { 5886 value = retValue[2]; 5887 } 5888 } 5889 if (exec) { 5890 try { 5891 this._getDoc().execCommand(action, false, value); 5892 } catch(e) { 5893 } 5894 } else { 5895 } 5896 this.on('afterExecCommand', function() { 5897 this.unsubscribeAll('afterExecCommand'); 5898 this.nodeChange(); 5899 }, this, true); 5900 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 5901 5902 }, 5903 /* {{{ Command Overrides */ 5904 5905 /** 5906 * @method cmd_bold 5907 * @param value Value passed from the execCommand method 5908 * @description This is an execCommand override method. It is called from execCommand when the execCommand('bold') is used. 5909 */ 5910 cmd_bold: function(value) { 5911 if (!this.browser.webkit) { 5912 var el = this._getSelectedElement(); 5913 if (el && this._isElement(el, 'span') && this._hasSelection()) { 5914 if (el.style.fontWeight == 'bold') { 5915 el.style.fontWeight = ''; 5916 var b = this._getDoc().createElement('b'), 5917 par = el.parentNode; 5918 par.replaceChild(b, el); 5919 b.appendChild(el); 5920 } 5921 } 5922 } 5923 return [true]; 5924 }, 5925 /** 5926 * @method cmd_italic 5927 * @param value Value passed from the execCommand method 5928 * @description This is an execCommand override method. It is called from execCommand when the execCommand('italic') is used. 5929 */ 5930 5931 cmd_italic: function(value) { 5932 if (!this.browser.webkit) { 5933 var el = this._getSelectedElement(); 5934 if (el && this._isElement(el, 'span') && this._hasSelection()) { 5935 if (el.style.fontStyle == 'italic') { 5936 el.style.fontStyle = ''; 5937 var i = this._getDoc().createElement('i'), 5938 par = el.parentNode; 5939 par.replaceChild(i, el); 5940 i.appendChild(el); 5941 } 5942 } 5943 } 5944 return [true]; 5945 }, 5946 5947 5948 /** 5949 * @method cmd_underline 5950 * @param value Value passed from the execCommand method 5951 * @description This is an execCommand override method. It is called from execCommand when the execCommand('underline') is used. 5952 */ 5953 cmd_underline: function(value) { 5954 if (!this.browser.webkit) { 5955 var el = this._getSelectedElement(); 5956 if (el && this._isElement(el, 'span')) { 5957 if (el.style.textDecoration == 'underline') { 5958 el.style.textDecoration = 'none'; 5959 } else { 5960 el.style.textDecoration = 'underline'; 5961 } 5962 return [false]; 5963 } 5964 } 5965 return [true]; 5966 }, 5967 /** 5968 * @method cmd_backcolor 5969 * @param value Value passed from the execCommand method 5970 * @description This is an execCommand override method. It is called from execCommand when the execCommand('backcolor') is used. 5971 */ 5972 cmd_backcolor: function(value) { 5973 var exec = true, 5974 el = this._getSelectedElement(), 5975 action = 'backcolor'; 5976 5977 if (this.browser.gecko || this.browser.opera) { 5978 this._setEditorStyle(true); 5979 action = 'hilitecolor'; 5980 } 5981 5982 if (!this._isElement(el, 'body') && !this._hasSelection()) { 5983 el.style.backgroundColor = value; 5984 this._selectNode(el); 5985 exec = false; 5986 } else { 5987 if (this.get('insert')) { 5988 el = this._createInsertElement({ backgroundColor: value }); 5989 } else { 5990 this._createCurrentElement('span', { backgroundColor: value, color: el.style.color, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily }); 5991 this._selectNode(this.currentElement[0]); 5992 } 5993 exec = false; 5994 } 5995 5996 return [exec, action]; 5997 }, 5998 /** 5999 * @method cmd_forecolor 6000 * @param value Value passed from the execCommand method 6001 * @description This is an execCommand override method. It is called from execCommand when the execCommand('forecolor') is used. 6002 */ 6003 cmd_forecolor: function(value) { 6004 var exec = true, 6005 el = this._getSelectedElement(); 6006 6007 if (!this._isElement(el, 'body') && !this._hasSelection()) { 6008 Dom.setStyle(el, 'color', value); 6009 this._selectNode(el); 6010 exec = false; 6011 } else { 6012 if (this.get('insert')) { 6013 el = this._createInsertElement({ color: value }); 6014 } else { 6015 this._createCurrentElement('span', { color: value, fontSize: el.style.fontSize, fontFamily: el.style.fontFamily, backgroundColor: el.style.backgroundColor }); 6016 this._selectNode(this.currentElement[0]); 6017 } 6018 exec = false; 6019 } 6020 return [exec]; 6021 }, 6022 /** 6023 * @method cmd_unlink 6024 * @param value Value passed from the execCommand method 6025 * @description This is an execCommand override method. It is called from execCommand when the execCommand('unlink') is used. 6026 */ 6027 cmd_unlink: function(value) { 6028 this._swapEl(this.currentElement[0], 'span', function(el) { 6029 el.className = 'yui-non'; 6030 }); 6031 return [false]; 6032 }, 6033 /** 6034 * @method cmd_createlink 6035 * @param value Value passed from the execCommand method 6036 * @description This is an execCommand override method. It is called from execCommand when the execCommand('createlink') is used. 6037 */ 6038 cmd_createlink: function(value) { 6039 var el = this._getSelectedElement(), _a = null; 6040 if (this._hasParent(el, 'a')) { 6041 this.currentElement[0] = this._hasParent(el, 'a'); 6042 } else if (this._isElement(el, 'li')) { 6043 _a = this._getDoc().createElement('a'); 6044 _a.innerHTML = el.innerHTML; 6045 el.innerHTML = ''; 6046 el.appendChild(_a); 6047 this.currentElement[0] = _a; 6048 } else if (!this._isElement(el, 'a')) { 6049 this._createCurrentElement('a'); 6050 _a = this._swapEl(this.currentElement[0], 'a'); 6051 this.currentElement[0] = _a; 6052 } else { 6053 this.currentElement[0] = el; 6054 } 6055 return [false]; 6056 }, 6057 /** 6058 * @method cmd_insertimage 6059 * @param value Value passed from the execCommand method 6060 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertimage') is used. 6061 */ 6062 cmd_insertimage: function(value) { 6063 var exec = true, _img = null, action = 'insertimage', 6064 el = this._getSelectedElement(); 6065 6066 if (value === '') { 6067 value = this.get('blankimage'); 6068 } 6069 6070 /* 6071 * @knownissue Safari Cursor Position 6072 * @browser Safari 2.x 6073 * @description The issue here is that we have no way of knowing where the cursor position is 6074 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6075 */ 6076 6077 if (this._isElement(el, 'img')) { 6078 this.currentElement[0] = el; 6079 exec = false; 6080 } else { 6081 if (this._getDoc().queryCommandEnabled(action)) { 6082 this._getDoc().execCommand(action, false, value); 6083 var imgs = this._getDoc().getElementsByTagName('img'); 6084 for (var i = 0; i < imgs.length; i++) { 6085 if (!YAHOO.util.Dom.hasClass(imgs[i], 'yui-img')) { 6086 YAHOO.util.Dom.addClass(imgs[i], 'yui-img'); 6087 this.currentElement[0] = imgs[i]; 6088 } 6089 } 6090 exec = false; 6091 } else { 6092 if (el == this._getDoc().body) { 6093 _img = this._getDoc().createElement('img'); 6094 _img.setAttribute('src', value); 6095 YAHOO.util.Dom.addClass(_img, 'yui-img'); 6096 this._getDoc().body.appendChild(_img); 6097 } else { 6098 this._createCurrentElement('img'); 6099 _img = this._getDoc().createElement('img'); 6100 _img.setAttribute('src', value); 6101 YAHOO.util.Dom.addClass(_img, 'yui-img'); 6102 this.currentElement[0].parentNode.replaceChild(_img, this.currentElement[0]); 6103 } 6104 this.currentElement[0] = _img; 6105 exec = false; 6106 } 6107 } 6108 return [exec]; 6109 }, 6110 /** 6111 * @method cmd_inserthtml 6112 * @param value Value passed from the execCommand method 6113 * @description This is an execCommand override method. It is called from execCommand when the execCommand('inserthtml') is used. 6114 */ 6115 cmd_inserthtml: function(value) { 6116 var exec = true, action = 'inserthtml', _span = null, _range = null; 6117 /* 6118 * @knownissue Safari cursor position 6119 * @browser Safari 2.x 6120 * @description The issue here is that we have no way of knowing where the cursor position is 6121 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6122 */ 6123 if (this.browser.webkit && !this._getDoc().queryCommandEnabled(action)) { 6124 this._createCurrentElement('img'); 6125 _span = this._getDoc().createElement('span'); 6126 _span.innerHTML = value; 6127 this.currentElement[0].parentNode.replaceChild(_span, this.currentElement[0]); 6128 exec = false; 6129 } else if (this.browser.ie) { 6130 _range = this._getRange(); 6131 if (_range.item) { 6132 _range.item(0).outerHTML = value; 6133 } else { 6134 _range.pasteHTML(value); 6135 } 6136 exec = false; 6137 } 6138 return [exec]; 6139 }, 6140 /** 6141 * @method cmd_list 6142 * @param tag The tag of the list you want to create (eg, ul or ol) 6143 * @description This is a combined execCommand override method. It is called from the cmd_insertorderedlist and cmd_insertunorderedlist methods. 6144 */ 6145 cmd_list: function(tag) { 6146 var exec = true, list = null, li = 0, el = null, str = '', 6147 selEl = this._getSelectedElement(), action = 'insertorderedlist'; 6148 if (tag == 'ul') { 6149 action = 'insertunorderedlist'; 6150 } 6151 /* 6152 * @knownissue Safari 2.+ doesn't support ordered and unordered lists 6153 * @browser Safari 2.x 6154 * The issue with this workaround is that when applied to a set of text 6155 * that has BR's in it, Safari may or may not pick up the individual items as 6156 * list items. This is fixed in WebKit (Safari 3) 6157 * 2.6.0: Seems there are still some issues with List Creation and Safari 3, reverting to previously working Safari 2.x code 6158 */ 6159 //if ((this.browser.webkit && !this._getDoc().queryCommandEnabled(action))) { 6160 if ((this.browser.webkit && !this.browser.webkit4) || (this.browser.opera)) { 6161 if (this._isElement(selEl, 'li') && this._isElement(selEl.parentNode, tag)) { 6162 el = selEl.parentNode; 6163 list = this._getDoc().createElement('span'); 6164 YAHOO.util.Dom.addClass(list, 'yui-non'); 6165 str = ''; 6166 var lis = el.getElementsByTagName('li'), p_tag = ((this.browser.opera && this.get('ptags')) ? 'p' : 'div'); 6167 for (li = 0; li < lis.length; li++) { 6168 str += '<' + p_tag + '>' + lis[li].innerHTML + '</' + p_tag + '>'; 6169 } 6170 list.innerHTML = str; 6171 this.currentElement[0] = el; 6172 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]); 6173 } else { 6174 this._createCurrentElement(tag.toLowerCase()); 6175 list = this._getDoc().createElement(tag); 6176 for (li = 0; li < this.currentElement.length; li++) { 6177 var newli = this._getDoc().createElement('li'); 6178 newli.innerHTML = this.currentElement[li].innerHTML + '<span class="yui-non"> </span> '; 6179 list.appendChild(newli); 6180 if (li > 0) { 6181 this.currentElement[li].parentNode.removeChild(this.currentElement[li]); 6182 } 6183 } 6184 var b_tag = ((this.browser.opera) ? '<BR>' : '<br>'), 6185 items = list.firstChild.innerHTML.split(b_tag), i, item; 6186 if (items.length > 0) { 6187 list.innerHTML = ''; 6188 for (i = 0; i < items.length; i++) { 6189 item = this._getDoc().createElement('li'); 6190 item.innerHTML = items[i]; 6191 list.appendChild(item); 6192 } 6193 } 6194 6195 this.currentElement[0].parentNode.replaceChild(list, this.currentElement[0]); 6196 this.currentElement[0] = list; 6197 var _h = this.currentElement[0].firstChild; 6198 _h = Dom.getElementsByClassName('yui-non', 'span', _h)[0]; 6199 if (this.browser.webkit) { 6200 this._getSelection().setBaseAndExtent(_h, 1, _h, _h.innerText.length); 6201 } 6202 } 6203 exec = false; 6204 } else { 6205 el = this._getSelectedElement(); 6206 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.. 6207 if (this.browser.ie) { 6208 if ((this.browser.ie && this._isElement(el, 'ul')) || (this.browser.ie && this._isElement(el, 'ol'))) { 6209 el = el.getElementsByTagName('li')[0]; 6210 } 6211 str = ''; 6212 var lis2 = el.parentNode.getElementsByTagName('li'); 6213 for (var j = 0; j < lis2.length; j++) { 6214 str += lis2[j].innerHTML + '<br>'; 6215 } 6216 var newEl = this._getDoc().createElement('span'); 6217 newEl.innerHTML = str; 6218 el.parentNode.parentNode.replaceChild(newEl, el.parentNode); 6219 } else { 6220 this.nodeChange(); 6221 this._getDoc().execCommand(action, '', el.parentNode); 6222 this.nodeChange(); 6223 } 6224 exec = false; 6225 } 6226 if (this.browser.opera) { 6227 var self = this; 6228 window.setTimeout(function() { 6229 var liso = self._getDoc().getElementsByTagName('li'); 6230 for (var i = 0; i < liso.length; i++) { 6231 if (liso[i].innerHTML.toLowerCase() == '<br>') { 6232 liso[i].parentNode.parentNode.removeChild(liso[i].parentNode); 6233 } 6234 } 6235 },30); 6236 } 6237 if (this.browser.ie && exec) { 6238 var html = ''; 6239 if (this._getRange().html) { 6240 html = '<li>' + this._getRange().html+ '</li>'; 6241 } else { 6242 var t = this._getRange().text.split('\n'); 6243 if (t.length > 1) { 6244 html = ''; 6245 for (var ie = 0; ie < t.length; ie++) { 6246 html += '<li>' + t[ie] + '</li>'; 6247 } 6248 } else { 6249 var txt = this._getRange().text; 6250 if (txt === '') { 6251 html = '<li id="new_list_item">' + txt + '</li>'; 6252 } else { 6253 html = '<li>' + txt + '</li>'; 6254 } 6255 } 6256 } 6257 this._getRange().pasteHTML('<' + tag + '>' + html + '</' + tag + '>'); 6258 var new_item = this._getDoc().getElementById('new_list_item'); 6259 if (new_item) { 6260 var range = this._getDoc().body.createTextRange(); 6261 range.moveToElementText(new_item); 6262 range.collapse(false); 6263 range.select(); 6264 new_item.id = ''; 6265 } 6266 exec = false; 6267 } 6268 } 6269 return exec; 6270 }, 6271 /** 6272 * @method cmd_insertorderedlist 6273 * @param value Value passed from the execCommand method 6274 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertorderedlist ') is used. 6275 */ 6276 cmd_insertorderedlist: function(value) { 6277 return [this.cmd_list('ol')]; 6278 }, 6279 /** 6280 * @method cmd_insertunorderedlist 6281 * @param value Value passed from the execCommand method 6282 * @description This is an execCommand override method. It is called from execCommand when the execCommand('insertunorderedlist') is used. 6283 */ 6284 cmd_insertunorderedlist: function(value) { 6285 return [this.cmd_list('ul')]; 6286 }, 6287 /** 6288 * @method cmd_fontname 6289 * @param value Value passed from the execCommand method 6290 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontname') is used. 6291 */ 6292 cmd_fontname: function(value) { 6293 var exec = true, 6294 selEl = this._getSelectedElement(); 6295 6296 this.currentFont = value; 6297 if (selEl && selEl.tagName && !this._hasSelection() && !this._isElement(selEl, 'body') && !this.get('insert')) { 6298 YAHOO.util.Dom.setStyle(selEl, 'font-family', value); 6299 exec = false; 6300 } else if (this.get('insert') && !this._hasSelection()) { 6301 var el = this._createInsertElement({ fontFamily: value }); 6302 exec = false; 6303 } 6304 return [exec]; 6305 }, 6306 /** 6307 * @method cmd_fontsize 6308 * @param value Value passed from the execCommand method 6309 * @description This is an execCommand override method. It is called from execCommand when the execCommand('fontsize') is used. 6310 */ 6311 cmd_fontsize: function(value) { 6312 var el = null, go = true; 6313 el = this._getSelectedElement(); 6314 if (this.browser.webkit) { 6315 if (this.currentElement[0]) { 6316 if (el == this.currentElement[0]) { 6317 go = false; 6318 YAHOO.util.Dom.setStyle(el, 'fontSize', value); 6319 this._selectNode(el); 6320 this.currentElement[0] = el; 6321 } 6322 } 6323 } 6324 if (go) { 6325 if (!this._isElement(this._getSelectedElement(), 'body') && (!this._hasSelection())) { 6326 el = this._getSelectedElement(); 6327 YAHOO.util.Dom.setStyle(el, 'fontSize', value); 6328 if (this.get('insert') && this.browser.ie) { 6329 var r = this._getRange(); 6330 r.collapse(false); 6331 r.select(); 6332 } else { 6333 this._selectNode(el); 6334 } 6335 } else if (this.currentElement && (this.currentElement.length > 0) && (!this._hasSelection()) && (!this.get('insert'))) { 6336 YAHOO.util.Dom.setStyle(this.currentElement, 'fontSize', value); 6337 } else { 6338 if (this.get('insert') && !this._hasSelection()) { 6339 el = this._createInsertElement({ fontSize: value }); 6340 this.currentElement[0] = el; 6341 this._selectNode(this.currentElement[0]); 6342 } else { 6343 this._createCurrentElement('span', {'fontSize': value, fontFamily: el.style.fontFamily, color: el.style.color, backgroundColor: el.style.backgroundColor }); 6344 this._selectNode(this.currentElement[0]); 6345 } 6346 } 6347 } 6348 return [false]; 6349 }, 6350 /* }}} */ 6351 /** 6352 * @private 6353 * @method _swapEl 6354 * @param {HTMLElement} el The element to swap with 6355 * @param {String} tagName The tagname of the element that you wish to create 6356 * @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. 6357 * @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. 6358 */ 6359 _swapEl: function(el, tagName, callback) { 6360 var _el = this._getDoc().createElement(tagName); 6361 if (el) { 6362 _el.innerHTML = el.innerHTML; 6363 } 6364 if (typeof callback == 'function') { 6365 callback.call(this, _el); 6366 } 6367 if (el) { 6368 el.parentNode.replaceChild(_el, el); 6369 } 6370 return _el; 6371 }, 6372 /** 6373 * @private 6374 * @method _createInsertElement 6375 * @description Creates a new "currentElement" then adds some text (and other things) to make it selectable and stylable. Then the user can continue typing. 6376 * @param {Object} css (optional) Object literal containing styles to apply to the new element. 6377 * @return {HTMLElement} 6378 */ 6379 _createInsertElement: function(css) { 6380 this._createCurrentElement('span', css); 6381 var el = this.currentElement[0]; 6382 if (this.browser.webkit) { 6383 //Little Safari Hackery here.. 6384 el.innerHTML = '<span class="yui-non"> </span>'; 6385 el = el.firstChild; 6386 this._getSelection().setBaseAndExtent(el, 1, el, el.innerText.length); 6387 } else if (this.browser.ie || this.browser.opera) { 6388 el.innerHTML = ' '; 6389 } 6390 this.focus(); 6391 this._selectNode(el, true); 6392 return el; 6393 }, 6394 /** 6395 * @private 6396 * @method _createCurrentElement 6397 * @param {String} tagName (optional defaults to a) The tagname of the element that you wish to create 6398 * @param {Object} tagStyle (optional) Object literal containing styles to apply to the new element. 6399 * @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. 6400 * 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 6401 * <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. 6402 */ 6403 _createCurrentElement: function(tagName, tagStyle) { 6404 tagName = ((tagName) ? tagName : 'a'); 6405 var tar = null, 6406 el = [], 6407 _doc = this._getDoc(); 6408 6409 if (this.currentFont) { 6410 if (!tagStyle) { 6411 tagStyle = {}; 6412 } 6413 tagStyle.fontFamily = this.currentFont; 6414 this.currentFont = null; 6415 } 6416 this.currentElement = []; 6417 6418 var _elCreate = function(tagName, tagStyle) { 6419 var el = null; 6420 tagName = ((tagName) ? tagName : 'span'); 6421 tagName = tagName.toLowerCase(); 6422 switch (tagName) { 6423 case 'h1': 6424 case 'h2': 6425 case 'h3': 6426 case 'h4': 6427 case 'h5': 6428 case 'h6': 6429 el = _doc.createElement(tagName); 6430 break; 6431 default: 6432 el = _doc.createElement(tagName); 6433 if (tagName === 'span') { 6434 YAHOO.util.Dom.addClass(el, 'yui-tag-' + tagName); 6435 YAHOO.util.Dom.addClass(el, 'yui-tag'); 6436 el.setAttribute('tag', tagName); 6437 } 6438 6439 for (var k in tagStyle) { 6440 if (YAHOO.lang.hasOwnProperty(tagStyle, k)) { 6441 el.style[k] = tagStyle[k]; 6442 } 6443 } 6444 break; 6445 } 6446 return el; 6447 }; 6448 6449 if (!this._hasSelection()) { 6450 if (this._getDoc().queryCommandEnabled('insertimage')) { 6451 this._getDoc().execCommand('insertimage', false, 'yui-tmp-img'); 6452 var imgs = this._getDoc().getElementsByTagName('img'); 6453 for (var j = 0; j < imgs.length; j++) { 6454 if (imgs[j].getAttribute('src', 2) == 'yui-tmp-img') { 6455 el = _elCreate(tagName, tagStyle); 6456 imgs[j].parentNode.replaceChild(el, imgs[j]); 6457 this.currentElement[this.currentElement.length] = el; 6458 } 6459 } 6460 } else { 6461 if (this.currentEvent) { 6462 tar = YAHOO.util.Event.getTarget(this.currentEvent); 6463 } else { 6464 //For Safari.. 6465 tar = this._getDoc().body; 6466 } 6467 } 6468 if (tar) { 6469 /* 6470 * @knownissue Safari Cursor Position 6471 * @browser Safari 2.x 6472 * @description The issue here is that we have no way of knowing where the cursor position is 6473 * inside of the iframe, so we have to place the newly inserted data in the best place that we can. 6474 */ 6475 el = _elCreate(tagName, tagStyle); 6476 if (this._isElement(tar, 'body') || this._isElement(tar, 'html')) { 6477 if (this._isElement(tar, 'html')) { 6478 tar = this._getDoc().body; 6479 } 6480 tar.appendChild(el); 6481 } else if (tar.nextSibling) { 6482 tar.parentNode.insertBefore(el, tar.nextSibling); 6483 } else { 6484 tar.parentNode.appendChild(el); 6485 } 6486 //this.currentElement = el; 6487 this.currentElement[this.currentElement.length] = el; 6488 this.currentEvent = null; 6489 if (this.browser.webkit) { 6490 //Force Safari to focus the new element 6491 this._getSelection().setBaseAndExtent(el, 0, el, 0); 6492 if (this.browser.webkit3) { 6493 this._getSelection().collapseToStart(); 6494 } else { 6495 this._getSelection().collapse(true); 6496 } 6497 } 6498 } 6499 } else { 6500 //Force CSS Styling for this action... 6501 this._setEditorStyle(true); 6502 this._getDoc().execCommand('fontname', false, 'yui-tmp'); 6503 var _tmp = [], __tmp, __els = ['font', 'span', 'i', 'b', 'u']; 6504 6505 if (!this._isElement(this._getSelectedElement(), 'body')) { 6506 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().tagName); 6507 __els[__els.length] = this._getDoc().getElementsByTagName(this._getSelectedElement().parentNode.tagName); 6508 } 6509 for (var _els = 0; _els < __els.length; _els++) { 6510 var _tmp1 = this._getDoc().getElementsByTagName(__els[_els]); 6511 for (var e = 0; e < _tmp1.length; e++) { 6512 _tmp[_tmp.length] = _tmp1[e]; 6513 } 6514 } 6515 6516 6517 for (var i = 0; i < _tmp.length; i++) { 6518 if ((YAHOO.util.Dom.getStyle(_tmp[i], 'font-family') == 'yui-tmp') || (_tmp[i].face && (_tmp[i].face == 'yui-tmp'))) { 6519 if (tagName !== 'span') { 6520 el = _elCreate(tagName, tagStyle); 6521 } else { 6522 el = _elCreate(_tmp[i].tagName, tagStyle); 6523 } 6524 el.innerHTML = _tmp[i].innerHTML; 6525 if (this._isElement(_tmp[i], 'ol') || (this._isElement(_tmp[i], 'ul'))) { 6526 var fc = _tmp[i].getElementsByTagName('li')[0]; 6527 _tmp[i].style.fontFamily = 'inherit'; 6528 fc.style.fontFamily = 'inherit'; 6529 el.innerHTML = fc.innerHTML; 6530 fc.innerHTML = ''; 6531 fc.appendChild(el); 6532 this.currentElement[this.currentElement.length] = el; 6533 } else if (this._isElement(_tmp[i], 'li')) { 6534 _tmp[i].innerHTML = ''; 6535 _tmp[i].appendChild(el); 6536 _tmp[i].style.fontFamily = 'inherit'; 6537 this.currentElement[this.currentElement.length] = el; 6538 } else { 6539 if (_tmp[i].parentNode) { 6540 _tmp[i].parentNode.replaceChild(el, _tmp[i]); 6541 this.currentElement[this.currentElement.length] = el; 6542 this.currentEvent = null; 6543 if (this.browser.webkit) { 6544 //Force Safari to focus the new element 6545 this._getSelection().setBaseAndExtent(el, 0, el, 0); 6546 if (this.browser.webkit3) { 6547 this._getSelection().collapseToStart(); 6548 } else { 6549 this._getSelection().collapse(true); 6550 } 6551 } 6552 if (this.browser.ie && tagStyle && tagStyle.fontSize) { 6553 this._getSelection().empty(); 6554 } 6555 if (this.browser.gecko) { 6556 this._getSelection().collapseToStart(); 6557 } 6558 } 6559 } 6560 } 6561 } 6562 var len = this.currentElement.length; 6563 for (var o = 0; o < len; o++) { 6564 if ((o + 1) != len) { //Skip the last one in the list 6565 if (this.currentElement[o] && this.currentElement[o].nextSibling) { 6566 if (this._isElement(this.currentElement[o], 'br')) { 6567 this.currentElement[this.currentElement.length] = this.currentElement[o].nextSibling; 6568 } 6569 } 6570 } 6571 } 6572 } 6573 }, 6574 /** 6575 * @method saveHTML 6576 * @description Cleans the HTML with the cleanHTML method then places that string back into the textarea. 6577 * @return String 6578 */ 6579 saveHTML: function() { 6580 var html = this.cleanHTML(); 6581 if (this._textarea) { 6582 this.get('element').value = html; 6583 } else { 6584 this.get('element').innerHTML = html; 6585 } 6586 if (this.get('saveEl') !== this.get('element')) { 6587 var out = this.get('saveEl'); 6588 if (Lang.isString(out)) { 6589 out = Dom.get(out); 6590 } 6591 if (out) { 6592 if (out.tagName.toLowerCase() === 'textarea') { 6593 out.value = html; 6594 } else { 6595 out.innerHTML = html; 6596 } 6597 } 6598 } 6599 return html; 6600 }, 6601 /** 6602 * @method setEditorHTML 6603 * @param {String} incomingHTML The html content to load into the editor 6604 * @description Loads HTML into the editors body 6605 */ 6606 setEditorHTML: function(incomingHTML) { 6607 var html = this._cleanIncomingHTML(incomingHTML); 6608 html = html.replace(/RIGHT_BRACKET/gi, '{'); 6609 html = html.replace(/LEFT_BRACKET/gi, '}'); 6610 this._getDoc().body.innerHTML = html; 6611 this.nodeChange(); 6612 }, 6613 /** 6614 * @method getEditorHTML 6615 * @description Gets the unprocessed/unfiltered HTML from the editor 6616 */ 6617 getEditorHTML: function() { 6618 try { 6619 var b = this._getDoc().body; 6620 if (b === null) { 6621 return null; 6622 } 6623 return this._getDoc().body.innerHTML; 6624 } catch (e) { 6625 return ''; 6626 } 6627 }, 6628 /** 6629 * @method show 6630 * @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. 6631 */ 6632 show: function() { 6633 if (this.browser.gecko) { 6634 this._setDesignMode('on'); 6635 this.focus(); 6636 } 6637 if (this.browser.webkit) { 6638 var self = this; 6639 window.setTimeout(function() { 6640 self._setInitialContent.call(self); 6641 }, 10); 6642 } 6643 //Adding this will close all other Editor window's when showing this one. 6644 if (this.currentWindow) { 6645 this.closeWindow(); 6646 } 6647 //Put the iframe back in place 6648 this.get('iframe').setStyle('position', 'static'); 6649 this.get('iframe').setStyle('left', ''); 6650 }, 6651 /** 6652 * @method hide 6653 * @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. 6654 */ 6655 hide: function() { 6656 //Adding this will close all other Editor window's. 6657 if (this.currentWindow) { 6658 this.closeWindow(); 6659 } 6660 if (this._fixNodesTimer) { 6661 clearTimeout(this._fixNodesTimer); 6662 this._fixNodesTimer = null; 6663 } 6664 if (this._nodeChangeTimer) { 6665 clearTimeout(this._nodeChangeTimer); 6666 this._nodeChangeTimer = null; 6667 } 6668 this._lastNodeChange = 0; 6669 //Move the iframe off of the screen, so that in containers with visiblity hidden, IE will not cover other elements. 6670 this.get('iframe').setStyle('position', 'absolute'); 6671 this.get('iframe').setStyle('left', '-9999px'); 6672 }, 6673 /** 6674 * @method _cleanIncomingHTML 6675 * @param {String} html The unfiltered HTML 6676 * @description Process the HTML with a few regexes to clean it up and stabilize the input 6677 * @return {String} The filtered HTML 6678 */ 6679 _cleanIncomingHTML: function(html) { 6680 html = html.replace(/{/gi, 'RIGHT_BRACKET'); 6681 html = html.replace(/}/gi, 'LEFT_BRACKET'); 6682 6683 html = html.replace(/<strong([^>]*)>/gi, '<b$1>'); 6684 html = html.replace(/<\/strong>/gi, '</b>'); 6685 6686 //replace embed before em check 6687 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>'); 6688 html = html.replace(/<\/embed>/gi, '</YUI_EMBED>'); 6689 6690 html = html.replace(/<em([^>]*)>/gi, '<i$1>'); 6691 html = html.replace(/<\/em>/gi, '</i>'); 6692 html = html.replace(/_moz_dirty=""/gi, ''); 6693 6694 //Put embed tags back in.. 6695 html = html.replace(/<YUI_EMBED([^>]*)>/gi, '<embed$1>'); 6696 html = html.replace(/<\/YUI_EMBED>/gi, '</embed>'); 6697 if (this.get('plainText')) { 6698 html = html.replace(/\n/g, '<br>').replace(/\r/g, '<br>'); 6699 html = html.replace(/ /gi, ' '); //Replace all double spaces 6700 html = html.replace(/\t/gi, ' '); //Replace all tabs 6701 } 6702 //Removing Script Tags from the Editor 6703 html = html.replace(/<script([^>]*)>/gi, '<bad>'); 6704 html = html.replace(/<\/script([^>]*)>/gi, '</bad>'); 6705 html = html.replace(/<script([^>]*)>/gi, '<bad>'); 6706 html = html.replace(/<\/script([^>]*)>/gi, '</bad>'); 6707 //Replace the line feeds 6708 html = html.replace(/\r\n/g, '<YUI_LF>').replace(/\n/g, '<YUI_LF>').replace(/\r/g, '<YUI_LF>'); 6709 6710 //Remove Bad HTML elements (used to be script nodes) 6711 html = html.replace(new RegExp('<bad([^>]*)>(.*?)<\/bad>', 'gi'), ''); 6712 //Replace the lines feeds 6713 html = html.replace(/<YUI_LF>/g, '\n'); 6714 return html; 6715 }, 6716 /** 6717 * @method cleanHTML 6718 * @param {String} html The unfiltered HTML 6719 * @description Process the HTML with a few regexes to clean it up and stabilize the output 6720 * @return {String} The filtered HTML 6721 */ 6722 cleanHTML: function(html) { 6723 //Start Filtering Output 6724 //Begin RegExs.. 6725 if (!html) { 6726 html = this.getEditorHTML(); 6727 } 6728 var markup = this.get('markup'); 6729 //Make some backups... 6730 html = this.pre_filter_linebreaks(html, markup); 6731 6732 //Filter MS Word 6733 html = this.filter_msword(html); 6734 6735 html = html.replace(/<img([^>]*)\/>/gi, '<YUI_IMG$1>'); 6736 html = html.replace(/<img([^>]*)>/gi, '<YUI_IMG$1>'); 6737 6738 html = html.replace(/<input([^>]*)\/>/gi, '<YUI_INPUT$1>'); 6739 html = html.replace(/<input([^>]*)>/gi, '<YUI_INPUT$1>'); 6740 6741 html = html.replace(/<ul([^>]*)>/gi, '<YUI_UL$1>'); 6742 html = html.replace(/<\/ul>/gi, '<\/YUI_UL>'); 6743 html = html.replace(/<blockquote([^>]*)>/gi, '<YUI_BQ$1>'); 6744 html = html.replace(/<\/blockquote>/gi, '<\/YUI_BQ>'); 6745 6746 html = html.replace(/<embed([^>]*)>/gi, '<YUI_EMBED$1>'); 6747 html = html.replace(/<\/embed>/gi, '<\/YUI_EMBED>'); 6748 6749 //Convert b and i tags to strong and em tags 6750 if ((markup == 'semantic') || (markup == 'xhtml')) { 6751 //html = html.replace(/<i(\s+[^>]*)?>/gi, "<em$1>"); 6752 html = html.replace(/<i([^>]*)>/gi, "<em$1>"); 6753 html = html.replace(/<\/i>/gi, '</em>'); 6754 //html = html.replace(/<b(\s+[^>]*)?>/gi, "<strong$1>"); 6755 html = html.replace(/<b([^>]*)>/gi, "<strong$1>"); 6756 html = html.replace(/<\/b>/gi, '</strong>'); 6757 } 6758 6759 html = html.replace(/_moz_dirty=""/gi, ''); 6760 6761 //normalize strikethrough 6762 html = html.replace(/<strike/gi, '<span style="text-decoration: line-through;"'); 6763 html = html.replace(/\/strike>/gi, '/span>'); 6764 6765 6766 //Case Changing 6767 if (this.browser.ie) { 6768 html = html.replace(/text-decoration/gi, 'text-decoration'); 6769 html = html.replace(/font-weight/gi, 'font-weight'); 6770 html = html.replace(/_width="([^>]*)"/gi, ''); 6771 html = html.replace(/_height="([^>]*)"/gi, ''); 6772 //Cleanup Image URL's 6773 var url = this._baseHREF.replace(/\//gi, '\\/'), 6774 re = new RegExp('src="' + url, 'gi'); 6775 html = html.replace(re, 'src="'); 6776 } 6777 html = html.replace(/<font/gi, '<font'); 6778 html = html.replace(/<\/font>/gi, '</font>'); 6779 html = html.replace(/<span/gi, '<span'); 6780 html = html.replace(/<\/span>/gi, '</span>'); 6781 if ((markup == 'semantic') || (markup == 'xhtml') || (markup == 'css')) { 6782 html = html.replace(new RegExp('<font([^>]*)face="([^>]*)">(.*?)<\/font>', 'gi'), '<span $1 style="font-family: $2;">$3</span>'); 6783 html = html.replace(/<u/gi, '<span style="text-decoration: underline;"'); 6784 if (this.browser.webkit) { 6785 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-weight: bold;">([^>]*)<\/span>', 'gi'), '<strong>$1</strong>'); 6786 html = html.replace(new RegExp('<span class="Apple-style-span" style="font-style: italic;">([^>]*)<\/span>', 'gi'), '<em>$1</em>'); 6787 } 6788 html = html.replace(/\/u>/gi, '/span>'); 6789 if (markup == 'css') { 6790 html = html.replace(/<em([^>]*)>/gi, '<i$1>'); 6791 html = html.replace(/<\/em>/gi, '</i>'); 6792 html = html.replace(/<strong([^>]*)>/gi, '<b$1>'); 6793 html = html.replace(/<\/strong>/gi, '</b>'); 6794 html = html.replace(/<b/gi, '<span style="font-weight: bold;"'); 6795 html = html.replace(/\/b>/gi, '/span>'); 6796 html = html.replace(/<i/gi, '<span style="font-style: italic;"'); 6797 html = html.replace(/\/i>/gi, '/span>'); 6798 } 6799 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single 6800 } else { 6801 html = html.replace(/<u/gi, '<u'); 6802 html = html.replace(/\/u>/gi, '/u>'); 6803 } 6804 html = html.replace(/<ol([^>]*)>/gi, '<ol$1>'); 6805 html = html.replace(/\/ol>/gi, '/ol>'); 6806 html = html.replace(/<li/gi, '<li'); 6807 html = html.replace(/\/li>/gi, '/li>'); 6808 html = this.filter_safari(html); 6809 6810 html = this.filter_internals(html); 6811 6812 html = this.filter_all_rgb(html); 6813 6814 //Replace our backups with the real thing 6815 html = this.post_filter_linebreaks(html, markup); 6816 6817 if (markup == 'xhtml') { 6818 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1 />'); 6819 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1 />'); 6820 } else { 6821 html = html.replace(/<YUI_IMG([^>]*)>/g, '<img $1>'); 6822 html = html.replace(/<YUI_INPUT([^>]*)>/g, '<input $1>'); 6823 } 6824 html = html.replace(/<YUI_UL([^>]*)>/g, '<ul$1>'); 6825 html = html.replace(/<\/YUI_UL>/g, '<\/ul>'); 6826 6827 html = this.filter_invalid_lists(html); 6828 6829 html = html.replace(/<YUI_BQ([^>]*)>/g, '<blockquote$1>'); 6830 html = html.replace(/<\/YUI_BQ>/g, '<\/blockquote>'); 6831 6832 html = html.replace(/<YUI_EMBED([^>]*)>/g, '<embed$1>'); 6833 html = html.replace(/<\/YUI_EMBED>/g, '<\/embed>'); 6834 6835 //This should fix &'s in URL's 6836 html = html.replace(/ & /gi, ' YUI_AMP '); 6837 html = html.replace(/ &/gi, ' YUI_AMP_F '); 6838 html = html.replace(/& /gi, ' YUI_AMP_R '); 6839 html = html.replace(/&/gi, '&'); 6840 html = html.replace(/ YUI_AMP /gi, ' & '); 6841 html = html.replace(/ YUI_AMP_F /gi, ' &'); 6842 html = html.replace(/ YUI_AMP_R /gi, '& '); 6843 6844 //Trim the output, removing whitespace from the beginning and end 6845 html = YAHOO.lang.trim(html); 6846 6847 if (this.get('removeLineBreaks')) { 6848 html = html.replace(/\n/g, '').replace(/\r/g, ''); 6849 html = html.replace(/ /gi, ' '); //Replace all double spaces and replace with a single 6850 } 6851 6852 for (var v in this.invalidHTML) { 6853 if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) { 6854 if (Lang.isObject(v) && v.keepContents) { 6855 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), '$1'); 6856 } else { 6857 html = html.replace(new RegExp('<' + v + '([^>]*)>(.*?)<\/' + v + '>', 'gi'), ''); 6858 } 6859 } 6860 } 6861 6862 /* LATER -- Add DOM manipulation 6863 console.log(html); 6864 var frag = document.createDocumentFragment(); 6865 frag.innerHTML = html; 6866 6867 var ps = frag.getElementsByTagName('p'), 6868 len = ps.length; 6869 for (var i = 0; i < len; i++) { 6870 var ps2 = ps[i].getElementsByTagName('p'); 6871 if (ps2.length) { 6872 6873 } 6874 6875 } 6876 html = frag.innerHTML; 6877 console.log(html); 6878 */ 6879 6880 this.fireEvent('cleanHTML', { type: 'cleanHTML', target: this, html: html }); 6881 6882 return html; 6883 }, 6884 /** 6885 * @method filter_msword 6886 * @param String html The HTML string to filter 6887 * @description Filters out msword html attributes and other junk. Activate with filterWord: true in config 6888 */ 6889 filter_msword: function(html) { 6890 if (!this.get('filterWord')) { 6891 return html; 6892 } 6893 //Remove the ms o: tags 6894 html = html.replace(/<o:p>\s*<\/o:p>/g, ''); 6895 html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, ' '); 6896 6897 //Remove the ms w: tags 6898 html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, ''); 6899 6900 //Remove mso-? styles. 6901 html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, ''); 6902 6903 //Remove more bogus MS styles. 6904 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, ''); 6905 html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\""); 6906 html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, ''); 6907 html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\""); 6908 html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\""); 6909 html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ); 6910 html = html.replace( /\s*tab-stops:[^;"]*;?/gi, ''); 6911 html = html.replace( /\s*tab-stops:[^"]*/gi, ''); 6912 6913 //Remove XML declarations 6914 html = html.replace(/<\\?\?xml[^>]*>/gi, ''); 6915 6916 //Remove lang 6917 html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3"); 6918 6919 //Remove language tags 6920 html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3"); 6921 6922 //Remove onmouseover and onmouseout events (from MS Word comments effect) 6923 html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3"); 6924 html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3"); 6925 6926 return html; 6927 }, 6928 /** 6929 * @method filter_invalid_lists 6930 * @param String html The HTML string to filter 6931 * @description Filters invalid ol and ul list markup, converts this: <li></li><ol>..</ol> to this: <li></li><li><ol>..</ol></li> 6932 */ 6933 filter_invalid_lists: function(html) { 6934 html = html.replace(/<\/li>\n/gi, '</li>'); 6935 6936 html = html.replace(/<\/li><ol>/gi, '</li><li><ol>'); 6937 html = html.replace(/<\/ol>/gi, '</ol></li>'); 6938 html = html.replace(/<\/ol><\/li>\n/gi, "</ol>"); 6939 6940 html = html.replace(/<\/li><ul>/gi, '</li><li><ul>'); 6941 html = html.replace(/<\/ul>/gi, '</ul></li>'); 6942 html = html.replace(/<\/ul><\/li>\n?/gi, "</ul>"); 6943 6944 html = html.replace(/<\/li>/gi, "</li>"); 6945 html = html.replace(/<\/ol>/gi, "</ol>"); 6946 html = html.replace(/<ol>/gi, "<ol>"); 6947 html = html.replace(/<ul>/gi, "<ul>"); 6948 return html; 6949 }, 6950 /** 6951 * @method filter_safari 6952 * @param String html The HTML string to filter 6953 * @description Filters strings specific to Safari 6954 * @return String 6955 */ 6956 filter_safari: function(html) { 6957 if (this.browser.webkit) { 6958 //<span class="Apple-tab-span" style="white-space:pre"> </span> 6959 html = html.replace(/<span class="Apple-tab-span" style="white-space:pre">([^>])<\/span>/gi, ' '); 6960 html = html.replace(/Apple-style-span/gi, ''); 6961 html = html.replace(/style="line-height: normal;"/gi, ''); 6962 html = html.replace(/yui-wk-div/gi, ''); 6963 html = html.replace(/yui-wk-p/gi, ''); 6964 6965 6966 //Remove bogus LI's 6967 html = html.replace(/<li><\/li>/gi, ''); 6968 html = html.replace(/<li> <\/li>/gi, ''); 6969 html = html.replace(/<li> <\/li>/gi, ''); 6970 //Remove bogus DIV's - updated from just removing the div's to replacing /div with a break 6971 if (this.get('ptags')) { 6972 html = html.replace(/<div([^>]*)>/g, '<p$1>'); 6973 html = html.replace(/<\/div>/gi, '</p>'); 6974 } else { 6975 //html = html.replace(/<div>/gi, '<br>'); 6976 html = html.replace(/<div([^>]*)>([ tnr]*)<\/div>/gi, '<br>'); 6977 html = html.replace(/<\/div>/gi, ''); 6978 } 6979 } 6980 return html; 6981 }, 6982 /** 6983 * @method filter_internals 6984 * @param String html The HTML string to filter 6985 * @description Filters internal RTE strings and bogus attrs we don't want 6986 * @return String 6987 */ 6988 filter_internals: function(html) { 6989 html = html.replace(/\r/g, ''); 6990 //Fix stuff we don't want 6991 html = html.replace(/<\/?(body|head|html)[^>]*>/gi, ''); 6992 //Fix last BR in LI 6993 html = html.replace(/<YUI_BR><\/li>/gi, '</li>'); 6994 6995 html = html.replace(/yui-tag-span/gi, ''); 6996 html = html.replace(/yui-tag/gi, ''); 6997 html = html.replace(/yui-non/gi, ''); 6998 html = html.replace(/yui-img/gi, ''); 6999 html = html.replace(/ tag="span"/gi, ''); 7000 html = html.replace(/ class=""/gi, ''); 7001 html = html.replace(/ style=""/gi, ''); 7002 html = html.replace(/ class=" "/gi, ''); 7003 html = html.replace(/ class=" "/gi, ''); 7004 html = html.replace(/ target=""/gi, ''); 7005 html = html.replace(/ title=""/gi, ''); 7006 7007 if (this.browser.ie) { 7008 html = html.replace(/ class= /gi, ''); 7009 html = html.replace(/ class= >/gi, ''); 7010 } 7011 7012 return html; 7013 }, 7014 /** 7015 * @method filter_all_rgb 7016 * @param String str The HTML string to filter 7017 * @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" 7018 * @return String 7019 */ 7020 filter_all_rgb: function(str) { 7021 var exp = new RegExp("rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)", "gi"); 7022 var arr = str.match(exp); 7023 if (Lang.isArray(arr)) { 7024 for (var i = 0; i < arr.length; i++) { 7025 var color = this.filter_rgb(arr[i]); 7026 str = str.replace(arr[i].toString(), color); 7027 } 7028 } 7029 7030 return str; 7031 }, 7032 /** 7033 * @method filter_rgb 7034 * @param String css The CSS string containing rgb(#,#,#); 7035 * @description Converts an RGB color string to a hex color, example: rgb(0, 255, 0) converts to #00ff00 7036 * @return String 7037 */ 7038 filter_rgb: function(css) { 7039 if (css.toLowerCase().indexOf('rgb') != -1) { 7040 var exp = new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)", "gi"); 7041 var rgb = css.replace(exp, "$1,$2,$3,$4,$5").split(','); 7042 7043 if (rgb.length == 5) { 7044 var r = parseInt(rgb[1], 10).toString(16); 7045 var g = parseInt(rgb[2], 10).toString(16); 7046 var b = parseInt(rgb[3], 10).toString(16); 7047 7048 r = r.length == 1 ? '0' + r : r; 7049 g = g.length == 1 ? '0' + g : g; 7050 b = b.length == 1 ? '0' + b : b; 7051 7052 css = "#" + r + g + b; 7053 } 7054 } 7055 return css; 7056 }, 7057 /** 7058 * @method pre_filter_linebreaks 7059 * @param String html The HTML to filter 7060 * @param String markup The markup type to filter to 7061 * @description HTML Pre Filter 7062 * @return String 7063 */ 7064 pre_filter_linebreaks: function(html, markup) { 7065 if (this.browser.webkit) { 7066 html = html.replace(/<br class="khtml-block-placeholder">/gi, '<YUI_BR>'); 7067 html = html.replace(/<br class="webkit-block-placeholder">/gi, '<YUI_BR>'); 7068 } 7069 html = html.replace(/<br>/gi, '<YUI_BR>'); 7070 html = html.replace(/<br (.*?)>/gi, '<YUI_BR>'); 7071 html = html.replace(/<br\/>/gi, '<YUI_BR>'); 7072 html = html.replace(/<br \/>/gi, '<YUI_BR>'); 7073 html = html.replace(/<div><YUI_BR><\/div>/gi, '<YUI_BR>'); 7074 html = html.replace(/<p>( | )<\/p>/g, '<YUI_BR>'); 7075 html = html.replace(/<p><br> <\/p>/gi, '<YUI_BR>'); 7076 html = html.replace(/<p> <\/p>/gi, '<YUI_BR>'); 7077 //Fix last BR 7078 html = html.replace(/<YUI_BR>$/, ''); 7079 //Fix last BR in P 7080 html = html.replace(/<YUI_BR><\/p>/g, '</p>'); 7081 if (this.browser.ie) { 7082 html = html.replace(/ /g, '\t'); 7083 } 7084 return html; 7085 }, 7086 /** 7087 * @method post_filter_linebreaks 7088 * @param String html The HTML to filter 7089 * @param String markup The markup type to filter to 7090 * @description HTML Pre Filter 7091 * @return String 7092 */ 7093 post_filter_linebreaks: function(html, markup) { 7094 if (markup == 'xhtml') { 7095 html = html.replace(/<YUI_BR>/g, '<br />'); 7096 } else { 7097 html = html.replace(/<YUI_BR>/g, '<br>'); 7098 } 7099 return html; 7100 }, 7101 /** 7102 * @method clearEditorDoc 7103 * @description Clear the doc of the Editor 7104 */ 7105 clearEditorDoc: function() { 7106 this._getDoc().body.innerHTML = ' '; 7107 }, 7108 /** 7109 * @method openWindow 7110 * @description Override Method for Advanced Editor 7111 */ 7112 openWindow: function(win) { 7113 }, 7114 /** 7115 * @method moveWindow 7116 * @description Override Method for Advanced Editor 7117 */ 7118 moveWindow: function() { 7119 }, 7120 /** 7121 * @private 7122 * @method _closeWindow 7123 * @description Override Method for Advanced Editor 7124 */ 7125 _closeWindow: function() { 7126 }, 7127 /** 7128 * @method closeWindow 7129 * @description Override Method for Advanced Editor 7130 */ 7131 closeWindow: function() { 7132 //this.unsubscribeAll('afterExecCommand'); 7133 this.toolbar.resetAllButtons(); 7134 this.focus(); 7135 }, 7136 /** 7137 * @method destroy 7138 * @description Destroys the editor, all of it's elements and objects. 7139 * @return {Boolean} 7140 */ 7141 destroy: function() { 7142 if (this._nodeChangeDelayTimer) { 7143 clearTimeout(this._nodeChangeDelayTimer); 7144 } 7145 this.hide(); 7146 7147 if (this.resize) { 7148 this.resize.destroy(); 7149 } 7150 if (this.dd) { 7151 this.dd.unreg(); 7152 } 7153 if (this.get('panel')) { 7154 this.get('panel').destroy(); 7155 } 7156 this.saveHTML(); 7157 this.toolbar.destroy(); 7158 this.setStyle('visibility', 'visible'); 7159 this.setStyle('position', 'static'); 7160 this.setStyle('top', ''); 7161 this.setStyle('left', ''); 7162 var textArea = this.get('element'); 7163 this.get('element_cont').get('parentNode').replaceChild(textArea, this.get('element_cont').get('element')); 7164 this.get('element_cont').get('element').innerHTML = ''; 7165 this.set('handleSubmit', false); //Remove the submit handler 7166 return true; 7167 }, 7168 /** 7169 * @method toString 7170 * @description Returns a string representing the editor. 7171 * @return {String} 7172 */ 7173 toString: function() { 7174 var str = 'SimpleEditor'; 7175 if (this.get && this.get('element_cont')) { 7176 str = 'SimpleEditor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : '')); 7177 } 7178 return str; 7179 } 7180 }); 7181 7182 /** 7183 * @event toolbarLoaded 7184 * @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. 7185 * @type YAHOO.util.CustomEvent 7186 */ 7187 /** 7188 * @event cleanHTML 7189 * @description Event is fired after the cleanHTML method is called. 7190 * @type YAHOO.util.CustomEvent 7191 */ 7192 /** 7193 * @event afterRender 7194 * @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. 7195 * @type YAHOO.util.CustomEvent 7196 */ 7197 /** 7198 * @event editorContentLoaded 7199 * @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. 7200 * @type YAHOO.util.CustomEvent 7201 */ 7202 /** 7203 * @event beforeNodeChange 7204 * @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. 7205 * @type YAHOO.util.CustomEvent 7206 */ 7207 /** 7208 * @event afterNodeChange 7209 * @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. 7210 * @type YAHOO.util.CustomEvent 7211 */ 7212 /** 7213 * @event beforeExecCommand 7214 * @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. 7215 * @type YAHOO.util.CustomEvent 7216 */ 7217 /** 7218 * @event afterExecCommand 7219 * @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. 7220 * @type YAHOO.util.CustomEvent 7221 */ 7222 /** 7223 * @event editorMouseUp 7224 * @param {Event} ev The DOM Event that occured 7225 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7226 * @type YAHOO.util.CustomEvent 7227 */ 7228 /** 7229 * @event editorMouseDown 7230 * @param {Event} ev The DOM Event that occured 7231 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7232 * @type YAHOO.util.CustomEvent 7233 */ 7234 /** 7235 * @event editorDoubleClick 7236 * @param {Event} ev The DOM Event that occured 7237 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7238 * @type YAHOO.util.CustomEvent 7239 */ 7240 /** 7241 * @event editorClick 7242 * @param {Event} ev The DOM Event that occured 7243 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7244 * @type YAHOO.util.CustomEvent 7245 */ 7246 /** 7247 * @event editorKeyUp 7248 * @param {Event} ev The DOM Event that occured 7249 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7250 * @type YAHOO.util.CustomEvent 7251 */ 7252 /** 7253 * @event editorKeyPress 7254 * @param {Event} ev The DOM Event that occured 7255 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7256 * @type YAHOO.util.CustomEvent 7257 */ 7258 /** 7259 * @event editorKeyDown 7260 * @param {Event} ev The DOM Event that occured 7261 * @description Passed through HTML Event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 7262 * @type YAHOO.util.CustomEvent 7263 */ 7264 /** 7265 * @event beforeEditorMouseUp 7266 * @param {Event} ev The DOM Event that occured 7267 * @description Fires before editor event, returning false will stop the internal processing. 7268 * @type YAHOO.util.CustomEvent 7269 */ 7270 /** 7271 * @event beforeEditorMouseDown 7272 * @param {Event} ev The DOM Event that occured 7273 * @description Fires before editor event, returning false will stop the internal processing. 7274 * @type YAHOO.util.CustomEvent 7275 */ 7276 /** 7277 * @event beforeEditorDoubleClick 7278 * @param {Event} ev The DOM Event that occured 7279 * @description Fires before editor event, returning false will stop the internal processing. 7280 * @type YAHOO.util.CustomEvent 7281 */ 7282 /** 7283 * @event beforeEditorClick 7284 * @param {Event} ev The DOM Event that occured 7285 * @description Fires before editor event, returning false will stop the internal processing. 7286 * @type YAHOO.util.CustomEvent 7287 */ 7288 /** 7289 * @event beforeEditorKeyUp 7290 * @param {Event} ev The DOM Event that occured 7291 * @description Fires before editor event, returning false will stop the internal processing. 7292 * @type YAHOO.util.CustomEvent 7293 */ 7294 /** 7295 * @event beforeEditorKeyPress 7296 * @param {Event} ev The DOM Event that occured 7297 * @description Fires before editor event, returning false will stop the internal processing. 7298 * @type YAHOO.util.CustomEvent 7299 */ 7300 /** 7301 * @event beforeEditorKeyDown 7302 * @param {Event} ev The DOM Event that occured 7303 * @description Fires before editor event, returning false will stop the internal processing. 7304 * @type YAHOO.util.CustomEvent 7305 */ 7306 7307 /** 7308 * @event editorWindowFocus 7309 * @description Fires when the iframe is focused. Note, this is window focus event, not an Editor focus event. 7310 * @type YAHOO.util.CustomEvent 7311 */ 7312 /** 7313 * @event editorWindowBlur 7314 * @description Fires when the iframe is blurred. Note, this is window blur event, not an Editor blur event. 7315 * @type YAHOO.util.CustomEvent 7316 */ 7317 7318 7319 /** 7320 * @description Singleton object used to track the open window objects and panels across the various open editors 7321 * @class EditorInfo 7322 * @static 7323 */ 7324 YAHOO.widget.EditorInfo = { 7325 /** 7326 * @private 7327 * @property _instances 7328 * @description A reference to all editors on the page. 7329 * @type Object 7330 */ 7331 _instances: {}, 7332 /** 7333 * @private 7334 * @property blankImage 7335 * @description A reference to the blankImage url 7336 * @type String 7337 */ 7338 blankImage: '', 7339 /** 7340 * @private 7341 * @property window 7342 * @description A reference to the currently open window object in any editor on the page. 7343 * @type Object <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> 7344 */ 7345 window: {}, 7346 /** 7347 * @private 7348 * @property panel 7349 * @description A reference to the currently open panel in any editor on the page. 7350 * @type Object <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a> 7351 */ 7352 panel: null, 7353 /** 7354 * @method getEditorById 7355 * @description Returns a reference to the Editor object associated with the given textarea 7356 * @param {String/HTMLElement} id The id or reference of the textarea to return the Editor instance of 7357 * @return Object <a href="YAHOO.widget.Editor.html">YAHOO.widget.Editor</a> 7358 */ 7359 getEditorById: function(id) { 7360 if (!YAHOO.lang.isString(id)) { 7361 //Not a string, assume a node Reference 7362 id = id.id; 7363 } 7364 if (this._instances[id]) { 7365 return this._instances[id]; 7366 } 7367 return false; 7368 }, 7369 /** 7370 * @method saveAll 7371 * @description Saves all Editor instances on the page. If a form reference is passed, only Editor's bound to this form will be saved. 7372 * @param {HTMLElement} form The form to check if this Editor instance belongs to 7373 */ 7374 saveAll: function(form) { 7375 var i, e, items = YAHOO.widget.EditorInfo._instances; 7376 if (form) { 7377 for (i in items) { 7378 if (Lang.hasOwnProperty(items, i)) { 7379 e = items[i]; 7380 if (e.get('element').form && (e.get('element').form == form)) { 7381 e.saveHTML(); 7382 } 7383 } 7384 } 7385 } else { 7386 for (i in items) { 7387 if (Lang.hasOwnProperty(items, i)) { 7388 items[i].saveHTML(); 7389 } 7390 } 7391 } 7392 }, 7393 /** 7394 * @method toString 7395 * @description Returns a string representing the EditorInfo. 7396 * @return {String} 7397 */ 7398 toString: function() { 7399 var len = 0; 7400 for (var i in this._instances) { 7401 if (Lang.hasOwnProperty(this._instances, i)) { 7402 len++; 7403 } 7404 } 7405 return 'Editor Info (' + len + ' registered intance' + ((len > 1) ? 's' : '') + ')'; 7406 } 7407 }; 7408 7409 7410 7411 7412 })(); 7413 /** 7414 * @module editor 7415 * @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> 7416 * @namespace YAHOO.widget 7417 * @requires yahoo, dom, element, event, container_core, simpleeditor 7418 * @optional dragdrop, animation, menu, button, resize 7419 */ 7420 7421 (function() { 7422 var Dom = YAHOO.util.Dom, 7423 Event = YAHOO.util.Event, 7424 Lang = YAHOO.lang, 7425 Toolbar = YAHOO.widget.Toolbar; 7426 7427 /** 7428 * 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. 7429 * @constructor 7430 * @class Editor 7431 * @extends YAHOO.widget.SimpleEditor 7432 * @param {String/HTMLElement} el The textarea element to turn into an editor. 7433 * @param {Object} attrs Object liternal containing configuration parameters. 7434 */ 7435 7436 YAHOO.widget.Editor = function(el, attrs) { 7437 YAHOO.widget.Editor.superclass.constructor.call(this, el, attrs); 7438 }; 7439 7440 YAHOO.extend(YAHOO.widget.Editor, YAHOO.widget.SimpleEditor, { 7441 /** 7442 * @private 7443 * @property _undoCache 7444 * @description An Array hash of the Undo Levels. 7445 * @type Array 7446 */ 7447 _undoCache: null, 7448 /** 7449 * @private 7450 * @property _undoLevel 7451 * @description The index of the current undo state. 7452 * @type Number 7453 */ 7454 _undoLevel: null, 7455 /** 7456 * @private 7457 * @method _hasUndoLevel 7458 * @description Checks to see if we have an undo level available 7459 * @return Boolean 7460 */ 7461 _hasUndoLevel: function() { 7462 return ((this._undoCache.length > 1) && this._undoLevel); 7463 }, 7464 /** 7465 * @private 7466 * @method _undoNodeChange 7467 * @description nodeChange listener for undo processing 7468 */ 7469 _undoNodeChange: function() { 7470 var undo_button = this.toolbar.getButtonByValue('undo'), 7471 redo_button = this.toolbar.getButtonByValue('redo'); 7472 if (undo_button && redo_button) { 7473 if (this._hasUndoLevel()) { 7474 this.toolbar.enableButton(undo_button); 7475 } 7476 if (this._undoLevel < this._undoCache.length) { 7477 this.toolbar.enableButton(redo_button); 7478 } 7479 } 7480 this._lastCommand = null; 7481 }, 7482 /** 7483 * @private 7484 * @method _checkUndo 7485 * @description Prunes the undo cache when it reaches the maxUndo config 7486 */ 7487 _checkUndo: function() { 7488 var len = this._undoCache.length, 7489 tmp = []; 7490 if (len >= this.get('maxUndo')) { 7491 for (var i = (len - this.get('maxUndo')); i < len; i++) { 7492 tmp.push(this._undoCache[i]); 7493 } 7494 this._undoCache = tmp; 7495 this._undoLevel = this._undoCache.length; 7496 } 7497 }, 7498 /** 7499 * @private 7500 * @method _putUndo 7501 * @description Puts the content of the Editor into the _undoCache. 7502 * //TODO Convert the hash to a series of TEXTAREAS to store state in. 7503 * @param {String} str The content of the Editor 7504 */ 7505 _putUndo: function(str) { 7506 if (this._undoLevel === this._undoCache.length) { 7507 this._undoCache.push(str); 7508 this._undoLevel = this._undoCache.length; 7509 } else { 7510 var str = this.getEditorHTML(); 7511 var last = this._undoCache[this._undoLevel]; 7512 if (last) { 7513 if (str !== last) { 7514 this._undoCache = []; 7515 this._undoLevel = 0; 7516 } 7517 } 7518 } 7519 }, 7520 /** 7521 * @private 7522 * @method _getUndo 7523 * @description Get's a level from the undo cache. 7524 * @param {Number} index The index of the undo level we want to get. 7525 * @return {String} 7526 */ 7527 _getUndo: function(index) { 7528 this._undoLevel = index; 7529 return this._undoCache[index]; 7530 }, 7531 /** 7532 * @private 7533 * @method _storeUndo 7534 * @description Method to call when you want to store an undo state. Currently called from nodeChange and _handleKeyUp 7535 */ 7536 _storeUndo: function() { 7537 if (this._lastCommand === 'undo' || this._lastCommand === 'redo') { 7538 return false; 7539 } 7540 if (!this._undoCache) { 7541 this._undoCache = []; 7542 this._undoLevel = 0; 7543 } 7544 this._checkUndo(); 7545 var str = this.getEditorHTML(); 7546 //var last = this._undoCache[this._undoCache.length - 1]; 7547 var last = this._undoCache[this._undoLevel - 1]; 7548 if (last) { 7549 if (str !== last) { 7550 this._putUndo(str); 7551 } 7552 } else { 7553 this._putUndo(str); 7554 } 7555 this._undoNodeChange(); 7556 }, 7557 /** 7558 * @property STR_BEFORE_EDITOR 7559 * @description The accessibility string for the element before the iFrame 7560 * @type String 7561 */ 7562 STR_BEFORE_EDITOR: 'This text field can contain stylized text and graphics. To cycle through all formatting options, use the keyboard shortcut Control + Shift + T to place focus on the toolbar and navigate between option heading names. <h4>Common formatting keyboard shortcuts:</h4><ul><li>Control Shift B sets text to bold</li> <li>Control Shift I sets text to italic</li> <li>Control Shift U underlines text</li> <li>Control Shift [ aligns text left</li> <li>Control Shift | centers text</li> <li>Control Shift ] aligns text right</li> <li>Control Shift L adds an HTML link</li> <li>To exit this text editor use the keyboard shortcut Control + Shift + ESC.</li></ul>', 7563 /** 7564 * @property STR_CLOSE_WINDOW 7565 * @description The Title of the close button in the Editor Window 7566 * @type String 7567 */ 7568 STR_CLOSE_WINDOW: 'Close Window', 7569 /** 7570 * @property STR_CLOSE_WINDOW_NOTE 7571 * @description A note appearing in the Editor Window to tell the user that the Escape key will close the window 7572 * @type String 7573 */ 7574 STR_CLOSE_WINDOW_NOTE: 'To close this window use the Control + Shift + W key', 7575 /** 7576 * @property STR_IMAGE_PROP_TITLE 7577 * @description The title for the Image Property Editor Window 7578 * @type String 7579 */ 7580 STR_IMAGE_PROP_TITLE: 'Image Options', 7581 /** 7582 * @property STR_IMAGE_TITLE 7583 * @description The label string for Image Description 7584 * @type String 7585 */ 7586 STR_IMAGE_TITLE: 'Description', 7587 /** 7588 * @property STR_IMAGE_SIZE 7589 * @description The label string for Image Size 7590 * @type String 7591 */ 7592 STR_IMAGE_SIZE: 'Size', 7593 /** 7594 * @property STR_IMAGE_ORIG_SIZE 7595 * @description The label string for Original Image Size 7596 * @type String 7597 */ 7598 STR_IMAGE_ORIG_SIZE: 'Original Size', 7599 /** 7600 * @property STR_IMAGE_COPY 7601 * @description The label string for the image copy and paste message for Opera and Safari 7602 * @type String 7603 */ 7604 STR_IMAGE_COPY: '<span class="tip"><span class="icon icon-info"></span><strong>Note:</strong>To move this image just highlight it, cut, and paste where ever you\'d like.</span>', 7605 /** 7606 * @property STR_IMAGE_PADDING 7607 * @description The label string for the image padding. 7608 * @type String 7609 */ 7610 STR_IMAGE_PADDING: 'Padding', 7611 /** 7612 * @property STR_IMAGE_BORDER 7613 * @description The label string for the image border. 7614 * @type String 7615 */ 7616 STR_IMAGE_BORDER: 'Border', 7617 /** 7618 * @property STR_IMAGE_BORDER_SIZE 7619 * @description The label string for the image border size. 7620 * @type String 7621 */ 7622 STR_IMAGE_BORDER_SIZE: 'Border Size', 7623 /** 7624 * @property STR_IMAGE_BORDER_TYPE 7625 * @description The label string for the image border type. 7626 * @type String 7627 */ 7628 STR_IMAGE_BORDER_TYPE: 'Border Type', 7629 /** 7630 * @property STR_IMAGE_TEXTFLOW 7631 * @description The label string for the image text flow. 7632 * @type String 7633 */ 7634 STR_IMAGE_TEXTFLOW: 'Text Flow', 7635 /** 7636 * @property STR_LOCAL_FILE_WARNING 7637 * @description The label string for the local file warning. 7638 * @type String 7639 */ 7640 STR_LOCAL_FILE_WARNING: '<span class="tip"><span class="icon icon-warn"></span><strong>Note:</strong>This image/link points to a file on your computer and will not be accessible to others on the internet.</span>', 7641 /** 7642 * @property STR_LINK_PROP_TITLE 7643 * @description The label string for the Link Property Editor Window. 7644 * @type String 7645 */ 7646 STR_LINK_PROP_TITLE: 'Link Options', 7647 /** 7648 * @property STR_LINK_PROP_REMOVE 7649 * @description The label string for the Remove link from text link inside the property editor. 7650 * @type String 7651 */ 7652 STR_LINK_PROP_REMOVE: 'Remove link from text', 7653 /** 7654 * @property STR_LINK_NEW_WINDOW 7655 * @description The string for the open in a new window label. 7656 * @type String 7657 */ 7658 STR_LINK_NEW_WINDOW: 'Open in a new window.', 7659 /** 7660 * @property STR_LINK_TITLE 7661 * @description The string for the link description. 7662 * @type String 7663 */ 7664 STR_LINK_TITLE: 'Description', 7665 /** 7666 * @property STR_NONE 7667 * @description The string for the word none. 7668 * @type String 7669 */ 7670 STR_NONE: 'none', 7671 /** 7672 * @protected 7673 * @property CLASS_LOCAL_FILE 7674 * @description CSS class applied to an element when it's found to have a local url. 7675 * @type String 7676 */ 7677 CLASS_LOCAL_FILE: 'warning-localfile', 7678 /** 7679 * @protected 7680 * @property CLASS_HIDDEN 7681 * @description CSS class applied to the body when the hiddenelements button is pressed. 7682 * @type String 7683 */ 7684 CLASS_HIDDEN: 'yui-hidden', 7685 /** 7686 * @method init 7687 * @description The Editor class' initialization method 7688 */ 7689 init: function(p_oElement, p_oAttributes) { 7690 7691 this._windows = {}; 7692 if (!this._defaultToolbar) { 7693 this._defaultToolbar = { 7694 collapse: true, 7695 titlebar: 'Text Editing Tools', 7696 draggable: false, 7697 buttonType: 'advanced', 7698 buttons: [ 7699 { group: 'fontstyle', label: 'Font Name and Size', 7700 buttons: [ 7701 { type: 'select', label: 'Arial', value: 'fontname', disabled: true, 7702 menu: [ 7703 { text: 'Arial', checked: true }, 7704 { text: 'Arial Black' }, 7705 { text: 'Comic Sans MS' }, 7706 { text: 'Courier New' }, 7707 { text: 'Lucida Console' }, 7708 { text: 'Tahoma' }, 7709 { text: 'Times New Roman' }, 7710 { text: 'Trebuchet MS' }, 7711 { text: 'Verdana' } 7712 ] 7713 }, 7714 { type: 'spin', label: '13', value: 'fontsize', range: [ 9, 75 ], disabled: true } 7715 ] 7716 }, 7717 { type: 'separator' }, 7718 { group: 'textstyle', label: 'Font Style', 7719 buttons: [ 7720 { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' }, 7721 { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' }, 7722 { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' }, 7723 { type: 'separator' }, 7724 { type: 'push', label: 'Subscript', value: 'subscript', disabled: true }, 7725 { type: 'push', label: 'Superscript', value: 'superscript', disabled: true } 7726 ] 7727 }, 7728 { type: 'separator' }, 7729 { group: 'textstyle2', label: ' ', 7730 buttons: [ 7731 { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true }, 7732 { type: 'color', label: 'Background Color', value: 'backcolor', disabled: true }, 7733 { type: 'separator' }, 7734 { type: 'push', label: 'Remove Formatting', value: 'removeformat', disabled: true }, 7735 { type: 'push', label: 'Show/Hide Hidden Elements', value: 'hiddenelements' } 7736 ] 7737 }, 7738 { type: 'separator' }, 7739 { group: 'undoredo', label: 'Undo/Redo', 7740 buttons: [ 7741 { type: 'push', label: 'Undo', value: 'undo', disabled: true }, 7742 { type: 'push', label: 'Redo', value: 'redo', disabled: true } 7743 7744 ] 7745 }, 7746 { type: 'separator' }, 7747 { group: 'alignment', label: 'Alignment', 7748 buttons: [ 7749 { type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' }, 7750 { type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' }, 7751 { type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' }, 7752 { type: 'push', label: 'Justify', value: 'justifyfull' } 7753 ] 7754 }, 7755 { type: 'separator' }, 7756 { group: 'parastyle', label: 'Paragraph Style', 7757 buttons: [ 7758 { type: 'select', label: 'Normal', value: 'heading', disabled: true, 7759 menu: [ 7760 { text: 'Normal', value: 'none', checked: true }, 7761 { text: 'Header 1', value: 'h1' }, 7762 { text: 'Header 2', value: 'h2' }, 7763 { text: 'Header 3', value: 'h3' }, 7764 { text: 'Header 4', value: 'h4' }, 7765 { text: 'Header 5', value: 'h5' }, 7766 { text: 'Header 6', value: 'h6' } 7767 ] 7768 } 7769 ] 7770 }, 7771 { type: 'separator' }, 7772 7773 { group: 'indentlist2', label: 'Indenting and Lists', 7774 buttons: [ 7775 { type: 'push', label: 'Indent', value: 'indent', disabled: true }, 7776 { type: 'push', label: 'Outdent', value: 'outdent', disabled: true }, 7777 { type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' }, 7778 { type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' } 7779 ] 7780 }, 7781 { type: 'separator' }, 7782 { group: 'insertitem', label: 'Insert Item', 7783 buttons: [ 7784 { type: 'push', label: 'HTML Link CTRL + SHIFT + L', value: 'createlink', disabled: true }, 7785 { type: 'push', label: 'Insert Image', value: 'insertimage' } 7786 ] 7787 } 7788 ] 7789 }; 7790 } 7791 7792 if (!this._defaultImageToolbarConfig) { 7793 this._defaultImageToolbarConfig = { 7794 buttonType: this._defaultToolbar.buttonType, 7795 buttons: [ 7796 { group: 'textflow', label: this.STR_IMAGE_TEXTFLOW + ':', 7797 buttons: [ 7798 { type: 'push', label: 'Left', value: 'left' }, 7799 { type: 'push', label: 'Inline', value: 'inline' }, 7800 { type: 'push', label: 'Block', value: 'block' }, 7801 { type: 'push', label: 'Right', value: 'right' } 7802 ] 7803 }, 7804 { type: 'separator' }, 7805 { group: 'padding', label: this.STR_IMAGE_PADDING + ':', 7806 buttons: [ 7807 { type: 'spin', label: '0', value: 'padding', range: [0, 50] } 7808 ] 7809 }, 7810 { type: 'separator' }, 7811 { group: 'border', label: this.STR_IMAGE_BORDER + ':', 7812 buttons: [ 7813 { type: 'select', label: this.STR_IMAGE_BORDER_SIZE, value: 'bordersize', 7814 menu: [ 7815 { text: 'none', value: '0', checked: true }, 7816 { text: '1px', value: '1' }, 7817 { text: '2px', value: '2' }, 7818 { text: '3px', value: '3' }, 7819 { text: '4px', value: '4' }, 7820 { text: '5px', value: '5' } 7821 ] 7822 }, 7823 { type: 'select', label: this.STR_IMAGE_BORDER_TYPE, value: 'bordertype', disabled: true, 7824 menu: [ 7825 { text: 'Solid', value: 'solid', checked: true }, 7826 { text: 'Dashed', value: 'dashed' }, 7827 { text: 'Dotted', value: 'dotted' } 7828 ] 7829 }, 7830 { type: 'color', label: 'Border Color', value: 'bordercolor', disabled: true } 7831 ] 7832 } 7833 ] 7834 }; 7835 } 7836 7837 YAHOO.widget.Editor.superclass.init.call(this, p_oElement, p_oAttributes); 7838 }, 7839 _render: function() { 7840 YAHOO.widget.Editor.superclass._render.apply(this, arguments); 7841 var self = this; 7842 //Render the panel in another thread and delay it a little.. 7843 window.setTimeout(function() { 7844 self._renderPanel.call(self); 7845 }, 800); 7846 }, 7847 /** 7848 * @method initAttributes 7849 * @description Initializes all of the configuration attributes used to create 7850 * the editor. 7851 * @param {Object} attr Object literal specifying a set of 7852 * configuration attributes used to create the editor. 7853 */ 7854 initAttributes: function(attr) { 7855 YAHOO.widget.Editor.superclass.initAttributes.call(this, attr); 7856 7857 /** 7858 * @attribute localFileWarning 7859 * @description Should we throw the warning if we detect a file that is local to their machine? 7860 * @default true 7861 * @type Boolean 7862 */ 7863 this.setAttributeConfig('localFileWarning', { 7864 value: attr.locaFileWarning || true 7865 }); 7866 7867 /** 7868 * @attribute hiddencss 7869 * @description The CSS used to show/hide hidden elements on the page, these rules must be prefixed with the class provided in <code>this.CLASS_HIDDEN</code> 7870 * @default <code><pre> 7871 .yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u, 7872 .yui-hidden div, .yui-hidden p, .yui-hidden span, .yui-hidden img, .yui-hidden ul, .yui-hidden ol, 7873 .yui-hidden li, .yui-hidden table { 7874 border: 1px dotted #ccc; 7875 } 7876 .yui-hidden .yui-non { 7877 border: none; 7878 } 7879 .yui-hidden img { 7880 padding: 2px; 7881 }</pre></code> 7882 * @type String 7883 */ 7884 this.setAttributeConfig('hiddencss', { 7885 value: attr.hiddencss || '.yui-hidden font, .yui-hidden strong, .yui-hidden b, .yui-hidden em, .yui-hidden i, .yui-hidden u, .yui-hidden div,.yui-hidden p,.yui-hidden span,.yui-hidden img, .yui-hidden ul, .yui-hidden ol, .yui-hidden li, .yui-hidden table { border: 1px dotted #ccc; } .yui-hidden .yui-non { border: none; } .yui-hidden img { padding: 2px; }', 7886 writeOnce: true 7887 }); 7888 7889 }, 7890 /** 7891 * @private 7892 * @method _windows 7893 * @description A reference to the HTML elements used for the body of Editor Windows. 7894 */ 7895 _windows: null, 7896 /** 7897 * @private 7898 * @method _defaultImageToolbar 7899 * @description A reference to the Toolbar Object inside Image Editor Window. 7900 */ 7901 _defaultImageToolbar: null, 7902 /** 7903 * @private 7904 * @method _defaultImageToolbarConfig 7905 * @description Config to be used for the default Image Editor Window. 7906 */ 7907 _defaultImageToolbarConfig: null, 7908 /** 7909 * @private 7910 * @method _fixNodes 7911 * @description Fix href and imgs as well as remove invalid HTML. 7912 */ 7913 _fixNodes: function() { 7914 YAHOO.widget.Editor.superclass._fixNodes.call(this); 7915 try { 7916 var url = ''; 7917 7918 var imgs = this._getDoc().getElementsByTagName('img'); 7919 for (var im = 0; im < imgs.length; im++) { 7920 if (imgs[im].getAttribute('href', 2)) { 7921 url = imgs[im].getAttribute('src', 2); 7922 if (this._isLocalFile(url)) { 7923 Dom.addClass(imgs[im], this.CLASS_LOCAL_FILE); 7924 } else { 7925 Dom.removeClass(imgs[im], this.CLASS_LOCAL_FILE); 7926 } 7927 } 7928 } 7929 var fakeAs = this._getDoc().body.getElementsByTagName('a'); 7930 for (var a = 0; a < fakeAs.length; a++) { 7931 if (fakeAs[a].getAttribute('href', 2)) { 7932 url = fakeAs[a].getAttribute('href', 2); 7933 if (this._isLocalFile(url)) { 7934 Dom.addClass(fakeAs[a], this.CLASS_LOCAL_FILE); 7935 } else { 7936 Dom.removeClass(fakeAs[a], this.CLASS_LOCAL_FILE); 7937 } 7938 } 7939 } 7940 } catch(e) {} 7941 }, 7942 /** 7943 * @private 7944 * @property _disabled 7945 * @description The Toolbar items that should be disabled if there is no selection present in the editor. 7946 * @type Array 7947 */ 7948 _disabled: [ 'createlink', 'forecolor', 'backcolor', 'fontname', 'fontsize', 'superscript', 'subscript', 'removeformat', 'heading', 'indent' ], 7949 /** 7950 * @private 7951 * @property _alwaysDisabled 7952 * @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor. 7953 * @type Object 7954 */ 7955 _alwaysDisabled: { 'outdent': true }, 7956 /** 7957 * @private 7958 * @property _alwaysEnabled 7959 * @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor. 7960 * @type Object 7961 */ 7962 _alwaysEnabled: { hiddenelements: true }, 7963 /** 7964 * @private 7965 * @method _handleKeyDown 7966 * @param {Event} ev The event we are working on. 7967 * @description Override method that handles some new keydown events inside the iFrame document. 7968 */ 7969 _handleKeyDown: function(ev) { 7970 YAHOO.widget.Editor.superclass._handleKeyDown.call(this, ev); 7971 var doExec = false, 7972 action = null, 7973 exec = false; 7974 7975 switch (ev.keyCode) { 7976 //case 219: //Left 7977 case this._keyMap.JUSTIFY_LEFT.key: //Left 7978 if (this._checkKey(this._keyMap.JUSTIFY_LEFT, ev)) { 7979 action = 'justifyleft'; 7980 doExec = true; 7981 } 7982 break; 7983 //case 220: //Center 7984 case this._keyMap.JUSTIFY_CENTER.key: 7985 if (this._checkKey(this._keyMap.JUSTIFY_CENTER, ev)) { 7986 action = 'justifycenter'; 7987 doExec = true; 7988 } 7989 break; 7990 case 221: //Right 7991 case this._keyMap.JUSTIFY_RIGHT.key: 7992 if (this._checkKey(this._keyMap.JUSTIFY_RIGHT, ev)) { 7993 action = 'justifyright'; 7994 doExec = true; 7995 } 7996 break; 7997 } 7998 if (doExec && action) { 7999 this.execCommand(action, null); 8000 Event.stopEvent(ev); 8001 this.nodeChange(); 8002 } 8003 }, 8004 /** 8005 * @private 8006 * @method _renderCreateLinkWindow 8007 * @description Pre renders the CreateLink window so we get faster window opening. 8008 */ 8009 _renderCreateLinkWindow: function() { 8010 var str = '<label for="' + this.get('id') + '_createlink_url"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_url" id="' + this.get('id') + '_createlink_url" value=""></label>'; 8011 str += '<label for="' + this.get('id') + '_createlink_target"><strong> </strong><input type="checkbox" name="' + this.get('id') + '_createlink_target" id="' + this.get('id') + '_createlink_target" value="_blank" class="createlink_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>'; 8012 str += '<label for="' + this.get('id') + '_createlink_title"><strong>' + this.STR_LINK_TITLE + ':</strong> <input type="text" name="' + this.get('id') + '_createlink_title" id="' + this.get('id') + '_createlink_title" value=""></label>'; 8013 8014 var body = document.createElement('div'); 8015 body.innerHTML = str; 8016 8017 var unlinkCont = document.createElement('div'); 8018 unlinkCont.className = 'removeLink'; 8019 var unlink = document.createElement('a'); 8020 unlink.href = '#'; 8021 unlink.innerHTML = this.STR_LINK_PROP_REMOVE; 8022 unlink.title = this.STR_LINK_PROP_REMOVE; 8023 Event.on(unlink, 'click', function(ev) { 8024 Event.stopEvent(ev); 8025 this.unsubscribeAll('afterExecCommand'); 8026 this.execCommand('unlink'); 8027 this.closeWindow(); 8028 }, this, true); 8029 unlinkCont.appendChild(unlink); 8030 body.appendChild(unlinkCont); 8031 8032 this._windows.createlink = {}; 8033 this._windows.createlink.body = body; 8034 //body.style.display = 'none'; 8035 Event.on(body, 'keyup', function(e) { 8036 Event.stopPropagation(e); 8037 }); 8038 this.get('panel').editor_form.appendChild(body); 8039 this.fireEvent('windowCreateLinkRender', { type: 'windowCreateLinkRender', panel: this.get('panel'), body: body }); 8040 return body; 8041 }, 8042 _handleCreateLinkClick: function() { 8043 var el = this._getSelectedElement(); 8044 if (this._isElement(el, 'img')) { 8045 this.STOP_EXEC_COMMAND = true; 8046 this.currentElement[0] = el; 8047 this.toolbar.fireEvent('insertimageClick', { type: 'insertimageClick', target: this.toolbar }); 8048 this.fireEvent('afterExecCommand', { type: 'afterExecCommand', target: this }); 8049 return false; 8050 } 8051 if (this.get('limitCommands')) { 8052 if (!this.toolbar.getButtonByValue('createlink')) { 8053 return false; 8054 } 8055 } 8056 8057 this.on('afterExecCommand', function() { 8058 var win = new YAHOO.widget.EditorWindow('createlink', { 8059 width: '350px' 8060 }); 8061 8062 var el = this.currentElement[0], 8063 url = '', 8064 title = '', 8065 target = '', 8066 localFile = false; 8067 if (el) { 8068 win.el = el; 8069 if (el.getAttribute('href', 2) !== null) { 8070 url = el.getAttribute('href', 2); 8071 if (this._isLocalFile(url)) { 8072 //Local File throw Warning 8073 win.setFooter(this.STR_LOCAL_FILE_WARNING); 8074 localFile = true; 8075 } else { 8076 win.setFooter(' '); 8077 } 8078 } 8079 if (el.getAttribute('title') !== null) { 8080 title = el.getAttribute('title'); 8081 } 8082 if (el.getAttribute('target') !== null) { 8083 target = el.getAttribute('target'); 8084 } 8085 } 8086 var body = null; 8087 if (this._windows.createlink && this._windows.createlink.body) { 8088 body = this._windows.createlink.body; 8089 } else { 8090 body = this._renderCreateLinkWindow(); 8091 } 8092 8093 win.setHeader(this.STR_LINK_PROP_TITLE); 8094 win.setBody(body); 8095 8096 Event.purgeElement(this.get('id') + '_createlink_url'); 8097 8098 Dom.get(this.get('id') + '_createlink_url').value = url; 8099 Dom.get(this.get('id') + '_createlink_title').value = title; 8100 Dom.get(this.get('id') + '_createlink_target').checked = ((target) ? true : false); 8101 8102 8103 Event.onAvailable(this.get('id') + '_createlink_url', function() { 8104 var id = this.get('id'); 8105 window.setTimeout(function() { 8106 try { 8107 YAHOO.util.Dom.get(id + '_createlink_url').focus(); 8108 } catch (e) {} 8109 }, 50); 8110 8111 if (this._isLocalFile(url)) { 8112 //Local File throw Warning 8113 Dom.addClass(this.get('id') + '_createlink_url', 'warning'); 8114 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING); 8115 } else { 8116 Dom.removeClass(this.get('id') + '_createlink_url', 'warning'); 8117 this.get('panel').setFooter(' '); 8118 } 8119 Event.on(this.get('id') + '_createlink_url', 'blur', function() { 8120 var url = Dom.get(this.get('id') + '_createlink_url'); 8121 if (this._isLocalFile(url.value)) { 8122 //Local File throw Warning 8123 Dom.addClass(url, 'warning'); 8124 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING); 8125 } else { 8126 Dom.removeClass(url, 'warning'); 8127 this.get('panel').setFooter(' '); 8128 } 8129 }, this, true); 8130 }, this, true); 8131 8132 this.openWindow(win); 8133 8134 }); 8135 }, 8136 /** 8137 * @private 8138 * @method _handleCreateLinkWindowClose 8139 * @description Handles the closing of the Link Properties Window. 8140 */ 8141 _handleCreateLinkWindowClose: function() { 8142 8143 var url = Dom.get(this.get('id') + '_createlink_url'), 8144 target = Dom.get(this.get('id') + '_createlink_target'), 8145 title = Dom.get(this.get('id') + '_createlink_title'), 8146 el = arguments[0].win.el, 8147 a = el; 8148 8149 if (url && url.value) { 8150 var urlValue = url.value; 8151 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 8152 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 8153 //Found an @ sign, prefix with mailto: 8154 urlValue = 'mailto:' + urlValue; 8155 } else { 8156 // :// not found adding 8157 if (urlValue.substring(0, 1) != '#') { 8158 urlValue = 'http:/'+'/' + urlValue; 8159 } 8160 8161 } 8162 } 8163 el.setAttribute('href', urlValue); 8164 if (target.checked) { 8165 el.setAttribute('target', target.value); 8166 } else { 8167 el.setAttribute('target', ''); 8168 } 8169 el.setAttribute('title', ((title.value) ? title.value : '')); 8170 8171 } else { 8172 var _span = this._getDoc().createElement('span'); 8173 _span.innerHTML = el.innerHTML; 8174 Dom.addClass(_span, 'yui-non'); 8175 el.parentNode.replaceChild(_span, el); 8176 } 8177 Dom.removeClass(url, 'warning'); 8178 Dom.get(this.get('id') + '_createlink_url').value = ''; 8179 Dom.get(this.get('id') + '_createlink_title').value = ''; 8180 Dom.get(this.get('id') + '_createlink_target').checked = false; 8181 this.nodeChange(); 8182 this.currentElement = []; 8183 8184 }, 8185 /** 8186 * @private 8187 * @method _renderInsertImageWindow 8188 * @description Pre renders the InsertImage window so we get faster window opening. 8189 */ 8190 _renderInsertImageWindow: function() { 8191 var el = this.currentElement[0]; 8192 var str = '<label for="' + this.get('id') + '_insertimage_url"><strong>' + this.STR_IMAGE_URL + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_url" value="" size="40"></label>'; 8193 var body = document.createElement('div'); 8194 body.innerHTML = str; 8195 8196 var tbarCont = document.createElement('div'); 8197 tbarCont.id = this.get('id') + '_img_toolbar'; 8198 body.appendChild(tbarCont); 8199 8200 var str2 = '<label for="' + this.get('id') + '_insertimage_title"><strong>' + this.STR_IMAGE_TITLE + ':</strong> <input type="text" id="' + this.get('id') + '_insertimage_title" value="" size="40"></label>'; 8201 str2 += '<label for="' + this.get('id') + '_insertimage_link"><strong>' + this.STR_LINK_URL + ':</strong> <input type="text" name="' + this.get('id') + '_insertimage_link" id="' + this.get('id') + '_insertimage_link" value=""></label>'; 8202 str2 += '<label for="' + this.get('id') + '_insertimage_target"><strong> </strong><input type="checkbox" name="' + this.get('id') + '_insertimage_target_" id="' + this.get('id') + '_insertimage_target" value="_blank" class="insertimage_target"> ' + this.STR_LINK_NEW_WINDOW + '</label>'; 8203 var div = document.createElement('div'); 8204 div.innerHTML = str2; 8205 body.appendChild(div); 8206 8207 var o = {}; 8208 Lang.augmentObject(o, this._defaultImageToolbarConfig); //Break the config reference 8209 8210 var tbar = new YAHOO.widget.Toolbar(tbarCont, o); 8211 tbar.editor_el = el; 8212 this._defaultImageToolbar = tbar; 8213 8214 var cont = tbar.get('cont'); 8215 var hw = document.createElement('div'); 8216 hw.className = 'yui-toolbar-group yui-toolbar-group-height-width height-width'; 8217 hw.innerHTML = '<h3>' + this.STR_IMAGE_SIZE + ':</h3>'; 8218 hw.innerHTML += '<span tabIndex="-1"><input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_width"> x <input type="text" size="3" value="" id="' + this.get('id') + '_insertimage_height"></span>'; 8219 cont.insertBefore(hw, cont.firstChild); 8220 8221 Event.onAvailable(this.get('id') + '_insertimage_width', function() { 8222 Event.on(this.get('id') + '_insertimage_width', 'blur', function() { 8223 var value = parseInt(Dom.get(this.get('id') + '_insertimage_width').value, 10); 8224 if (value > 5) { 8225 this._defaultImageToolbar.editor_el.style.width = value + 'px'; 8226 //Removed moveWindow call so the window doesn't jump 8227 //this.moveWindow(); 8228 } 8229 }, this, true); 8230 }, this, true); 8231 Event.onAvailable(this.get('id') + '_insertimage_height', function() { 8232 Event.on(this.get('id') + '_insertimage_height', 'blur', function() { 8233 var value = parseInt(Dom.get(this.get('id') + '_insertimage_height').value, 10); 8234 if (value > 5) { 8235 this._defaultImageToolbar.editor_el.style.height = value + 'px'; 8236 //Removed moveWindow call so the window doesn't jump 8237 //this.moveWindow(); 8238 } 8239 }, this, true); 8240 }, this, true); 8241 8242 8243 tbar.on('colorPickerClicked', function(o) { 8244 var size = '1', type = 'solid', color = 'black', el = this._defaultImageToolbar.editor_el; 8245 8246 if (el.style.borderLeftWidth) { 8247 size = parseInt(el.style.borderLeftWidth, 10); 8248 } 8249 if (el.style.borderLeftStyle) { 8250 type = el.style.borderLeftStyle; 8251 } 8252 if (el.style.borderLeftColor) { 8253 color = el.style.borderLeftColor; 8254 } 8255 var borderString = size + 'px ' + type + ' #' + o.color; 8256 el.style.border = borderString; 8257 }, this, true); 8258 8259 tbar.on('buttonClick', function(o) { 8260 var value = o.button.value, 8261 el = this._defaultImageToolbar.editor_el, 8262 borderString = ''; 8263 if (o.button.menucmd) { 8264 value = o.button.menucmd; 8265 } 8266 var size = '1', type = 'solid', color = 'black'; 8267 8268 /* All border calcs are done on the left border 8269 since our default interface only supports 8270 one border size/type and color */ 8271 if (el.style.borderLeftWidth) { 8272 size = parseInt(el.style.borderLeftWidth, 10); 8273 } 8274 if (el.style.borderLeftStyle) { 8275 type = el.style.borderLeftStyle; 8276 } 8277 if (el.style.borderLeftColor) { 8278 color = el.style.borderLeftColor; 8279 } 8280 switch(value) { 8281 case 'bordersize': 8282 if (this.browser.webkit && this._lastImage) { 8283 Dom.removeClass(this._lastImage, 'selected'); 8284 this._lastImage = null; 8285 } 8286 8287 borderString = parseInt(o.button.value, 10) + 'px ' + type + ' ' + color; 8288 el.style.border = borderString; 8289 if (parseInt(o.button.value, 10) > 0) { 8290 tbar.enableButton('bordertype'); 8291 tbar.enableButton('bordercolor'); 8292 } else { 8293 tbar.disableButton('bordertype'); 8294 tbar.disableButton('bordercolor'); 8295 } 8296 break; 8297 case 'bordertype': 8298 if (this.browser.webkit && this._lastImage) { 8299 Dom.removeClass(this._lastImage, 'selected'); 8300 this._lastImage = null; 8301 } 8302 borderString = size + 'px ' + o.button.value + ' ' + color; 8303 el.style.border = borderString; 8304 break; 8305 case 'right': 8306 case 'left': 8307 tbar.deselectAllButtons(); 8308 el.style.display = ''; 8309 el.align = o.button.value; 8310 break; 8311 case 'inline': 8312 tbar.deselectAllButtons(); 8313 el.style.display = ''; 8314 el.align = ''; 8315 break; 8316 case 'block': 8317 tbar.deselectAllButtons(); 8318 el.style.display = 'block'; 8319 el.align = 'center'; 8320 break; 8321 case 'padding': 8322 var _button = tbar.getButtonById(o.button.id); 8323 el.style.margin = _button.get('label') + 'px'; 8324 break; 8325 } 8326 tbar.selectButton(o.button.value); 8327 if (value !== 'padding') { 8328 this.moveWindow(); 8329 } 8330 }, this, true); 8331 8332 8333 8334 if (this.get('localFileWarning')) { 8335 Event.on(this.get('id') + '_insertimage_link', 'blur', function() { 8336 var url = Dom.get(this.get('id') + '_insertimage_link'); 8337 if (this._isLocalFile(url.value)) { 8338 //Local File throw Warning 8339 Dom.addClass(url, 'warning'); 8340 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING); 8341 } else { 8342 Dom.removeClass(url, 'warning'); 8343 this.get('panel').setFooter(' '); 8344 //Adobe AIR Code 8345 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) { 8346 this.get('panel').setFooter(this.STR_IMAGE_COPY); 8347 } 8348 } 8349 }, this, true); 8350 } 8351 8352 Event.on(this.get('id') + '_insertimage_url', 'blur', function() { 8353 var url = Dom.get(this.get('id') + '_insertimage_url'), 8354 el = this.currentElement[0]; 8355 8356 if (url.value && el) { 8357 if (url.value == el.getAttribute('src', 2)) { 8358 return false; 8359 } 8360 } 8361 if (this._isLocalFile(url.value)) { 8362 //Local File throw Warning 8363 Dom.addClass(url, 'warning'); 8364 this.get('panel').setFooter(this.STR_LOCAL_FILE_WARNING); 8365 } else if (this.currentElement[0]) { 8366 Dom.removeClass(url, 'warning'); 8367 this.get('panel').setFooter(' '); 8368 //Adobe AIR Code 8369 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) { 8370 this.get('panel').setFooter(this.STR_IMAGE_COPY); 8371 } 8372 8373 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) { 8374 this.currentElement[0].setAttribute('src', url.value); 8375 var self = this, 8376 img = new Image(); 8377 8378 img.onerror = function() { 8379 url.value = self.STR_IMAGE_HERE; 8380 img.setAttribute('src', self.get('blankimage')); 8381 self.currentElement[0].setAttribute('src', self.get('blankimage')); 8382 YAHOO.util.Dom.get(self.get('id') + '_insertimage_height').value = img.height; 8383 YAHOO.util.Dom.get(self.get('id') + '_insertimage_width').value = img.width; 8384 }; 8385 var id = this.get('id'); 8386 window.setTimeout(function() { 8387 YAHOO.util.Dom.get(id + '_insertimage_height').value = img.height; 8388 YAHOO.util.Dom.get(id + '_insertimage_width').value = img.width; 8389 if (self.currentElement && self.currentElement[0]) { 8390 if (!self.currentElement[0]._height) { 8391 self.currentElement[0]._height = img.height; 8392 } 8393 if (!self.currentElement[0]._width) { 8394 self.currentElement[0]._width = img.width; 8395 } 8396 } 8397 //Removed moveWindow call so the window doesn't jump 8398 //self.moveWindow(); 8399 }, 800); //Bumped the timeout up to account for larger images.. 8400 8401 if (url.value != this.STR_IMAGE_HERE) { 8402 img.src = url.value; 8403 } 8404 } 8405 } 8406 }, this, true); 8407 8408 8409 8410 this._windows.insertimage = {}; 8411 this._windows.insertimage.body = body; 8412 //body.style.display = 'none'; 8413 this.get('panel').editor_form.appendChild(body); 8414 this.fireEvent('windowInsertImageRender', { type: 'windowInsertImageRender', panel: this.get('panel'), body: body, toolbar: tbar }); 8415 return body; 8416 }, 8417 /** 8418 * @private 8419 * @method _handleInsertImageClick 8420 * @description Opens the Image Properties Window when the insert Image button is clicked or an Image is Double Clicked. 8421 */ 8422 _handleInsertImageClick: function() { 8423 if (this.get('limitCommands')) { 8424 if (!this.toolbar.getButtonByValue('insertimage')) { 8425 return false; 8426 } 8427 } 8428 this.on('afterExecCommand', function() { 8429 var el = this.currentElement[0], 8430 body = null, 8431 link = '', 8432 target = '', 8433 tbar = null, 8434 title = '', 8435 src = '', 8436 align = '', 8437 height = 75, 8438 width = 75, 8439 padding = 0, 8440 oheight = 0, 8441 owidth = 0, 8442 blankimage = false, 8443 win = new YAHOO.widget.EditorWindow('insertimage', { 8444 width: '415px' 8445 }); 8446 8447 if (!el) { 8448 el = this._getSelectedElement(); 8449 } 8450 if (el) { 8451 win.el = el; 8452 if (el.getAttribute('src')) { 8453 src = el.getAttribute('src', 2); 8454 if (src.indexOf(this.get('blankimage')) != -1) { 8455 src = this.STR_IMAGE_HERE; 8456 blankimage = true; 8457 } 8458 } 8459 if (el.getAttribute('alt', 2)) { 8460 title = el.getAttribute('alt', 2); 8461 } 8462 if (el.getAttribute('title', 2)) { 8463 title = el.getAttribute('title', 2); 8464 } 8465 8466 if (el.parentNode && this._isElement(el.parentNode, 'a')) { 8467 link = el.parentNode.getAttribute('href', 2); 8468 if (el.parentNode.getAttribute('target') !== null) { 8469 target = el.parentNode.getAttribute('target'); 8470 } 8471 } 8472 height = parseInt(el.height, 10); 8473 width = parseInt(el.width, 10); 8474 if (el.style.height) { 8475 height = parseInt(el.style.height, 10); 8476 } 8477 if (el.style.width) { 8478 width = parseInt(el.style.width, 10); 8479 } 8480 if (el.style.margin) { 8481 padding = parseInt(el.style.margin, 10); 8482 } 8483 if (!blankimage) { 8484 if (!el._height) { 8485 el._height = height; 8486 } 8487 if (!el._width) { 8488 el._width = width; 8489 } 8490 oheight = el._height; 8491 owidth = el._width; 8492 } 8493 } 8494 if (this._windows.insertimage && this._windows.insertimage.body) { 8495 body = this._windows.insertimage.body; 8496 this._defaultImageToolbar.resetAllButtons(); 8497 } else { 8498 body = this._renderInsertImageWindow(); 8499 } 8500 8501 tbar = this._defaultImageToolbar; 8502 tbar.editor_el = el; 8503 8504 8505 var bsize = '0', 8506 btype = 'solid'; 8507 8508 if (el.style.borderLeftWidth) { 8509 bsize = parseInt(el.style.borderLeftWidth, 10); 8510 } 8511 if (el.style.borderLeftStyle) { 8512 btype = el.style.borderLeftStyle; 8513 } 8514 var bs_button = tbar.getButtonByValue('bordersize'), 8515 bSizeStr = ((parseInt(bsize, 10) > 0) ? '' : this.STR_NONE); 8516 bs_button.set('label', '<span class="yui-toolbar-bordersize-' + bsize + '">' + bSizeStr + '</span>'); 8517 this._updateMenuChecked('bordersize', bsize, tbar); 8518 8519 var bt_button = tbar.getButtonByValue('bordertype'); 8520 bt_button.set('label', '<span class="yui-toolbar-bordertype-' + btype + '">asdfa</span>'); 8521 this._updateMenuChecked('bordertype', btype, tbar); 8522 if (parseInt(bsize, 10) > 0) { 8523 tbar.enableButton(bt_button); 8524 tbar.enableButton(bs_button); 8525 tbar.enableButton('bordercolor'); 8526 } 8527 8528 if ((el.align == 'right') || (el.align == 'left')) { 8529 tbar.selectButton(el.align); 8530 } else if (el.style.display == 'block') { 8531 tbar.selectButton('block'); 8532 } else { 8533 tbar.selectButton('inline'); 8534 } 8535 if (parseInt(el.style.marginLeft, 10) > 0) { 8536 tbar.getButtonByValue('padding').set('label', ''+parseInt(el.style.marginLeft, 10)); 8537 } 8538 if (el.style.borderSize) { 8539 tbar.selectButton('bordersize'); 8540 tbar.selectButton(parseInt(el.style.borderSize, 10)); 8541 } 8542 tbar.getButtonByValue('padding').set('label', ''+padding); 8543 8544 8545 8546 win.setHeader(this.STR_IMAGE_PROP_TITLE); 8547 win.setBody(body); 8548 //Adobe AIR Code 8549 if ((this.browser.webkit && !this.browser.webkit3 || this.browser.air) || this.browser.opera) { 8550 win.setFooter(this.STR_IMAGE_COPY); 8551 } 8552 this.openWindow(win); 8553 Dom.get(this.get('id') + '_insertimage_url').value = src; 8554 Dom.get(this.get('id') + '_insertimage_title').value = title; 8555 Dom.get(this.get('id') + '_insertimage_link').value = link; 8556 Dom.get(this.get('id') + '_insertimage_target').checked = ((target) ? true : false); 8557 Dom.get(this.get('id') + '_insertimage_width').value = width; 8558 Dom.get(this.get('id') + '_insertimage_height').value = height; 8559 8560 8561 if (((height != oheight) || (width != owidth)) && (!blankimage)) { 8562 var s = document.createElement('span'); 8563 s.className = 'info'; 8564 s.innerHTML = this.STR_IMAGE_ORIG_SIZE + ': ('+ owidth +' x ' + oheight + ')'; 8565 if (Dom.get(this.get('id') + '_insertimage_height').nextSibling) { 8566 var old = Dom.get(this.get('id') + '_insertimage_height').nextSibling; 8567 old.parentNode.removeChild(old); 8568 } 8569 Dom.get(this.get('id') + '_insertimage_height').parentNode.appendChild(s); 8570 } 8571 8572 this.toolbar.selectButton('insertimage'); 8573 var id = this.get('id'); 8574 window.setTimeout(function() { 8575 try { 8576 YAHOO.util.Dom.get(id + '_insertimage_url').focus(); 8577 if (blankimage) { 8578 YAHOO.util.Dom.get(id + '_insertimage_url').select(); 8579 } 8580 } catch (e) {} 8581 }, 50); 8582 8583 }); 8584 }, 8585 /** 8586 * @private 8587 * @method _handleInsertImageWindowClose 8588 * @description Handles the closing of the Image Properties Window. 8589 */ 8590 _handleInsertImageWindowClose: function() { 8591 var url = Dom.get(this.get('id') + '_insertimage_url'), 8592 title = Dom.get(this.get('id') + '_insertimage_title'), 8593 link = Dom.get(this.get('id') + '_insertimage_link'), 8594 target = Dom.get(this.get('id') + '_insertimage_target'), 8595 el = arguments[0].win.el; 8596 8597 if (url && url.value && (url.value != this.STR_IMAGE_HERE)) { 8598 el.setAttribute('src', url.value); 8599 el.setAttribute('title', title.value); 8600 el.setAttribute('alt', title.value); 8601 var par = el.parentNode; 8602 if (link.value) { 8603 var urlValue = link.value; 8604 if ((urlValue.indexOf(':/'+'/') == -1) && (urlValue.substring(0,1) != '/') && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 8605 if ((urlValue.indexOf('@') != -1) && (urlValue.substring(0, 6).toLowerCase() != 'mailto')) { 8606 //Found an @ sign, prefix with mailto: 8607 urlValue = 'mailto:' + urlValue; 8608 } else { 8609 // :// not found adding 8610 urlValue = 'http:/'+'/' + urlValue; 8611 } 8612 } 8613 if (par && this._isElement(par, 'a')) { 8614 par.setAttribute('href', urlValue); 8615 if (target.checked) { 8616 par.setAttribute('target', target.value); 8617 } else { 8618 par.setAttribute('target', ''); 8619 } 8620 } else { 8621 var _a = this._getDoc().createElement('a'); 8622 _a.setAttribute('href', urlValue); 8623 if (target.checked) { 8624 _a.setAttribute('target', target.value); 8625 } else { 8626 _a.setAttribute('target', ''); 8627 } 8628 el.parentNode.replaceChild(_a, el); 8629 _a.appendChild(el); 8630 } 8631 } else { 8632 if (par && this._isElement(par, 'a')) { 8633 par.parentNode.replaceChild(el, par); 8634 } 8635 } 8636 } else { 8637 //No url/src given, remove the node from the document 8638 el.parentNode.removeChild(el); 8639 } 8640 Dom.get(this.get('id') + '_insertimage_url').value = ''; 8641 Dom.get(this.get('id') + '_insertimage_title').value = ''; 8642 Dom.get(this.get('id') + '_insertimage_link').value = ''; 8643 Dom.get(this.get('id') + '_insertimage_target').checked = false; 8644 Dom.get(this.get('id') + '_insertimage_width').value = 0; 8645 Dom.get(this.get('id') + '_insertimage_height').value = 0; 8646 this._defaultImageToolbar.resetAllButtons(); 8647 this.currentElement = []; 8648 this.nodeChange(); 8649 }, 8650 /** 8651 * @property EDITOR_PANEL_ID 8652 * @description HTML id to give the properties window in the DOM. 8653 * @type String 8654 */ 8655 EDITOR_PANEL_ID: '-panel', 8656 /** 8657 * @private 8658 * @method _renderPanel 8659 * @description Renders the panel used for Editor Windows to the document so we can start using it.. 8660 * @return {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>} 8661 */ 8662 _renderPanel: function() { 8663 var panelEl = document.createElement('div'); 8664 Dom.addClass(panelEl, 'yui-editor-panel'); 8665 panelEl.id = this.get('id') + this.EDITOR_PANEL_ID; 8666 panelEl.style.position = 'absolute'; 8667 panelEl.style.top = '-9999px'; 8668 panelEl.style.left = '-9999px'; 8669 document.body.appendChild(panelEl); 8670 this.get('element_cont').insertBefore(panelEl, this.get('element_cont').get('firstChild')); 8671 8672 8673 8674 var panel = new YAHOO.widget.Overlay(this.get('id') + this.EDITOR_PANEL_ID, { 8675 width: '300px', 8676 iframe: true, 8677 visible: false, 8678 underlay: 'none', 8679 draggable: false, 8680 close: false 8681 }); 8682 this.set('panel', panel); 8683 8684 panel.setBody('---'); 8685 panel.setHeader(' '); 8686 panel.setFooter(' '); 8687 8688 8689 var body = document.createElement('div'); 8690 body.className = this.CLASS_PREFIX + '-body-cont'; 8691 for (var b in this.browser) { 8692 if (this.browser[b]) { 8693 Dom.addClass(body, b); 8694 break; 8695 } 8696 } 8697 Dom.addClass(body, ((YAHOO.widget.Button && (this._defaultToolbar.buttonType == 'advanced')) ? 'good-button' : 'no-button')); 8698 8699 var _note = document.createElement('h3'); 8700 _note.className = 'yui-editor-skipheader'; 8701 _note.innerHTML = this.STR_CLOSE_WINDOW_NOTE; 8702 body.appendChild(_note); 8703 var form = document.createElement('fieldset'); 8704 panel.editor_form = form; 8705 8706 body.appendChild(form); 8707 var _close = document.createElement('span'); 8708 _close.innerHTML = 'X'; 8709 _close.title = this.STR_CLOSE_WINDOW; 8710 _close.className = 'close'; 8711 8712 Event.on(_close, 'click', this.closeWindow, this, true); 8713 8714 var _knob = document.createElement('span'); 8715 _knob.innerHTML = '^'; 8716 _knob.className = 'knob'; 8717 panel.editor_knob = _knob; 8718 8719 var _header = document.createElement('h3'); 8720 panel.editor_header = _header; 8721 _header.innerHTML = '<span></span>'; 8722 8723 panel.setHeader(' '); //Clear the current header 8724 panel.appendToHeader(_header); 8725 _header.appendChild(_close); 8726 _header.appendChild(_knob); 8727 panel.setBody(' '); //Clear the current body 8728 panel.setFooter(' '); //Clear the current footer 8729 panel.appendToBody(body); //Append the new DOM node to it 8730 8731 Event.on(panel.element, 'click', function(ev) { 8732 Event.stopPropagation(ev); 8733 }); 8734 8735 var fireShowEvent = function() { 8736 panel.bringToTop(); 8737 YAHOO.util.Dom.setStyle(this.element, 'display', 'block'); 8738 this._handleWindowInputs(false); 8739 }; 8740 panel.showEvent.subscribe(fireShowEvent, this, true); 8741 panel.hideEvent.subscribe(function() { 8742 this._handleWindowInputs(true); 8743 }, this, true); 8744 panel.renderEvent.subscribe(function() { 8745 this._renderInsertImageWindow(); 8746 this._renderCreateLinkWindow(); 8747 this.fireEvent('windowRender', { type: 'windowRender', panel: panel }); 8748 this._handleWindowInputs(true); 8749 }, this, true); 8750 8751 if (this.DOMReady) { 8752 this.get('panel').render(); 8753 } else { 8754 Event.onDOMReady(function() { 8755 this.get('panel').render(); 8756 }, this, true); 8757 } 8758 return this.get('panel'); 8759 }, 8760 /** 8761 * @method _handleWindowInputs 8762 * @param {Boolean} disable The state to set all inputs in all Editor windows to. Defaults to: false. 8763 * @description Disables/Enables all fields inside Editor windows. Used in show/hide events to keep window fields from submitting when the parent form is submitted. 8764 */ 8765 _handleWindowInputs: function(disable) { 8766 if (!Lang.isBoolean(disable)) { 8767 disable = false; 8768 } 8769 var inputs = this.get('panel').element.getElementsByTagName('input'); 8770 for (var i = 0; i < inputs.length; i++) { 8771 try { 8772 inputs[i].disabled = disable; 8773 } catch (e) {} 8774 } 8775 }, 8776 /** 8777 * @method openWindow 8778 * @param {<a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a>} win A <a href="YAHOO.widget.EditorWindow.html">YAHOO.widget.EditorWindow</a> instance 8779 * @description Opens a new "window/panel" 8780 */ 8781 openWindow: function(win) { 8782 8783 var self = this; 8784 window.setTimeout(function() { 8785 self.toolbar.set('disabled', true); //Disable the toolbar when an editor window is open.. 8786 }, 10); 8787 Event.on(document, 'keydown', this._closeWindow, this, true); 8788 8789 if (this.currentWindow) { 8790 this.closeWindow(); 8791 } 8792 8793 var xy = Dom.getXY(this.currentElement[0]), 8794 elXY = Dom.getXY(this.get('iframe').get('element')), 8795 panel = this.get('panel'), 8796 newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)], 8797 wWidth = (parseInt(win.attrs.width, 10) / 2), 8798 align = 'center', 8799 body = null; 8800 8801 this.fireEvent('beforeOpenWindow', { type: 'beforeOpenWindow', win: win, panel: panel }); 8802 8803 var form = panel.editor_form; 8804 8805 var wins = this._windows; 8806 for (var b in wins) { 8807 if (Lang.hasOwnProperty(wins, b)) { 8808 if (wins[b] && wins[b].body) { 8809 if (b == win.name) { 8810 Dom.setStyle(wins[b].body, 'display', 'block'); 8811 } else { 8812 Dom.setStyle(wins[b].body, 'display', 'none'); 8813 } 8814 } 8815 } 8816 } 8817 8818 if (this._windows[win.name].body) { 8819 Dom.setStyle(this._windows[win.name].body, 'display', 'block'); 8820 form.appendChild(this._windows[win.name].body); 8821 } else { 8822 if (Lang.isObject(win.body)) { //Assume it's a reference 8823 form.appendChild(win.body); 8824 } else { //Assume it's a string 8825 var _tmp = document.createElement('div'); 8826 _tmp.innerHTML = win.body; 8827 form.appendChild(_tmp); 8828 } 8829 } 8830 panel.editor_header.firstChild.innerHTML = win.header; 8831 if (win.footer !== null) { 8832 panel.setFooter(win.footer); 8833 } 8834 panel.cfg.setProperty('width', win.attrs.width); 8835 8836 this.currentWindow = win; 8837 this.moveWindow(true); 8838 panel.show(); 8839 this.fireEvent('afterOpenWindow', { type: 'afterOpenWindow', win: win, panel: panel }); 8840 }, 8841 /** 8842 * @method moveWindow 8843 * @param {Boolean} force Boolean to tell it to move but not use any animation (Usually done the first time the window is loaded.) 8844 * @description Realign the window with the currentElement and reposition the knob above the panel. 8845 */ 8846 moveWindow: function(force) { 8847 if (!this.currentWindow) { 8848 return false; 8849 } 8850 var win = this.currentWindow, 8851 xy = Dom.getXY(this.currentElement[0]), 8852 elXY = Dom.getXY(this.get('iframe').get('element')), 8853 panel = this.get('panel'), 8854 //newXY = [(xy[0] + elXY[0] - 20), (xy[1] + elXY[1] + 10)], 8855 newXY = [(xy[0] + elXY[0]), (xy[1] + elXY[1])], 8856 wWidth = (parseInt(win.attrs.width, 10) / 2), 8857 align = 'center', 8858 orgXY = panel.cfg.getProperty('xy') || [0,0], 8859 _knob = panel.editor_knob, 8860 xDiff = 0, 8861 yDiff = 0, 8862 anim = false; 8863 8864 8865 newXY[0] = ((newXY[0] - wWidth) + 20); 8866 //Account for the Scroll bars in a scrolled editor window. 8867 newXY[0] = newXY[0] - Dom.getDocumentScrollLeft(this._getDoc()); 8868 newXY[1] = newXY[1] - Dom.getDocumentScrollTop(this._getDoc()); 8869 8870 if (this._isElement(this.currentElement[0], 'img')) { 8871 if (this.currentElement[0].src.indexOf(this.get('blankimage')) != -1) { 8872 newXY[0] = (newXY[0] + (75 / 2)); //Placeholder size 8873 newXY[1] = (newXY[1] + 75); //Placeholder sizea 8874 } else { 8875 var w = parseInt(this.currentElement[0].width, 10); 8876 var h = parseInt(this.currentElement[0].height, 10); 8877 newXY[0] = (newXY[0] + (w / 2)); 8878 newXY[1] = (newXY[1] + h); 8879 } 8880 newXY[1] = newXY[1] + 15; 8881 } else { 8882 var fs = Dom.getStyle(this.currentElement[0], 'fontSize'); 8883 if (fs && fs.indexOf && fs.indexOf('px') != -1) { 8884 newXY[1] = newXY[1] + parseInt(Dom.getStyle(this.currentElement[0], 'fontSize'), 10) + 5; 8885 } else { 8886 newXY[1] = newXY[1] + 20; 8887 } 8888 } 8889 if (newXY[0] < elXY[0]) { 8890 newXY[0] = elXY[0] + 5; 8891 align = 'left'; 8892 } 8893 8894 if ((newXY[0] + (wWidth * 2)) > (elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10))) { 8895 newXY[0] = ((elXY[0] + parseInt(this.get('iframe').get('element').clientWidth, 10)) - (wWidth * 2) - 5); 8896 align = 'right'; 8897 } 8898 8899 try { 8900 xDiff = (newXY[0] - orgXY[0]); 8901 yDiff = (newXY[1] - orgXY[1]); 8902 } catch (e) {} 8903 8904 8905 var iTop = elXY[1] + parseInt(this.get('height'), 10); 8906 var iLeft = elXY[0] + parseInt(this.get('width'), 10); 8907 if (newXY[1] > iTop) { 8908 newXY[1] = iTop; 8909 } 8910 if (newXY[0] > iLeft) { 8911 newXY[0] = (iLeft / 2); 8912 } 8913 8914 //Convert negative numbers to positive so we can get the difference in distance 8915 xDiff = ((xDiff < 0) ? (xDiff * -1) : xDiff); 8916 yDiff = ((yDiff < 0) ? (yDiff * -1) : yDiff); 8917 8918 if (((xDiff > 10) || (yDiff > 10)) || force) { //Only move the window if it's supposed to move more than 10px or force was passed (new window) 8919 var _knobLeft = 0, 8920 elW = 0; 8921 8922 if (this.currentElement[0].width) { 8923 elW = (parseInt(this.currentElement[0].width, 10) / 2); 8924 } 8925 8926 var leftOffset = xy[0] + elXY[0] + elW; 8927 _knobLeft = leftOffset - newXY[0]; 8928 //Check to see if the knob will go off either side & reposition it 8929 if (_knobLeft > (parseInt(win.attrs.width, 10) - 1)) { 8930 _knobLeft = ((parseInt(win.attrs.width, 10) - 30) - 1); 8931 } else if (_knobLeft < 40) { 8932 _knobLeft = 1; 8933 } 8934 if (isNaN(_knobLeft)) { 8935 _knobLeft = 1; 8936 } 8937 if (force) { 8938 if (_knob) { 8939 _knob.style.left = _knobLeft + 'px'; 8940 } 8941 //Removed Animation from a forced move.. 8942 panel.cfg.setProperty('xy', newXY); 8943 } else { 8944 if (this.get('animate')) { 8945 anim = new YAHOO.util.Anim(panel.element, {}, 0.5, YAHOO.util.Easing.easeOut); 8946 anim.attributes = { 8947 top: { 8948 to: newXY[1] 8949 }, 8950 left: { 8951 to: newXY[0] 8952 } 8953 }; 8954 anim.onComplete.subscribe(function() { 8955 panel.cfg.setProperty('xy', newXY); 8956 }); 8957 //We have to animate the iframe shim at the same time as the panel or we get scrollbar bleed .. 8958 var iframeAnim = new YAHOO.util.Anim(panel.iframe, anim.attributes, 0.5, YAHOO.util.Easing.easeOut); 8959 8960 var _knobAnim = new YAHOO.util.Anim(_knob, { 8961 left: { 8962 to: _knobLeft 8963 } 8964 }, 0.6, YAHOO.util.Easing.easeOut); 8965 anim.animate(); 8966 iframeAnim.animate(); 8967 _knobAnim.animate(); 8968 } else { 8969 _knob.style.left = _knobLeft + 'px'; 8970 panel.cfg.setProperty('xy', newXY); 8971 } 8972 } 8973 } 8974 }, 8975 /** 8976 * @private 8977 * @method _closeWindow 8978 * @description Close the currently open EditorWindow with the Escape key. 8979 * @param {Event} ev The keypress Event that we are trapping 8980 */ 8981 _closeWindow: function(ev) { 8982 //if ((ev.charCode == 87) && ev.shiftKey && ev.ctrlKey) { 8983 if (this._checkKey(this._keyMap.CLOSE_WINDOW, ev)) { 8984 if (this.currentWindow) { 8985 this.closeWindow(); 8986 } 8987 } 8988 }, 8989 /** 8990 * @method closeWindow 8991 * @description Close the currently open EditorWindow. 8992 */ 8993 closeWindow: function(keepOpen) { 8994 this.fireEvent('window' + this.currentWindow.name + 'Close', { type: 'window' + this.currentWindow.name + 'Close', win: this.currentWindow, el: this.currentElement[0] }); 8995 this.fireEvent('closeWindow', { type: 'closeWindow', win: this.currentWindow }); 8996 this.currentWindow = null; 8997 this.get('panel').hide(); 8998 this.get('panel').cfg.setProperty('xy', [-900,-900]); 8999 this.get('panel').syncIframe(); //Needed to move the iframe with the hidden panel 9000 this.unsubscribeAll('afterExecCommand'); 9001 this.toolbar.set('disabled', false); //enable the toolbar now that the window is closed 9002 this.toolbar.resetAllButtons(); 9003 this.focus(); 9004 Event.removeListener(document, 'keydown', this._closeWindow); 9005 }, 9006 9007 /* {{{ Command Overrides - These commands are only over written when we are using the advanced version */ 9008 9009 /** 9010 * @method cmd_undo 9011 * @description Pulls an item from the Undo stack and updates the Editor 9012 * @param value Value passed from the execCommand method 9013 */ 9014 cmd_undo: function(value) { 9015 if (this._hasUndoLevel()) { 9016 var c_html = this.getEditorHTML(), html; 9017 if (!this._undoLevel) { 9018 this._undoLevel = this._undoCache.length; 9019 } 9020 this._undoLevel = (this._undoLevel - 1); 9021 if (this._undoCache[this._undoLevel]) { 9022 html = this._getUndo(this._undoLevel); 9023 if (html != c_html) { 9024 this.setEditorHTML(html); 9025 } else { 9026 this._undoLevel = (this._undoLevel - 1); 9027 html = this._getUndo(this._undoLevel); 9028 if (html != c_html) { 9029 this.setEditorHTML(html); 9030 } 9031 } 9032 } else { 9033 this._undoLevel = 0; 9034 this.toolbar.disableButton('undo'); 9035 } 9036 } 9037 return [false]; 9038 }, 9039 9040 /** 9041 * @method cmd_redo 9042 * @description Pulls an item from the Undo stack and updates the Editor 9043 * @param value Value passed from the execCommand method 9044 */ 9045 cmd_redo: function(value) { 9046 this._undoLevel = this._undoLevel + 1; 9047 if (this._undoLevel >= this._undoCache.length) { 9048 this._undoLevel = this._undoCache.length; 9049 } 9050 if (this._undoCache[this._undoLevel]) { 9051 var html = this._getUndo(this._undoLevel); 9052 this.setEditorHTML(html); 9053 } else { 9054 this.toolbar.disableButton('redo'); 9055 } 9056 return [false]; 9057 }, 9058 9059 /** 9060 * @method cmd_heading 9061 * @param value Value passed from the execCommand method 9062 * @description This is an execCommand override method. It is called from execCommand when the execCommand('heading') is used. 9063 */ 9064 cmd_heading: function(value) { 9065 var exec = true, 9066 el = null, 9067 action = 'heading', 9068 _sel = this._getSelection(), 9069 _selEl = this._getSelectedElement(); 9070 9071 if (_selEl) { 9072 _sel = _selEl; 9073 } 9074 9075 if (this.browser.ie) { 9076 action = 'formatblock'; 9077 } 9078 if (value == this.STR_NONE) { 9079 if ((_sel && _sel.tagName && (_sel.tagName.toLowerCase().substring(0,1) == 'h')) || (_sel && _sel.parentNode && _sel.parentNode.tagName && (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h'))) { 9080 if (_sel.parentNode.tagName.toLowerCase().substring(0,1) == 'h') { 9081 _sel = _sel.parentNode; 9082 } 9083 if (this._isElement(_sel, 'html')) { 9084 return [false]; 9085 } 9086 el = this._swapEl(_selEl, 'span', function(el) { 9087 el.className = 'yui-non'; 9088 }); 9089 this._selectNode(el); 9090 this.currentElement[0] = el; 9091 } 9092 exec = false; 9093 } else { 9094 if (this._isElement(_selEl, 'h1') || this._isElement(_selEl, 'h2') || this._isElement(_selEl, 'h3') || this._isElement(_selEl, 'h4') || this._isElement(_selEl, 'h5') || this._isElement(_selEl, 'h6')) { 9095 el = this._swapEl(_selEl, value); 9096 this._selectNode(el); 9097 this.currentElement[0] = el; 9098 } else { 9099 this._createCurrentElement(value); 9100 this._selectNode(this.currentElement[0]); 9101 } 9102 exec = false; 9103 } 9104 return [exec, action]; 9105 }, 9106 /** 9107 * @method cmd_hiddenelements 9108 * @param value Value passed from the execCommand method 9109 * @description This is an execCommand override method. It is called from execCommand when the execCommand('hiddenelements') is used. 9110 */ 9111 cmd_hiddenelements: function(value) { 9112 if (this._showingHiddenElements) { 9113 //Don't auto highlight the hidden button 9114 this._lastButton = null; 9115 this._showingHiddenElements = false; 9116 this.toolbar.deselectButton('hiddenelements'); 9117 Dom.removeClass(this._getDoc().body, this.CLASS_HIDDEN); 9118 } else { 9119 this._showingHiddenElements = true; 9120 Dom.addClass(this._getDoc().body, this.CLASS_HIDDEN); 9121 this.toolbar.selectButton('hiddenelements'); 9122 } 9123 return [false]; 9124 }, 9125 /** 9126 * @method cmd_removeformat 9127 * @param value Value passed from the execCommand method 9128 * @description This is an execCommand override method. It is called from execCommand when the execCommand('removeformat') is used. 9129 */ 9130 cmd_removeformat: function(value) { 9131 var exec = true; 9132 /* 9133 * @knownissue Remove Format issue 9134 * @browser Safari 2.x 9135 * @description There is an issue here with Safari, that it may not always remove the format of the item that is selected. 9136 * Due to the way that Safari 2.x handles ranges, it is very difficult to determine what the selection holds. 9137 * So here we are making the best possible guess and acting on it. 9138 */ 9139 if (this.browser.webkit && !this._getDoc().queryCommandEnabled('removeformat')) { 9140 var _txt = this._getSelection()+''; 9141 this._createCurrentElement('span'); 9142 this.currentElement[0].className = 'yui-non'; 9143 this.currentElement[0].innerHTML = _txt; 9144 for (var i = 1; i < this.currentElement.length; i++) { 9145 this.currentElement[i].parentNode.removeChild(this.currentElement[i]); 9146 } 9147 9148 exec = false; 9149 } 9150 return [exec]; 9151 }, 9152 /** 9153 * @method cmd_script 9154 * @param action action passed from the execCommand method 9155 * @param value Value passed from the execCommand method 9156 * @description This is a combined execCommand override method. It is called from the cmd_superscript and cmd_subscript methods. 9157 */ 9158 cmd_script: function(action, value) { 9159 var exec = true, tag = action.toLowerCase().substring(0, 3), 9160 _span = null, _selEl = this._getSelectedElement(); 9161 9162 if (this.browser.webkit) { 9163 if (this._isElement(_selEl, tag)) { 9164 _span = this._swapEl(this.currentElement[0], 'span', function(el) { 9165 el.className = 'yui-non'; 9166 }); 9167 this._selectNode(_span); 9168 } else { 9169 this._createCurrentElement(tag); 9170 var _sub = this._swapEl(this.currentElement[0], tag); 9171 this._selectNode(_sub); 9172 this.currentElement[0] = _sub; 9173 } 9174 exec = false; 9175 } 9176 return exec; 9177 }, 9178 /** 9179 * @method cmd_superscript 9180 * @param value Value passed from the execCommand method 9181 * @description This is an execCommand override method. It is called from execCommand when the execCommand('superscript') is used. 9182 */ 9183 cmd_superscript: function(value) { 9184 return [this.cmd_script('superscript', value)]; 9185 }, 9186 /** 9187 * @method cmd_subscript 9188 * @param value Value passed from the execCommand method 9189 * @description This is an execCommand override method. It is called from execCommand when the execCommand('subscript') is used. 9190 */ 9191 cmd_subscript: function(value) { 9192 return [this.cmd_script('subscript', value)]; 9193 }, 9194 /** 9195 * @method cmd_indent 9196 * @param value Value passed from the execCommand method 9197 * @description This is an execCommand override method. It is called from execCommand when the execCommand('indent') is used. 9198 */ 9199 cmd_indent: function(value) { 9200 var exec = true, selEl = this._getSelectedElement(), _bq = null; 9201 9202 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) { 9203 //if (this.browser.webkit || this.browser.ie) { 9204 if (this.browser.ie) { 9205 if (this._isElement(selEl, 'blockquote')) { 9206 _bq = this._getDoc().createElement('blockquote'); 9207 _bq.innerHTML = selEl.innerHTML; 9208 selEl.innerHTML = ''; 9209 selEl.appendChild(_bq); 9210 this._selectNode(_bq); 9211 } else { 9212 _bq = this._getDoc().createElement('blockquote'); 9213 var html = this._getRange().htmlText; 9214 _bq.innerHTML = html; 9215 this._createCurrentElement('blockquote'); 9216 /* 9217 for (var i = 0; i < this.currentElement.length; i++) { 9218 _bq = this._getDoc().createElement('blockquote'); 9219 _bq.innerHTML = this.currentElement[i].innerHTML; 9220 this.currentElement[i].parentNode.replaceChild(_bq, this.currentElement[i]); 9221 this.currentElement[i] = _bq; 9222 } 9223 */ 9224 this.currentElement[0].parentNode.replaceChild(_bq, this.currentElement[0]); 9225 this.currentElement[0] = _bq; 9226 this._selectNode(this.currentElement[0]); 9227 } 9228 exec = false; 9229 } else { 9230 value = 'blockquote'; 9231 } 9232 return [exec, 'formatblock', value]; 9233 }, 9234 /** 9235 * @method cmd_outdent 9236 * @param value Value passed from the execCommand method 9237 * @description This is an execCommand override method. It is called from execCommand when the execCommand('outdent') is used. 9238 */ 9239 cmd_outdent: function(value) { 9240 var exec = true, selEl = this._getSelectedElement(), _bq = null, _span = null; 9241 //if (this.browser.webkit || this.browser.ie || this.browser.gecko) { 9242 if (this.browser.webkit || this.browser.ie) { 9243 //if (this.browser.ie) { 9244 selEl = this._getSelectedElement(); 9245 if (this._isElement(selEl, 'blockquote')) { 9246 var par = selEl.parentNode; 9247 if (this._isElement(selEl.parentNode, 'blockquote')) { 9248 par.innerHTML = selEl.innerHTML; 9249 this._selectNode(par); 9250 } else { 9251 _span = this._getDoc().createElement('span'); 9252 _span.innerHTML = selEl.innerHTML; 9253 YAHOO.util.Dom.addClass(_span, 'yui-non'); 9254 par.replaceChild(_span, selEl); 9255 this._selectNode(_span); 9256 } 9257 } else { 9258 } 9259 exec = false; 9260 } else { 9261 value = false; 9262 } 9263 return [exec, 'outdent', value]; 9264 }, 9265 /** 9266 * @method cmd_justify 9267 * @param dir The direction to justify 9268 * @description This is a factory method for the justify family of commands. 9269 */ 9270 cmd_justify: function(dir) { 9271 if (this.browser.ie) { 9272 if (this._hasSelection()) { 9273 this._createCurrentElement('span'); 9274 this._swapEl(this.currentElement[0], 'div', function(el) { 9275 el.style.textAlign = dir; 9276 }); 9277 9278 return [false]; 9279 } 9280 } 9281 return [true, 'justify' + dir, '']; 9282 }, 9283 /** 9284 * @method cmd_justifycenter 9285 * @param value Value passed from the execCommand method 9286 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifycenter') is used. 9287 */ 9288 cmd_justifycenter: function() { 9289 return [this.cmd_justify('center')]; 9290 }, 9291 /** 9292 * @method cmd_justifyleft 9293 * @param value Value passed from the execCommand method 9294 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyleft') is used. 9295 */ 9296 cmd_justifyleft: function() { 9297 return [this.cmd_justify('left')]; 9298 }, 9299 /** 9300 * @method cmd_justifyright 9301 * @param value Value passed from the execCommand method 9302 * @description This is an execCommand override method. It is called from execCommand when the execCommand('justifyright') is used. 9303 */ 9304 cmd_justifyright: function() { 9305 return [this.cmd_justify('right')]; 9306 }, 9307 /* }}}*/ 9308 /** 9309 * @method toString 9310 * @description Returns a string representing the editor. 9311 * @return {String} 9312 */ 9313 toString: function() { 9314 var str = 'Editor'; 9315 if (this.get && this.get('element_cont')) { 9316 str = 'Editor (#' + this.get('element_cont').get('id') + ')' + ((this.get('disabled') ? ' Disabled' : '')); 9317 } 9318 return str; 9319 } 9320 }); 9321 /** 9322 * @event beforeOpenWindow 9323 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9324 * @param {Overlay} panel The Overlay object that is used to create the window. 9325 * @description Event fires before an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 9326 * @type YAHOO.util.CustomEvent 9327 */ 9328 /** 9329 * @event afterOpenWindow 9330 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9331 * @param {Overlay} panel The Overlay object that is used to create the window. 9332 * @description Event fires after an Editor Window is opened. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 9333 * @type YAHOO.util.CustomEvent 9334 */ 9335 /** 9336 * @event closeWindow 9337 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9338 * @description Event fires after an Editor Window is closed. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 9339 * @type YAHOO.util.CustomEvent 9340 */ 9341 /** 9342 * @event windowCMDOpen 9343 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9344 * @param {Overlay} panel The Overlay object that is used to create the window. 9345 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is opened.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkOpen event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 9346 * @type YAHOO.util.CustomEvent 9347 */ 9348 /** 9349 * @event windowCMDClose 9350 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9351 * @param {Overlay} panel The Overlay object that is used to create the window. 9352 * @description Dynamic event fired when an <a href="YAHOO.widget.EditorWindow.html">EditorWindow</a> is closed.. The dynamic event is based on the name of the window. Example Window: createlink, opening this window would fire the windowcreatelinkClose event. See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event. 9353 * @type YAHOO.util.CustomEvent 9354 */ 9355 /** 9356 * @event windowRender 9357 * @param {<a href="YAHOO.widget.EditorWindow.html">EditorWindow</a>} win The EditorWindow object 9358 * @param {Overlay} panel The Overlay object that is used to create the window. 9359 * @description Event fired when the initial Overlay is rendered. Can be used to manipulate the content of the panel. 9360 * @type YAHOO.util.CustomEvent 9361 */ 9362 /** 9363 * @event windowInsertImageRender 9364 * @param {Overlay} panel The Overlay object that is used to create the window. 9365 * @param {HTMLElement} body The HTML element used as the body of the window.. 9366 * @param {Toolbar} toolbar A reference to the toolbar object used inside this window. 9367 * @description Event fired when the pre render of the Insert Image window has finished. 9368 * @type YAHOO.util.CustomEvent 9369 */ 9370 /** 9371 * @event windowCreateLinkRender 9372 * @param {Overlay} panel The Overlay object that is used to create the window. 9373 * @param {HTMLElement} body The HTML element used as the body of the window.. 9374 * @description Event fired when the pre render of the Create Link window has finished. 9375 * @type YAHOO.util.CustomEvent 9376 */ 9377 9378 9379 9380 /** 9381 * @description Class to hold Window information between uses. We use the same panel to show the windows, so using this will allow you to configure a window before it is shown. 9382 * This is what you pass to Editor.openWindow();. These parameters will not take effect until the openWindow() is called in the editor. 9383 * @class EditorWindow 9384 * @param {String} name The name of the window. 9385 * @param {Object} attrs Attributes for the window. Current attributes used are : height and width 9386 */ 9387 YAHOO.widget.EditorWindow = function(name, attrs) { 9388 /** 9389 * @private 9390 * @property name 9391 * @description A unique name for the window 9392 */ 9393 this.name = name.replace(' ', '_'); 9394 /** 9395 * @private 9396 * @property attrs 9397 * @description The window attributes 9398 */ 9399 this.attrs = attrs; 9400 }; 9401 9402 YAHOO.widget.EditorWindow.prototype = { 9403 /** 9404 * @private 9405 * @property header 9406 * @description Holder for the header of the window, used in Editor.openWindow 9407 */ 9408 header: null, 9409 /** 9410 * @private 9411 * @property body 9412 * @description Holder for the body of the window, used in Editor.openWindow 9413 */ 9414 body: null, 9415 /** 9416 * @private 9417 * @property footer 9418 * @description Holder for the footer of the window, used in Editor.openWindow 9419 */ 9420 footer: null, 9421 /** 9422 * @method setHeader 9423 * @description Sets the header for the window. 9424 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows header. 9425 */ 9426 setHeader: function(str) { 9427 this.header = str; 9428 }, 9429 /** 9430 * @method setBody 9431 * @description Sets the body for the window. 9432 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows body. 9433 */ 9434 setBody: function(str) { 9435 this.body = str; 9436 }, 9437 /** 9438 * @method setFooter 9439 * @description Sets the footer for the window. 9440 * @param {String/HTMLElement} str The string or DOM reference to be used as the windows footer. 9441 */ 9442 setFooter: function(str) { 9443 this.footer = str; 9444 }, 9445 /** 9446 * @method toString 9447 * @description Returns a string representing the EditorWindow. 9448 * @return {String} 9449 */ 9450 toString: function() { 9451 return 'Editor Window (' + this.name + ')'; 9452 } 9453 }; 9454 })(); 9455 YAHOO.register("editor", YAHOO.widget.Editor, {version: "2.9.0", build: "2800"}); 9456 9457 }, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-event", "yui2-containercore", "yui2-skin-sam-editor", "yui2-skin-sam-button", "yui2-element", "yui2-skin-sam-menu", "yui2-menu", "yui2-button"], "supersedes": ["yui2-simpleeditor"], "optional": ["yui2-animation", "yui2-dragdrop"]});
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 |