1/* 2 * jQuery dotdotdot 3.2.2 3 * @requires jQuery 1.7.0 or later 4 * 5 * dotdotdot.frebsite.nl 6 * 7 * Copyright (c) Fred Heusschen 8 * www.frebsite.nl 9 * 10 * License: CC-BY-NC-4.0 11 * http://creativecommons.org/licenses/by-nc/4.0/ 12 */ 13 14(function( $ ) { 15 'use strict'; 16 17 var _PLUGIN_ = 'dotdotdot'; 18 var _VERSION_ = '3.2.2'; 19 20 if ( $[ _PLUGIN_ ] && $[ _PLUGIN_ ].version > _VERSION_ ) 21 { 22 return; 23 } 24 25 26 27 /* 28 The class 29 */ 30 $[ _PLUGIN_ ] = function( $container, opts ) 31 { 32 this.$dot = $container; 33 this.api = [ 'getInstance', 'truncate', 'restore', 'destroy', 'watch', 'unwatch' ]; 34 this.opts = opts; 35 36 var oldAPI = this.$dot.data( _PLUGIN_ ); 37 if ( oldAPI ) 38 { 39 oldAPI.destroy(); 40 } 41 42 this.init(); 43 this.truncate(); 44 45 if ( this.opts.watch ) 46 { 47 this.watch(); 48 } 49 50 return this; 51 }; 52 53 $[ _PLUGIN_ ].version = _VERSION_; 54 $[ _PLUGIN_ ].uniqueId = 0; 55 56 $[ _PLUGIN_ ].defaults = { 57 ellipsis : '\u2026 ', 58 callback : function( isTruncated ) {}, 59 truncate : 'word', 60 tolerance : 0, 61 keep : null, 62 watch : 'window', 63 height : null 64 }; 65 66 67 $[ _PLUGIN_ ].prototype = { 68 69 init: function() 70 { 71 this.watchTimeout = null; 72 this.watchInterval = null; 73 this.uniqueId = $[ _PLUGIN_ ].uniqueId++; 74 this.originalStyle = this.$dot.attr( 'style' ) || ''; 75 this.originalContent = this._getOriginalContent(); 76 77 if ( this.$dot.css( 'word-wrap' ) !== 'break-word' ) 78 { 79 this.$dot.css( 'word-wrap', 'break-word' ); 80 } 81 if ( this.$dot.css( 'white-space' ) === 'nowrap' ) 82 { 83 this.$dot.css( 'white-space', 'normal' ); 84 } 85 86 if ( this.opts.height === null ) 87 { 88 this.opts.height = this._getMaxHeight(); 89 } 90 91 if ( typeof this.opts.ellipsis == 'string' ) 92 { 93 this.opts.ellipsis = document.createTextNode( this.opts.ellipsis ); 94 } 95 }, 96 97 getInstance: function() 98 { 99 return this; 100 }, 101 102 truncate: function() 103 { 104 var that = this; 105 106 107 // Add inner node for measuring the height 108 this.$inner = this.$dot 109 .wrapInner( '<div />' ) 110 .children() 111 .css({ 112 'display' : 'block', 113 'height' : 'auto', 114 'width' : 'auto', 115 'border' : 'none', 116 'padding' : 0, 117 'margin' : 0 118 }); 119 120 121 // Set original content 122 this.$inner 123 .empty() 124 .append( this.originalContent.clone( true ) ); 125 126 127 // Get max height 128 this.maxHeight = this._getMaxHeight(); 129 130 131 // Truncate the text 132 var isTruncated = false; 133 if ( !this._fits() ) 134 { 135 isTruncated = true; 136 this._truncateToNode( this.$inner[ 0 ] ); 137 } 138 139 this.$dot[ isTruncated ? 'addClass' : 'removeClass' ]( _c.truncated ); 140 141 142 // Remove inner node 143 this.$inner.replaceWith( this.$inner.contents() ); 144 this.$inner = null; 145 146 147 // Callback 148 this.opts.callback.call( this.$dot[ 0 ], isTruncated ); 149 150 return isTruncated; 151 }, 152 153 restore: function() 154 { 155 this.unwatch(); 156 157 this.$dot 158 .empty() 159 .append( this.originalContent ) 160 .attr( 'style', this.originalStyle ) 161 .removeClass( _c.truncated ); 162 }, 163 164 destroy: function() 165 { 166 this.restore(); 167 this.$dot.data( _PLUGIN_, null ); 168 }, 169 170 watch: function() 171 { 172 var that = this; 173 174 this.unwatch(); 175 176 var oldSizes = {}; 177 178 if ( this.opts.watch == 'window' ) 179 { 180 $wndw.on( 181 _e.resize + that.uniqueId, 182 function( e ) 183 { 184 if ( that.watchTimeout ) 185 { 186 clearTimeout( that.watchTimeout ); 187 } 188 that.watchTimeout = setTimeout( 189 function() { 190 191 oldSizes = that._watchSizes( oldSizes, $wndw, 'width', 'height' ); 192 193 }, 100 194 ); 195 } 196 ); 197 198 } 199 else 200 { 201 this.watchInterval = setInterval( 202 function() 203 { 204 oldSizes = that._watchSizes( oldSizes, that.$dot, 'innerWidth', 'innerHeight' ); 205 206 }, 500 207 ); 208 } 209 }, 210 211 unwatch: function() 212 { 213 $wndw.off( _e.resize + this.uniqueId ); 214 215 if ( this.watchInterval ) 216 { 217 clearInterval( this.watchInterval ); 218 } 219 220 if ( this.watchTimeout ) 221 { 222 clearTimeout( this.watchTimeout ); 223 } 224 }, 225 226 _api: function() 227 { 228 var that = this, 229 api = {}; 230 231 $.each( this.api, 232 function( i ) 233 { 234 var fn = this; 235 api[ fn ] = function() 236 { 237 var re = that[ fn ].apply( that, arguments ); 238 return ( typeof re == 'undefined' ) ? api : re; 239 }; 240 } 241 ); 242 return api; 243 }, 244 245 _truncateToNode: function( _elem ) 246 { 247 248 var that = this; 249 250 var _coms = [], 251 _elms = []; 252 253 // Empty the node 254 // -> replace all contents with comments 255 $(_elem) 256 .contents() 257 .each( 258 function() 259 { 260 var $e = $(this); 261 if ( !$e.hasClass( _c.keep ) ) 262 { 263 var c = document.createComment( '' ); 264 $e.replaceWith( c ); 265 266 _elms.push( this ); 267 _coms.push( c ); 268 } 269 } 270 ); 271 272 if ( !_elms.length ) 273 { 274 return; 275 } 276 277 // Re-fill the node 278 // -> replace comments with contents until it doesn't fit anymore 279 for ( var e = 0; e < _elms.length; e++ ) 280 { 281 282 $(_coms[ e ]).replaceWith( _elms[ e ] ); 283 284 $(_elms[ e ]).append( this.opts.ellipsis ); 285 var fits = this._fits(); 286 $(this.opts.ellipsis, _elms[ e ]).remove(); 287 288 if ( !fits ) 289 { 290 if ( this.opts.truncate == 'node' && e > 1 ) 291 { 292 $(_elms[ e - 2 ]).remove(); 293 return; 294 } 295 break; 296 } 297 } 298 299 // Remove left over comments 300 for ( var c = e; c < _coms.length; c++ ) 301 { 302 $(_coms[ c ]).remove(); 303 } 304 305 // Get last node 306 // -> the node that overflows 307 308 var _last = _elms[ Math.max( 0, Math.min( e, _elms.length - 1 ) ) ]; 309 310 // Border case 311 // -> the last node with only an ellipsis in it... 312 if ( _last.nodeType == 1 ) 313 { 314 315 var $e = $('<' + _last.nodeName + ' />'); 316 $e.append( this.opts.ellipsis ); 317 318 $(_last).replaceWith( $e ); 319 320 // ... fits 321 // -> Restore the full last node 322 if ( this._fits() ) 323 { 324 $e.replaceWith( _last ); 325 } 326 327 // ... doesn't fit 328 // -> remove it and go back one node 329 else 330 { 331 $e.remove(); 332 _last = _elms[ Math.max( 0, e - 1 ) ]; 333 } 334 } 335 336 // Proceed inside last node 337 if ( _last.nodeType == 1 ) 338 { 339 this._truncateToNode( _last ); 340 } 341 else 342 { 343 this._truncateToWord( _last ); 344 } 345 }, 346 347 _truncateToWord: function( _elem ) 348 { 349 350 var e = _elem; 351 352 var that = this; 353 354 var txt = this.__getTextContent( e ), 355 sep = ( txt.indexOf( ' ' ) !== -1 ) ? ' ' : '\u3000', 356 arr = txt.split( sep ), 357 str = ''; 358 359 for ( var a = arr.length; a >= 0; a-- ) 360 { 361 str = arr.slice( 0, a ).join( sep ); 362 363 that.__setTextContent( e, that._addEllipsis( str ) ); 364 365 if ( that._fits() ) 366 { 367 if ( that.opts.truncate == 'letter' ) 368 { 369 that.__setTextContent( e, arr.slice( 0, a + 1 ).join( sep ) ); 370 that._truncateToLetter( e ); 371 } 372 break; 373 } 374 } 375 }, 376 377 _truncateToLetter: function( e ) 378 { 379 var that = this; 380 381 var txt = this.__getTextContent( e ), 382 arr = txt.split( '' ), 383 str = ''; 384 385 for ( var a = arr.length; a >= 0; a-- ) 386 { 387 str = arr.slice( 0, a ).join( '' ); 388 389 if ( !str.length ) 390 { 391 continue; 392 } 393 394 that.__setTextContent( e, that._addEllipsis( str ) ); 395 396 if ( that._fits() ) 397 { 398 break; 399 } 400 } 401 }, 402 403 _fits: function() 404 {
405 return ( this.$inner.innerHeight() <= this.maxHeight + this.opts.tolerance ); 406 }, 407 408 _addEllipsis: function( txt ) 409 { 410 var remove = [' ', '\u3000', ',', ';', '.', '!', '?']; 411 412 while ( $.inArray( txt.slice( -1 ), remove ) > -1 ) 413 { 414 txt = txt.slice( 0, -1 ); 415 } 416 txt += this.__getTextContent( this.opts.ellipsis ); 417 418 return txt; 419 }, 420 421 _getOriginalContent: function() 422 { 423 var that = this; 424 425 // Add "keep" class to nodes to keep 426 this.$dot 427 .find( 'script, style' ) 428 .addClass( _c.keep ); 429 430 if ( this.opts.keep ) 431 { 432 this.$dot 433 .find( this.opts.keep ) 434 .addClass( _c.keep ); 435 } 436 437 // Filter out unneeded whitespace 438 this.$dot 439 .find( '*' ) 440 .not( '.' + _c.keep ) 441 .add( this.$dot ) 442 .contents() 443 .each( 444 function() 445 { 446 447 var e = this, 448 $e = $(this); 449 450 // Text nodes 451 if ( e.nodeType == 3 ) 452 { 453 454 // Remove whitespace where it does not take up space in the DOM 455 if ( $.trim( that.__getTextContent( e ) ) == '' ) 456 { 457 if ( $e.parent().is( 'table, thead, tbody, tfoot, tr, dl, ul, ol, video' ) ) 458 { 459 $e.remove(); 460 return; 461 } 462 if ( $e.prev().is( 'div, p, table, td, td, dt, dd, li' ) ) 463 { 464 $e.remove(); 465 return; 466 } 467 if ( $e.next().is( 'div, p, table, td, td, dt, dd, li' ) ) 468 { 469 $e.remove(); 470 return; 471 } 472 if ( !$e.prev().length ) 473 { 474 $e.remove(); 475 return; 476 } 477 if ( !$e.next().length ) 478 { 479 $e.remove(); 480 return; 481 } 482 } 483 } 484 485 // Comment nodes 486 else if ( e.nodeType == 8 ) 487 { 488 $e.remove(); 489 } 490 491 } 492 ); 493 494 return this.$dot.contents(); 495 }, 496 497 _getMaxHeight: function() 498 { 499 if ( typeof this.opts.height == 'number' ) 500 { 501 return this.opts.height; 502 } 503 504 // Find smallest CSS height 505 var arr = [ 'maxHeight', 'height' ], 506 hgh = 0; 507 508 for ( var a = 0; a < arr.length; a++ ) 509 { 510 hgh = window.getComputedStyle( this.$dot[ 0 ] )[ arr[ a ] ]; 511 if ( hgh.slice( -2 ) == 'px' ) 512 { 513 hgh = parseFloat( hgh ); 514 break; 515 } 516 } 517 518 // Remove padding-top/bottom when needed. 519 var arr = []; 520 switch ( this.$dot.css( 'boxSizing' ) ) 521 { 522 case 'border-box': 523 arr.push( 'borderTopWidth' ); 524 arr.push( 'borderBottomWidth' ); 525 // no break -> padding needs to be added too 526 527 case 'padding-box': 528 arr.push( 'paddingTop' ); 529 arr.push( 'paddingBottom' ); 530 break; 531 } 532 for ( var a = 0; a < arr.length; a++ ) 533 { 534 var p = window.getComputedStyle( this.$dot[ 0 ] )[ arr[ a ] ]; 535 if ( p.slice( -2 ) == 'px' ) 536 { 537 hgh -= parseFloat( p ); 538 } 539 } 540 541 // Sanitize 542 return Math.max( hgh, 0 ); 543 }, 544 545 _watchSizes: function( oldSizes, $elem, width, height ) 546 { 547 if ( this.$dot.is( ':visible' ) ) 548 { 549 var newSizes = { 550 'width' : $elem[ width ](), 551 'height' : $elem[ height ]() 552 }; 553 554 if ( oldSizes.width != newSizes.width || oldSizes.height != newSizes.height ) 555 { 556 this.truncate(); 557 } 558 559 return newSizes; 560 } 561 return oldSizes; 562 }, 563 564 __getTextContent: function( elem ) 565 { 566 var arr = [ 'nodeValue', 'textContent', 'innerText' ]; 567 for ( var a = 0; a < arr.length; a++ ) 568 { 569 if ( typeof elem[ arr[ a ] ] == 'string' ) 570 { 571 return elem[ arr[ a ] ]; 572 } 573 } 574 return ''; 575 }, 576 __setTextContent: function( elem, content ) 577 { 578 var arr = [ 'nodeValue', 'textContent', 'innerText' ]; 579 for ( var a = 0; a < arr.length; a++ ) 580 { 581 elem[ arr[ a ] ] = content; 582 } 583 } 584 }; 585 586 587 588 /* 589 The jQuery plugin 590 */ 591 $.fn[ _PLUGIN_ ] = function( opts ) 592 { 593 initPlugin(); 594 595 opts = $.extend( true, {}, $[ _PLUGIN_ ].defaults, opts ); 596 597 return this.each( 598 function() 599 { 600 $(this).data( _PLUGIN_, new $[ _PLUGIN_ ]( $(this), opts )._api() ); 601 } 602 ); 603 }; 604 605 606 607 /* 608 Global variables 609 */ 610 var _c, _d, _e, $wndw; 611 612 function initPlugin() 613 { 614 $wndw = $(window); 615 616 // Classnames, Datanames, Eventnames 617 _c = {}; 618 _d = {}; 619 _e = {}; 620 621 $.each( [ _c, _d, _e ], 622 function( i, o ) 623 { 624 o.add = function( a ) 625 { 626 a = a.split( ' ' ); 627 for ( var b = 0, l = a.length; b < l; b++ ) 628 { 629 o[ a[ b ] ] = o.ddd( a[ b ] ); 630 } 631 }; 632 } 633 ); 634 635 // Classnames 636 _c.ddd = function( c ) { return 'ddd-' + c; }; 637 _c.add( 'truncated keep' ); 638 639 // Datanames 640 _d.ddd = function( d ) { return 'ddd-' + d; }; 641 642 // Eventnames 643 _e.ddd = function( e ) { return e + '.ddd'; }; 644 _e.add( 'resize' ); 645 646 647 // Only once 648 initPlugin = function() {}; 649 650 } 651 652 653})( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.