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