1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 /** 7 * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which 8 * represents a DOM element. 9 */ 10 11 /** 12 * Represents a DOM element. 13 * @constructor 14 * @augments CKEDITOR.dom.node 15 * @param {Object|String} element A native DOM element or the element name for 16 * new elements. 17 * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain 18 * the element in case of element creation. 19 * @example 20 * // Create a new <span> element. 21 * var element = new CKEDITOR.dom.element( 'span' ); 22 * @example 23 * // Create an element based on a native DOM element. 24 * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) ); 25 */ 26 CKEDITOR.dom.element = function( element, ownerDocument ) 27 { 28 if ( typeof element == 'string' ) 29 element = ( ownerDocument ? ownerDocument.$ : document ).createElement( element ); 30 31 // Call the base constructor (we must not call CKEDITOR.dom.node). 32 CKEDITOR.dom.domObject.call( this, element ); 33 }; 34 35 // PACKAGER_RENAME( CKEDITOR.dom.element ) 36 37 /** 38 * The the {@link CKEDITOR.dom.element} representing and element. If the 39 * element is a native DOM element, it will be transformed into a valid 40 * CKEDITOR.dom.element object. 41 * @returns {CKEDITOR.dom.element} The transformed element. 42 * @example 43 * var element = new CKEDITOR.dom.element( 'span' ); 44 * alert( element == <b>CKEDITOR.dom.element.get( element )</b> ); "true" 45 * @example 46 * var element = document.getElementById( 'myElement' ); 47 * alert( <b>CKEDITOR.dom.element.get( element )</b>.getName() ); e.g. "p" 48 */ 49 CKEDITOR.dom.element.get = function( element ) 50 { 51 return element && ( element.$ ? element : new CKEDITOR.dom.element( element ) ); 52 }; 53 54 CKEDITOR.dom.element.prototype = new CKEDITOR.dom.node(); 55 56 /** 57 * Creates an instance of the {@link CKEDITOR.dom.element} class based on the 58 * HTML representation of an element. 59 * @param {String} html The element HTML. It should define only one element in 60 * the "root" level. The "root" element can have child nodes, but not 61 * siblings. 62 * @returns {CKEDITOR.dom.element} The element instance. 63 * @example 64 * var element = <b>CKEDITOR.dom.element.createFromHtml( '<strong class="anyclass">My element</strong>' )</b>; 65 * alert( element.getName() ); // "strong" 66 */ 67 CKEDITOR.dom.element.createFromHtml = function( html, ownerDocument ) 68 { 69 var temp = new CKEDITOR.dom.element( 'div', ownerDocument ); 70 temp.setHtml( html ); 71 72 // When returning the node, remove it from its parent to detach it. 73 return temp.getFirst().remove(); 74 }; 75 76 CKEDITOR.dom.element.setMarker = function( database, element, name, value ) 77 { 78 var id = element.getCustomData( 'list_marker_id' ) || 79 ( element.setCustomData( 'list_marker_id', CKEDITOR.tools.getNextNumber() ).getCustomData( 'list_marker_id' ) ), 80 markerNames = element.getCustomData( 'list_marker_names' ) || 81 ( element.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) ); 82 database[id] = element; 83 markerNames[name] = 1; 84 85 return element.setCustomData( name, value ); 86 }; 87 88 CKEDITOR.dom.element.clearAllMarkers = function( database ) 89 { 90 for ( var i in database ) 91 CKEDITOR.dom.element.clearMarkers( database, database[i], true ); 92 }; 93 94 CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase ) 95 { 96 var names = element.getCustomData( 'list_marker_names' ), 97 id = element.getCustomData( 'list_marker_id' ); 98 for ( var i in names ) 99 element.removeCustomData( i ); 100 element.removeCustomData( 'list_marker_names' ); 101 if ( removeFromDatabase ) 102 { 103 element.removeCustomData( 'list_marker_id' ); 104 delete database[id]; 105 } 106 }; 107 108 CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, 109 /** @lends CKEDITOR.dom.element.prototype */ 110 { 111 /** 112 * The node type. This is a constant value set to 113 * {@link CKEDITOR.NODE_ELEMENT}. 114 * @type Number 115 * @example 116 */ 117 type : CKEDITOR.NODE_ELEMENT, 118 119 /** 120 * Adds a CSS class to the element. It appends the class to the 121 * already existing names. 122 * @param {String} className The name of the class to be added. 123 * @example 124 * var element = new CKEDITOR.dom.element( 'div' ); 125 * element.addClass( 'classA' ); // <div class="classA"> 126 * element.addClass( 'classB' ); // <div class="classA classB"> 127 * element.addClass( 'classA' ); // <div class="classA classB"> 128 */ 129 addClass : function( className ) 130 { 131 var c = this.$.className; 132 if ( c ) 133 { 134 var regex = new RegExp( '(?:^|\\s)' + className + '(?:\\s|$)', '' ); 135 if ( !regex.test( c ) ) 136 c += ' ' + className; 137 } 138 this.$.className = c || className; 139 }, 140 141 /** 142 * Removes a CSS class name from the elements classes. Other classes 143 * remain untouched. 144 * @param {String} className The name of the class to remove. 145 * @example 146 * var element = new CKEDITOR.dom.element( 'div' ); 147 * element.addClass( 'classA' ); // <div class="classA"> 148 * element.addClass( 'classB' ); // <div class="classA classB"> 149 * element.removeClass( 'classA' ); // <div class="classB"> 150 * element.removeClass( 'classB' ); // <div> 151 */ 152 removeClass : function( className ) 153 { 154 var c = this.$.className; 155 if ( c ) 156 { 157 var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', '' ); 158 if ( regex.test( c ) ) 159 { 160 c = c.replace( regex, '' ).replace( /^\s+/, '' ); 161 162 if ( c ) 163 this.$.className = c; 164 else 165 this.removeAttribute( 'class' ); 166 } 167 } 168 }, 169 170 hasClass : function( className ) 171 { 172 var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', '' ); 173 return regex.test( this.$.className ); 174 }, 175 176 /** 177 * Append a node as a child of this element. 178 * @param {CKEDITOR.dom.node|String} node The node or element name to be 179 * appended. 180 * @param {Boolean} [toStart] Indicates that the element is to be 181 * appended at the start. 182 * @returns {CKEDITOR.dom.node} The appended node. 183 * @example 184 * var p = new CKEDITOR.dom.element( 'p' ); 185 * 186 * var strong = new CKEDITOR.dom.element( 'strong' ); 187 * <b>p.append( strong );</b> 188 * 189 * var em = <b>p.append( 'em' );</b> 190 * 191 * // result: "<p><strong></strong><em></em></p>" 192 */ 193 append : function( node, toStart ) 194 { 195 if ( typeof node == 'string' ) 196 node = this.getDocument().createElement( node ); 197 198 if ( toStart ) 199 this.$.insertBefore( node.$, this.$.firstChild ); 200 else 201 this.$.appendChild( node.$ ); 202 203 return node; 204 }, 205 206 appendHtml : function( html ) 207 { 208 if ( !this.$.childNodes.length ) 209 this.setHtml( html ); 210 else 211 { 212 var temp = new CKEDITOR.dom.element( 'div', this.getDocument() ); 213 temp.setHtml( html ); 214 temp.moveChildren( this ); 215 } 216 }, 217 218 /** 219 * Append text to this element. 220 * @param {String} text The text to be appended. 221 * @returns {CKEDITOR.dom.node} The appended node. 222 * @example 223 * var p = new CKEDITOR.dom.element( 'p' ); 224 * p.appendText( 'This is' ); 225 * p.appendText( ' some text' ); 226 * 227 * // result: "<p>This is some text</p>" 228 */ 229 appendText : function( text ) 230 { 231 if ( this.$.text != undefined ) 232 this.$.text += text; 233 else 234 this.append( new CKEDITOR.dom.text( text ) ); 235 }, 236 237 appendBogus : function() 238 { 239 var lastChild = this.getLast() ; 240 241 // Ignore empty/spaces text. 242 while ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.rtrim( lastChild.getText() ).length == 0 ) 243 lastChild = lastChild.getPrevious(); 244 245 if ( !lastChild || ( lastChild.is && ( !lastChild.is( 'br' ) || !lastChild.getAttribute( '_cke_bogus' ) ) ) ) 246 { 247 this.append( 248 CKEDITOR.env.opera ? 249 this.getDocument().createText('') : 250 this.getDocument().createElement( 'br', { attributes : { _cke_bogus : 1 } } ) ); 251 } 252 }, 253 254 /** 255 * Breaks one of the ancestor element in the element position, moving 256 * this element between the broken parts. 257 * @param {CKEDITOR.dom.element} parent The anscestor element to get broken. 258 * @example 259 * // Before breaking: 260 * // <b>This <i>is some<span /> sample</i> test text</b> 261 * // If "element" is <span /> and "parent" is <i>: 262 * // <b>This <i>is some</i><span /><i> sample</i> test text</b> 263 * element.breakParent( parent ); 264 * @example 265 * // Before breaking: 266 * // <b>This <i>is some<span /> sample</i> test text</b> 267 * // If "element" is <span /> and "parent" is <b>: 268 * // <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b> 269 * element.breakParent( parent ); 270 */ 271 breakParent : function( parent ) 272 { 273 var range = new CKEDITOR.dom.range( this.getDocument() ); 274 275 // We'll be extracting part of this element, so let's use our 276 // range to get the correct piece. 277 range.setStartAfter( this ); 278 range.setEndAfter( parent ); 279 280 // Extract it. 281 var docFrag = range.extractContents(); 282 283 // Move the element outside the broken element. 284 range.insertNode( this.remove() ); 285 286 // Re-insert the extracted piece after the element. 287 docFrag.insertAfterNode( this ); 288 }, 289 290 contains : 291 CKEDITOR.env.ie || CKEDITOR.env.webkit ? 292 function( node ) 293 { 294 var $ = this.$; 295 296 return node.type != CKEDITOR.NODE_ELEMENT ? 297 $.contains( node.getParent().$ ) : 298 $ != node.$ && $.contains( node.$ ); 299 } 300 : 301 function( node ) 302 { 303 return !!( this.$.compareDocumentPosition( node.$ ) & 16 ); 304 }, 305 306 /** 307 * Moves the selection focus to this element. 308 * @example 309 * var element = CKEDITOR.document.getById( 'myTextarea' ); 310 * <b>element.focus()</b>; 311 */ 312 focus : function() 313 { 314 // IE throws error if the element is not visible. 315 try 316 { 317 this.$.focus(); 318 } 319 catch (e) 320 {} 321 }, 322 323 /** 324 * Gets the inner HTML of this element. 325 * @returns {String} The inner HTML of this element. 326 * @example 327 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' ); 328 * alert( <b>p.getHtml()</b> ); // "<b>Example</b>" 329 */ 330 getHtml : function() 331 { 332 return this.$.innerHTML; 333 }, 334 335 getOuterHtml : function() 336 { 337 if ( this.$.outerHTML ) 338 return this.$.outerHTML; 339 340 var tmpDiv = this.$.ownerDocument.createElement( 'div' ); 341 tmpDiv.appendChild( this.$.cloneNode( true ) ); 342 return tmpDiv.innerHTML; 343 }, 344 345 /** 346 * Sets the inner HTML of this element. 347 * @param {String} html The HTML to be set for this element. 348 * @returns {String} The inserted HTML. 349 * @example 350 * var p = new CKEDITOR.dom.element( 'p' ); 351 * <b>p.setHtml( '<b>Inner</b> HTML' );</b> 352 * 353 * // result: "<p><b>Inner</b> HTML</p>" 354 */ 355 setHtml : function( html ) 356 { 357 return ( this.$.innerHTML = html ); 358 }, 359 360 /** 361 * Sets the element contents as plain text. 362 * @param {String} text The text to be set. 363 * @returns {String} The inserted text. 364 * @example 365 * var element = new CKEDITOR.dom.element( 'div' ); 366 * element.setText( 'A > B & C < D' ); 367 * alert( element.innerHTML ); // "A > B & C < D" 368 */ 369 setText : function( text ) 370 { 371 CKEDITOR.dom.element.prototype.setText = ( this.$.innerText != undefined ) ? 372 function ( text ) 373 { 374 return this.$.innerText = text; 375 } : 376 function ( text ) 377 { 378 return this.$.textContent = text; 379 }; 380 381 return this.setText( text ); 382 }, 383 384 /** 385 * Gets the value of an element attribute. 386 * @function 387 * @param {String} name The attribute name. 388 * @returns {String} The attribute value or null if not defined. 389 * @example 390 * var element = CKEDITOR.dom.element.createFromHtml( '<input type="text" />' ); 391 * alert( <b>element.getAttribute( 'type' )</b> ); // "text" 392 */ 393 getAttribute : (function() 394 { 395 var standard = function( name ) 396 { 397 return this.$.getAttribute( name, 2 ); 398 }; 399 400 if ( CKEDITOR.env.ie ) 401 { 402 return function( name ) 403 { 404 switch ( name ) 405 { 406 case 'class': 407 name = 'className'; 408 break; 409 410 case 'tabindex': 411 var tabIndex = standard.call( this, name ); 412 413 // IE returns tabIndex=0 by default for all 414 // elements. For those elements, 415 // getAtrribute( 'tabindex', 2 ) returns 32768 416 // instead. So, we must make this check to give a 417 // uniform result among all browsers. 418 if ( tabIndex !== 0 && this.$.tabIndex === 0 ) 419 tabIndex = null; 420 421 return tabIndex; 422 break; 423 424 case 'style': 425 // IE does not return inline styles via getAttribute(). See #2947. 426 return this.$.style.cssText; 427 } 428 429 return standard.call( this, name ); 430 }; 431 } 432 else 433 return standard; 434 })(), 435 436 getChildren : function() 437 { 438 return new CKEDITOR.dom.nodeList( this.$.childNodes ); 439 }, 440 441 /** 442 * Gets the current computed value of one of the element CSS style 443 * properties. 444 * @function 445 * @param {String} propertyName The style property name. 446 * @returns {String} The property value. 447 * @example 448 * var element = new CKEDITOR.dom.element( 'span' ); 449 * alert( <b>element.getComputedStyle( 'display' )</b> ); // "inline" 450 */ 451 getComputedStyle : 452 CKEDITOR.env.ie ? 453 function( propertyName ) 454 { 455 return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ]; 456 } 457 : 458 function( propertyName ) 459 { 460 return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName ); 461 }, 462 463 /** 464 * Gets the DTD entries for this element. 465 * @returns {Object} An object containing the list of elements accepted 466 * by this element. 467 */ 468 getDtd : function() 469 { 470 var dtd = CKEDITOR.dtd[ this.getName() ]; 471 472 this.getDtd = function() 473 { 474 return dtd; 475 }; 476 477 return dtd; 478 }, 479 480 getElementsByTag : function( tagName, namespace ) 481 { 482 if ( !CKEDITOR.env.ie && namespace ) 483 tagName = namespace + ':' + tagName; 484 return new CKEDITOR.dom.nodeList( this.$.getElementsByTagName( tagName ) ); 485 }, 486 487 /** 488 * Gets the computed tabindex for this element. 489 * @function 490 * @returns {Number} The tabindex value. 491 * @example 492 * var element = CKEDITOR.document.getById( 'myDiv' ); 493 * alert( <b>element.getTabIndex()</b> ); // e.g. "-1" 494 */ 495 getTabIndex : 496 CKEDITOR.env.ie ? 497 function() 498 { 499 var tabIndex = this.$.tabIndex; 500 501 // IE returns tabIndex=0 by default for all elements. In 502 // those cases we must check that the element really has 503 // the tabindex attribute set to zero, or it is one of 504 // those element that should have zero by default. 505 if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 ) 506 tabIndex = -1; 507 508 return tabIndex; 509 } 510 : CKEDITOR.env.webkit ? 511 function() 512 { 513 var tabIndex = this.$.tabIndex; 514 515 // Safari returns "undefined" for elements that should not 516 // have tabindex (like a div). So, we must try to get it 517 // from the attribute. 518 // https://bugs.webkit.org/show_bug.cgi?id=20596 519 if ( tabIndex == undefined ) 520 { 521 tabIndex = parseInt( this.getAttribute( 'tabindex' ), 10 ); 522 523 // If the element don't have the tabindex attribute, 524 // then we should return -1. 525 if ( isNaN( tabIndex ) ) 526 tabIndex = -1; 527 } 528 529 return tabIndex; 530 } 531 : 532 function() 533 { 534 return this.$.tabIndex; 535 }, 536 537 /** 538 * Gets the text value of this element. 539 * 540 * Only in IE (which uses innerText), <br> will cause linebreaks, 541 * and sucessive whitespaces (including line breaks) will be reduced to 542 * a single space. This behavior is ok for us, for now. It may change 543 * in the future. 544 * @returns {String} The text value. 545 * @example 546 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Same <i>text</i>.</div>' ); 547 * alert( <b>element.getText()</b> ); // "Sample text." 548 */ 549 getText : function() 550 { 551 return this.$.textContent || this.$.innerText; 552 }, 553 554 /** 555 * Gets the window object that contains this element. 556 * @returns {CKEDITOR.dom.window} The window object. 557 * @example 558 */ 559 getWindow : function() 560 { 561 return this.getDocument().getWindow(); 562 }, 563 564 /** 565 * Gets the value of the "id" attribute of this element. 566 * @returns {String} The element id, or null if not available. 567 * @example 568 * var element = CKEDITOR.dom.element.createFromHtml( '<p id="myId"></p>' ); 569 * alert( <b>element.getId()</b> ); // "myId" 570 */ 571 getId : function() 572 { 573 return this.$.id || null; 574 }, 575 576 /** 577 * Gets the value of the "name" attribute of this element. 578 * @returns {String} The element name, or null if not available. 579 * @example 580 * var element = CKEDITOR.dom.element.createFromHtml( '<input name="myName"></input>' ); 581 * alert( <b>element.getNameAtt()</b> ); // "myName" 582 */ 583 getNameAtt : function() 584 { 585 return this.$.name || null; 586 }, 587 588 /** 589 * Gets the element name (tag name). The returned name is guaranteed to 590 * be always full lowercased. 591 * @returns {String} The element name. 592 * @example 593 * var element = new CKEDITOR.dom.element( 'span' ); 594 * alert( <b>element.getName()</b> ); // "span" 595 */ 596 getName : function() 597 { 598 // Cache the lowercased name inside a closure. 599 var nodeName = this.$.nodeName.toLowerCase(); 600 601 if ( CKEDITOR.env.ie ) 602 { 603 var scopeName = this.$.scopeName; 604 if ( scopeName != 'HTML' ) 605 nodeName = scopeName.toLowerCase() + ':' + nodeName; 606 } 607 608 return ( 609 /** @ignore */ 610 this.getName = function() 611 { 612 return nodeName; 613 })(); 614 }, 615 616 /** 617 * Gets the value set to this element. This value is usually available 618 * for form field elements. 619 * @returns {String} The element value. 620 */ 621 getValue : function() 622 { 623 return this.$.value; 624 }, 625 626 /** 627 * Gets the first child node of this element. 628 * @returns {CKEDITOR.dom.node} The first child node or null if not 629 * available. 630 * @example 631 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' ); 632 * var first = <b>element.getFirst()</b>; 633 * alert( first.getName() ); // "b" 634 */ 635 getFirst : function() 636 { 637 var $ = this.$.firstChild; 638 return $ ? new CKEDITOR.dom.node( $ ) : null; 639 }, 640 641 getLast : function() 642 { 643 var $ = this.$.lastChild; 644 return $ ? new CKEDITOR.dom.node( $ ) : null; 645 }, 646 647 /** 648 * Gets the node that follows this element in its parent's child list. 649 * @returns {CKEDITOR.dom.node} The next node or null if not 650 * available. 651 * @example 652 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b> <i>next</i></div>' ); 653 * var first = <b>element.getFirst().getNext()</b>; 654 * alert( first.getName() ); // "i" 655 */ 656 getNext : function() 657 { 658 var $ = this.$.nextSibling; 659 return $ ? new CKEDITOR.dom.node( $ ) : null; 660 }, 661 662 getStyle : function( name ) 663 { 664 return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ]; 665 }, 666 667 /** 668 * Checks if the element name matches one or more names. 669 * @param {String} name[,name[,...]] One or more names to be checked. 670 * @returns {Boolean} true if the element name matches any of the names. 671 * @example 672 * var element = new CKEDITOR.element( 'span' ); 673 * alert( <b>element.is( 'span' )</b> ); "true" 674 * alert( <b>element.is( 'p', 'span' )</b> ); "true" 675 * alert( <b>element.is( 'p' )</b> ); "false" 676 * alert( <b>element.is( 'p', 'div' )</b> ); "false" 677 */ 678 is : function() 679 { 680 var name = this.getName(); 681 for ( var i = 0 ; i < arguments.length ; i++ ) 682 { 683 if ( arguments[ i ] == name ) 684 return true; 685 } 686 return false; 687 }, 688 689 isEditable : function() 690 { 691 // Get the element name. 692 var name = this.getName(); 693 694 // Get the element DTD (defaults to span for unknown elements). 695 var dtd = !CKEDITOR.dtd.$nonEditable[ name ] 696 && ( CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span ); 697 698 // In the DTD # == text node. 699 return ( dtd && dtd['#'] ); 700 }, 701 702 isIdentical : function( otherElement ) 703 { 704 if ( this.getName() != otherElement.getName() ) 705 return false; 706 707 var thisAttribs = this.$.attributes, 708 otherAttribs = otherElement.$.attributes; 709 710 var thisLength = thisAttribs.length, 711 otherLength = otherAttribs.length; 712 713 if ( !CKEDITOR.env.ie && thisLength != otherLength ) 714 return false; 715 716 for ( var i = 0 ; i < thisLength ; i++ ) 717 { 718 var attribute = thisAttribs[ i ]; 719 720 if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) ) 721 return false; 722 } 723 724 // For IE, we have to for both elements, because it's difficult to 725 // know how the atttibutes collection is organized in its DOM. 726 if ( CKEDITOR.env.ie ) 727 { 728 for ( i = 0 ; i < otherLength ; i++ ) 729 { 730 attribute = otherAttribs[ i ]; 731 732 if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != thisAttribs.getAttribute( attribute.nodeName ) ) 733 return false; 734 } 735 } 736 737 return true; 738 }, 739 740 /** 741 * Checks if this element is visible. May not work if the element is 742 * child of an element with visibility set to "hidden", but works well 743 * on the great majority of cases. 744 * @return {Boolean} True if the element is visible. 745 */ 746 isVisible : function() 747 { 748 return this.$.offsetWidth && ( this.$.style.visibility != 'hidden' ); 749 }, 750 751 /** 752 * Indicates that the element has defined attributes. 753 * @returns {Boolean} True if the element has attributes. 754 * @example 755 * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' ); 756 * alert( <b>element.hasAttributes()</b> ); "true" 757 * @example 758 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' ); 759 * alert( <b>element.hasAttributes()</b> ); "false" 760 */ 761 hasAttributes : 762 CKEDITOR.env.ie ? 763 function() 764 { 765 var attributes = this.$.attributes; 766 767 for ( var i = 0 ; i < attributes.length ; i++ ) 768 { 769 var attribute = attributes[i]; 770 771 switch ( attribute.nodeName ) 772 { 773 case 'class' : 774 // IE has a strange bug. If calling removeAttribute('className'), 775 // the attributes collection will still contain the "class" 776 // attribute, which will be marked as "specified", even if the 777 // outerHTML of the element is not displaying the class attribute. 778 // Note : I was not able to reproduce it outside the editor, 779 // but I've faced it while working on the TC of #1391. 780 if ( this.$.className.length > 0 ) 781 return true; 782 783 // Attributes to be ignored. 784 case '_cke_expando' : 785 continue; 786 787 /*jsl:fallthru*/ 788 789 default : 790 if ( attribute.specified ) 791 return true; 792 } 793 } 794 795 return false; 796 } 797 : 798 function() 799 { 800 return this.$.attributes.length > 0; 801 }, 802 803 /** 804 * Hides this element (display:none). 805 * @example 806 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 807 * <b>element.hide()</b>; 808 */ 809 hide : function() 810 { 811 this.setStyle( 'display', 'none' ); 812 }, 813 814 moveChildren : function( target, toStart ) 815 { 816 var $ = this.$; 817 target = target.$; 818 819 if ( $ == target ) 820 return; 821 822 var child; 823 824 if ( toStart ) 825 { 826 while ( ( child = $.lastChild ) ) 827 target.insertBefore( $.removeChild( child ), target.firstChild ); 828 } 829 else 830 { 831 while ( ( child = $.firstChild ) ) 832 target.appendChild( $.removeChild( child ) ); 833 } 834 }, 835 836 copyAttributes : function( target, skip ) 837 { 838 skip || ( skip = {} ); 839 var attributes = this.$.attributes; 840 841 for ( var n = 0 ; n < attributes.length ; n++ ) 842 { 843 var attr = attributes[n]; 844 845 if ( attr.specified ) 846 { 847 var attrName = attr.nodeName; 848 if ( attrName in skip ) 849 continue; 850 851 var attrValue = this.getAttribute( attrName ); 852 if ( !attrValue ) 853 attrValue = attr.nodeValue; 854 855 target.setAttribute( attrName, attrValue ); 856 } 857 } 858 859 if ( this.$.style.cssText !== '' ) 860 target.$.style.cssText = this.$.style.cssText; 861 }, 862 863 /** 864 * Shows this element (display it). 865 * @example 866 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 867 * <b>element.show()</b>; 868 */ 869 show : function() 870 { 871 this.setStyles( 872 { 873 display : '', 874 visibility : '' 875 }); 876 }, 877 878 /** 879 * Sets the value of an element attribute. 880 * @param {String} name The name of the attribute. 881 * @param {String} value The value to be set to the attribute. 882 * @function 883 * @returns {CKEDITOR.dom.element} This element instance. 884 * @example 885 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 886 * <b>element.setAttribute( 'class', 'myClass' )</b>; 887 * <b>element.setAttribute( 'title', 'This is an example' )</b>; 888 */ 889 setAttribute : (function() 890 { 891 var standard = function( name, value ) 892 { 893 this.$.setAttribute( name, value ); 894 return this; 895 }; 896 897 if ( CKEDITOR.env.ie ) 898 { 899 return function( name, value ) 900 { 901 if ( name == 'class' ) 902 this.$.className = value; 903 else if ( name == 'style' ) 904 this.$.style.cssText = value; 905 else if ( name == 'tabindex' ) // Case sensitive. 906 this.$.tabIndex = value; 907 else 908 standard.apply( this, arguments ); 909 return this; 910 }; 911 } 912 else 913 return standard; 914 })(), 915 916 /** 917 * Sets the value of several element attributes. 918 * @param {Object} attributesPairs An object containing the names and 919 * values of the attributes. 920 * @returns {CKEDITOR.dom.element} This element instance. 921 * @example 922 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 923 * <b>element.setAttributes({ 924 * 'class' : 'myClass', 925 * 'title' : 'This is an example' })</b>; 926 */ 927 setAttributes : function( attributesPairs ) 928 { 929 for ( var name in attributesPairs ) 930 this.setAttribute( name, attributesPairs[ name ] ); 931 return this; 932 }, 933 934 /** 935 * Sets the element value. This function is usually used with form 936 * field element. 937 * @param {String} value The element value. 938 * @returns {CKEDITOR.dom.element} This element instance. 939 */ 940 setValue : function( value ) 941 { 942 this.$.value = value; 943 return this; 944 }, 945 946 /** 947 * Removes an attribute from the element. 948 * @param {String} name The attribute name. 949 * @function 950 * @example 951 * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' ); 952 * element.removeAttribute( 'class' ); 953 */ 954 removeAttribute : (function() 955 { 956 var standard = function( name ) 957 { 958 this.$.removeAttribute( name ); 959 }; 960 961 if ( CKEDITOR.env.ie ) 962 { 963 return function( name ) 964 { 965 if ( name == 'class' ) 966 name = 'className'; 967 standard.call( this, name ); 968 }; 969 } 970 else 971 return standard; 972 })(), 973 974 removeAttributes : function ( attributes ) 975 { 976 for ( var i = 0 ; i < attributes.length ; i++ ) 977 this.removeAttribute( attributes[ i ] ); 978 }, 979 980 /** 981 * Removes a style from the element. 982 * @param {String} name The style name. 983 * @function 984 * @example 985 * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' ); 986 * element.removeStyle( 'display' ); 987 */ 988 removeStyle : function( name ) 989 { 990 this.setStyle( name, '' ); 991 992 if ( !this.$.style.cssText ) 993 this.removeAttribute( 'style' ); 994 }, 995 996 /** 997 * Sets the value of an element style. 998 * @param {String} name The name of the style. The CSS naming notation 999 * must be used (e.g. "background-color"). 1000 * @param {String} value The value to be set to the style. 1001 * @returns {CKEDITOR.dom.element} This element instance. 1002 * @example 1003 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 1004 * <b>element.setStyle( 'background-color', '#ff0000' )</b>; 1005 * <b>element.setStyle( 'margin-top', '10px' )</b>; 1006 * <b>element.setStyle( 'float', 'right' )</b>; 1007 */ 1008 setStyle : function( name, value ) 1009 { 1010 this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value; 1011 return this; 1012 }, 1013 1014 /** 1015 * Sets the value of several element styles. 1016 * @param {Object} stylesPairs An object containing the names and 1017 * values of the styles. 1018 * @returns {CKEDITOR.dom.element} This element instance. 1019 * @example 1020 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 1021 * <b>element.setStyles({ 1022 * 'position' : 'absolute', 1023 * 'float' : 'right' })</b>; 1024 */ 1025 setStyles : function( stylesPairs ) 1026 { 1027 for ( var name in stylesPairs ) 1028 this.setStyle( name, stylesPairs[ name ] ); 1029 return this; 1030 }, 1031 1032 /** 1033 * Sets the opacity of an element. 1034 * @param {Number} opacity A number within the range [0.0, 1.0]. 1035 * @example 1036 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 1037 * <b>element.setOpacity( 0.75 )</b>; 1038 */ 1039 setOpacity : function( opacity ) 1040 { 1041 if ( CKEDITOR.env.ie ) 1042 { 1043 opacity = Math.round( opacity * 100 ); 1044 this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' ); 1045 } 1046 else 1047 this.setStyle( 'opacity', opacity ); 1048 }, 1049 1050 /** 1051 * Makes the element and its children unselectable. 1052 * @function 1053 * @example 1054 * var element = CKEDITOR.dom.element.getById( 'myElement' ); 1055 * element.unselectable(); 1056 */ 1057 unselectable : 1058 CKEDITOR.env.gecko ? 1059 function() 1060 { 1061 this.$.style.MozUserSelect = 'none'; 1062 } 1063 : CKEDITOR.env.webkit ? 1064 function() 1065 { 1066 this.$.style.KhtmlUserSelect = 'none'; 1067 } 1068 : 1069 function() 1070 { 1071 if ( CKEDITOR.env.ie || CKEDITOR.env.opera ) 1072 { 1073 var element = this.$, 1074 e, 1075 i = 0; 1076 1077 element.unselectable = 'on'; 1078 1079 while ( ( e = element.all[ i++ ] ) ) 1080 { 1081 switch ( e.tagName.toLowerCase() ) 1082 { 1083 case 'iframe' : 1084 case 'textarea' : 1085 case 'input' : 1086 case 'select' : 1087 /* Ignore the above tags */ 1088 break; 1089 default : 1090 e.unselectable = 'on'; 1091 } 1092 } 1093 } 1094 }, 1095 1096 getPositionedAncestor : function() 1097 { 1098 var current = this; 1099 while ( current.getName() != 'html' ) 1100 { 1101 if ( current.getComputedStyle( 'position' ) != 'static' ) 1102 return current; 1103 1104 current = current.getParent(); 1105 } 1106 return null; 1107 }, 1108 1109 getDocumentPosition : function( refDocument ) 1110 { 1111 var x = 0, y = 0, current = this, previous = null; 1112 while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) ) 1113 { 1114 x += current.$.offsetLeft - current.$.scrollLeft; 1115 y += current.$.offsetTop - current.$.scrollTop; 1116 1117 if ( !CKEDITOR.env.opera ) 1118 { 1119 var scrollElement = previous; 1120 while ( scrollElement && !scrollElement.equals( current ) ) 1121 { 1122 x -= scrollElement.$.scrollLeft; 1123 y -= scrollElement.$.scrollTop; 1124 scrollElement = scrollElement.getParent(); 1125 } 1126 } 1127 1128 previous = current; 1129 current = new CKEDITOR.dom.element( current.$.offsetParent ); 1130 } 1131 1132 if ( refDocument ) 1133 { 1134 var currentWindow = current.getWindow(), 1135 refWindow = refDocument.getWindow(); 1136 1137 if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement ) 1138 { 1139 var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument ); 1140 1141 x += iframePosition.x; 1142 y += iframePosition.y; 1143 } 1144 } 1145 1146 // document.body is a special case when it comes to offsetTop and offsetLeft 1147 // values. 1148 // 1. It matters if document.body itself is a positioned element; 1149 // 2. It matters when we're in IE and the element has no positioned ancestor. 1150 // Otherwise the values should be ignored. 1151 if ( this.getComputedStyle( 'position' ) != 'static' || ( CKEDITOR.env.ie && this.getPositionedAncestor() == null ) ) 1152 { 1153 x += this.getDocument().getBody().$.offsetLeft; 1154 y += this.getDocument().getBody().$.offsetTop; 1155 } 1156 1157 return { x : x, y : y }; 1158 }, 1159 1160 scrollIntoView : function( alignTop ) 1161 { 1162 // Get the element window. 1163 var win = this.getWindow(), 1164 winHeight = win.getViewPaneSize().height; 1165 1166 // Starts from the offset that will be scrolled with the negative value of 1167 // the visible window height. 1168 var offset = winHeight * -1; 1169 1170 // Append the height if we are about the align the bottom. 1171 if ( !alignTop ) 1172 { 1173 offset += this.$.offsetHeight || 0; 1174 1175 // Consider the margin in the scroll, which is ok for our current needs, but 1176 // needs investigation if we will be using this function in other places. 1177 offset += parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0; 1178 } 1179 1180 // Append the offsets for the entire element hierarchy. 1181 var elementPosition = this.getDocumentPosition(); 1182 offset += elementPosition.y; 1183 1184 // Scroll the window to the desired position, if not already visible. 1185 var currentScroll = win.getScrollPosition().y; 1186 if ( offset > 0 && ( offset > currentScroll || offset < currentScroll - winHeight ) ) 1187 win.$.scrollTo( 0, offset ); 1188 }, 1189 1190 setState : function( state ) 1191 { 1192 switch ( state ) 1193 { 1194 case CKEDITOR.TRISTATE_ON : 1195 this.addClass( 'cke_on' ); 1196 this.removeClass( 'cke_off' ); 1197 this.removeClass( 'cke_disabled' ); 1198 break; 1199 case CKEDITOR.TRISTATE_DISABLED : 1200 this.addClass( 'cke_disabled' ); 1201 this.removeClass( 'cke_off' ); 1202 this.removeClass( 'cke_on' ); 1203 break; 1204 default : 1205 this.addClass( 'cke_off' ); 1206 this.removeClass( 'cke_on' ); 1207 this.removeClass( 'cke_disabled' ); 1208 break; 1209 } 1210 } 1211 }); 1212