1/* 2 * jQuery dotdotdot 3.2.1 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.1'; 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 = !this._fits(); 133 this._truncateToNode( this.$inner[ 0 ] ); 134 135 this.$dot[ isTruncated ? 'addClass' : 'removeClass' ]( _c.truncated ); 136 137 138 // Remove inner node 139 this.$inner.replaceWith( this.$inner.contents() ); 140 this.$inner = null; 141 142 143 // Callback 144 this.opts.callback.call( this.$dot[ 0 ], isTruncated ); 145 146 return isTruncated; 147 }, 148 149 restore: function() 150 { 151 this.unwatch(); 152 153 this.$dot 154 .empty() 155 .append( this.originalContent ) 156 .attr( 'style', this.originalStyle ) 157 .removeClass( _c.truncated ); 158 }, 159 160 destroy: function() 161 { 162 this.restore(); 163 this.$dot.data( _PLUGIN_, null ); 164 }, 165 166 watch: function() 167 { 168 var that = this; 169 170 this.unwatch(); 171 172 var oldSizes = {}; 173 174 if ( this.opts.watch == 'window' ) 175 { 176 $wndw.on( 177 _e.resize + that.uniqueId, 178 function( e ) 179 { 180 if ( that.watchTimeout ) 181 { 182 clearTimeout( that.watchTimeout ); 183 } 184 that.watchTimeout = setTimeout( 185 function() { 186 187 oldSizes = that._watchSizes( oldSizes, $wndw, 'width', 'height' ); 188 189 }, 100 190 ); 191 } 192 ); 193 194 } 195 else 196 { 197 this.watchInterval = setInterval( 198 function() 199 { 200 oldSizes = that._watchSizes( oldSizes, that.$dot, 'innerWidth', 'innerHeight' ); 201 202 }, 500 203 ); 204 } 205 }, 206 207 unwatch: function() 208 { 209 $wndw.off( _e.resize + this.uniqueId ); 210 211 if ( this.watchInterval ) 212 { 213 clearInterval( this.watchInterval ); 214 } 215 216 if ( this.watchTimeout ) 217 { 218 clearTimeout( this.watchTimeout ); 219 } 220 }, 221 222 _api: function() 223 { 224 var that = this, 225 api = {}; 226 227 $.each( this.api, 228 function( i ) 229 { 230 var fn = this; 231 api[ fn ] = function() 232 { 233 var re = that[ fn ].apply( that, arguments ); 234 return ( typeof re == 'undefined' ) ? api : re; 235 }; 236 } 237 ); 238 return api; 239 }, 240 241 _truncateToNode: function( _elem ) 242 { 243 244 var that = this; 245 246 var _coms = [], 247 _elms = []; 248 249 // Empty the node 250 // -> replace all contents with comments 251 $(_elem) 252 .contents() 253 .each( 254 function() 255 { 256 var $e = $(this); 257 if ( !$e.hasClass( _c.keep ) ) 258 { 259 var c = document.createComment( '' ); 260 $e.replaceWith( c ); 261 262 _elms.push( this ); 263 _coms.push( c ); 264 } 265 } 266 ); 267 268 if ( !_elms.length ) 269 { 270 return; 271 } 272 273 // Re-fill the node 274 // -> replace comments with contents until it doesn't fit anymore 275 for ( var e = 0; e < _elms.length; e++ ) 276 { 277 278 $(_coms[ e ]).replaceWith( _elms[ e ] ); 279 280 $(_elms[ e ]).append( this.opts.ellipsis ); 281 var fits = this._fits(); 282 $(this.opts.ellipsis, _elms[ e ]).remove(); 283 284 if ( !fits ) 285 { 286 if ( this.opts.truncate == 'node' && e > 1 ) 287 { 288 $(_elms[ e - 2 ]).remove(); 289 return; 290 } 291 break; 292 } 293 } 294 295 // Remove left over comments 296 for ( var c = e; c < _coms.length; c++ ) 297 { 298 $(_coms[ c ]).remove(); 299 } 300 301 // Get last node 302 // -> the node that overflows 303 304 var _last = _elms[ Math.max( 0, Math.min( e, _elms.length - 1 ) ) ]; 305 306 // Border case 307 // -> the last node with only an ellipsis in it... 308 if ( _last.nodeType == 1 ) 309 { 310 311 var $e = $('<' + _last.nodeName + ' />'); 312 $e.append( this.opts.ellipsis ); 313 314 $(_last).replaceWith( $e ); 315 316 // ... fits 317 // -> Restore the full last node 318 if ( this._fits() ) 319 { 320 $e.replaceWith( _last ); 321 } 322 323 // ... doesn't fit 324 // -> remove it and go back one node 325 else 326 { 327 $e.remove(); 328 _last = _elms[ Math.max( 0, e - 1 ) ]; 329 } 330 } 331 332 // Proceed inside last node 333 if ( _last.nodeType == 1 ) 334 { 335 this._truncateToNode( _last ); 336 } 337 else 338 {
339 this._truncateToWord( _last ); 340 } 341 }, 342 343 _truncateToWord: function( _elem ) 344 { 345 346 var e = _elem; 347 348 var that = this; 349 350 var txt = this.__getTextContent( e ), 351 sep = ( txt.indexOf( ' ' ) !== -1 ) ? ' ' : '\u3000', 352 arr = txt.split( sep ), 353 str = ''; 354 355 for ( var a = arr.length; a >= 0; a-- ) 356 { 357 str = arr.slice( 0, a ).join( sep ); 358 359 that.__setTextContent( e, that._addEllipsis( str ) ); 360 361 if ( that._fits() ) 362 { 363 if ( that.opts.truncate == 'letter' ) 364 { 365 that.__setTextContent( e, arr.slice( 0, a + 1 ).join( sep ) ); 366 that._truncateToLetter( e ); 367 } 368 break; 369 } 370 } 371 }, 372 373 _truncateToLetter: function( e ) 374 { 375 var that = this; 376 377 var txt = this.__getTextContent( e ), 378 arr = txt.split( '' ), 379 str = ''; 380 381 for ( var a = arr.length; a >= 0; a-- ) 382 { 383 str = arr.slice( 0, a ).join( '' ); 384 385 if ( !str.length ) 386 { 387 continue; 388 } 389 390 that.__setTextContent( e, that._addEllipsis( str ) ); 391 392 if ( that._fits() ) 393 { 394 break; 395 } 396 } 397 }, 398 399 _fits: function() 400 { 401 return ( this.$inner.innerHeight() <= this.maxHeight + this.opts.tolerance ); 402 }, 403 404 _addEllipsis: function( txt ) 405 { 406 var remove = [' ', '\u3000', ',', ';', '.', '!', '?']; 407 408 while ( $.inArray( txt.slice( -1 ), remove ) > -1 ) 409 { 410 txt = txt.slice( 0, -1 ); 411 } 412 txt += this.__getTextContent( this.opts.ellipsis ); 413 414 return txt; 415 }, 416 417 _getOriginalContent: function() 418 { 419 var that = this; 420 421 // Add "keep" class to nodes to keep 422 this.$dot 423 .find( 'script, style' ) 424 .addClass( _c.keep ); 425 426 if ( this.opts.keep ) 427 { 428 this.$dot 429 .find( this.opts.keep ) 430 .addClass( _c.keep ); 431 } 432 433 // Filter out unneeded whitespace 434 this.$dot 435 .find( '*' ) 436 .not( '.' + _c.keep ) 437 .add( this.$dot ) 438 .contents() 439 .each( 440 function() 441 { 442 443 var e = this, 444 $e = $(this); 445 446 // Text nodes 447 if ( e.nodeType == 3 ) 448 { 449 450 // Remove whitespace where it does not take up space in the DOM 451 if ( $.trim( that.__getTextContent( e ) ) == '' ) 452 { 453 if ( $e.parent().is( 'table, thead, tbody, tfoot, tr, dl, ul, ol, video' ) ) 454 { 455 $e.remove(); 456 return; 457 } 458 if ( $e.prev().is( 'div, p, table, td, td, dt, dd, li' ) ) 459 { 460 $e.remove(); 461 return; 462 } 463 if ( $e.next().is( 'div, p, table, td, td, dt, dd, li' ) ) 464 { 465 $e.remove(); 466 return; 467 } 468 if ( !$e.prev().length ) 469 { 470 $e.remove(); 471 return; 472 } 473 if ( !$e.next().length ) 474 { 475 $e.remove(); 476 return; 477 } 478 } 479 } 480 481 // Comment nodes 482 else if ( e.nodeType == 8 ) 483 { 484 $e.remove(); 485 } 486 487 } 488 ); 489 490 return this.$dot.contents(); 491 }, 492 493 _getMaxHeight: function() 494 { 495 if ( typeof this.opts.height == 'number' ) 496 { 497 return this.opts.height; 498 } 499 500 // Find smallest CSS height 501 var arr = [ 'maxHeight', 'height' ], 502 hgh = 0; 503 504 for ( var a = 0; a < arr.length; a++ ) 505 { 506 hgh = window.getComputedStyle( this.$dot[ 0 ] )[ arr[ a ] ]; 507 if ( hgh.slice( -2 ) == 'px' ) 508 { 509 hgh = parseFloat( hgh ); 510 break; 511 } 512 } 513 514 // Remove padding-top/bottom when needed. 515 var arr = []; 516 switch ( this.$dot.css( 'boxSizing' ) ) 517 { 518 case 'border-box': 519 arr.push( 'borderTopWidth' ); 520 arr.push( 'borderBottomWidth' ); 521 // no break -> padding needs to be added too 522 523 case 'padding-box': 524 arr.push( 'paddingTop' ); 525 arr.push( 'paddingBottom' ); 526 break; 527 } 528 for ( var a = 0; a < arr.length; a++ ) 529 { 530 var p = window.getComputedStyle( this.$dot[ 0 ] )[ arr[ a ] ]; 531 if ( p.slice( -2 ) == 'px' ) 532 { 533 hgh -= parseFloat( p ); 534 } 535 } 536 537 // Sanitize 538 return Math.max( hgh, 0 ); 539 }, 540 541 _watchSizes: function( oldSizes, $elem, width, height ) 542 { 543 if ( this.$dot.is( ':visible' ) ) 544 { 545 var newSizes = { 546 'width' : $elem[ width ](), 547 'height' : $elem[ height ]() 548 }; 549 550 if ( oldSizes.width != newSizes.width || oldSizes.height != newSizes.height ) 551 { 552 this.truncate(); 553 } 554 555 return newSizes; 556 } 557 return oldSizes; 558 }, 559 560 __getTextContent: function( elem ) 561 { 562 var arr = [ 'nodeValue', 'textContent', 'innerText' ]; 563 for ( var a = 0; a < arr.length; a++ ) 564 { 565 if ( typeof elem[ arr[ a ] ] == 'string' ) 566 { 567 return elem[ arr[ a ] ]; 568 } 569 } 570 return ''; 571 }, 572 __setTextContent: function( elem, content ) 573 { 574 var arr = [ 'nodeValue', 'textContent', 'innerText' ]; 575 for ( var a = 0; a < arr.length; a++ ) 576 { 577 elem[ arr[ a ] ] = content; 578 } 579 } 580 }; 581 582 583 584 /* 585 The jQuery plugin 586 */ 587 $.fn[ _PLUGIN_ ] = function( opts ) 588 { 589 initPlugin(); 590 591 opts = $.extend( true, {}, $[ _PLUGIN_ ].defaults, opts ); 592 593 return this.each( 594 function() 595 { 596 $(this).data( _PLUGIN_, new $[ _PLUGIN_ ]( $(this), opts )._api() ); 597 } 598 ); 599 }; 600 601 602 603 /* 604 Global variables 605 */ 606 var _c, _d, _e, $wndw; 607 608 function initPlugin() 609 { 610 $wndw = $(window); 611 612 // Classnames, Datanames, Eventnames 613 _c = {}; 614 _d = {}; 615 _e = {}; 616 617 $.each( [ _c, _d, _e ], 618 function( i, o ) 619 { 620 o.add = function( a ) 621 { 622 a = a.split( ' ' ); 623 for ( var b = 0, l = a.length; b < l; b++ ) 624 { 625 o[ a[ b ] ] = o.ddd( a[ b ] ); 626 } 627 }; 628 } 629 ); 630 631 // Classnames 632 _c.ddd = function( c ) { return 'ddd-' + c; }; 633 _c.add( 'truncated keep' ); 634 635 // Datanames 636 _d.ddd = function( d ) { return 'ddd-' + d; }; 637 638 // Eventnames 639 _e.ddd = function( e ) { return e + '.ddd'; }; 640 _e.add( 'resize' ); 641 642 643 // Only once 644 initPlugin = function() {}; 645 646 } 647 648 649})( 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.