jquery.dotdotdot.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. // JavaScript Document
  2. /*
  3. * jQuery dotdotdot 1.8.1
  4. *
  5. * Copyright (c) Fred Heusschen
  6. * www.frebsite.nl
  7. *
  8. * Plugin website:
  9. * dotdotdot.frebsite.nl
  10. *
  11. * Licensed under the MIT license.
  12. * http://en.wikipedia.org/wiki/MIT_License
  13. */
  14. (function( $, undef )
  15. {
  16. if ( $.fn.dotdotdot )
  17. {
  18. return;
  19. }
  20. $.fn.dotdotdot = function( o )
  21. {
  22. if ( this.length == 0 )
  23. {
  24. $.fn.dotdotdot.debug( 'No element found for "' + this.selector + '".' );
  25. return this;
  26. }
  27. if ( this.length > 1 )
  28. {
  29. return this.each(
  30. function()
  31. {
  32. $(this).dotdotdot( o );
  33. }
  34. );
  35. }
  36. var $dot = this;
  37. var orgContent = $dot.contents();
  38. if ( $dot.data( 'dotdotdot' ) )
  39. {
  40. $dot.trigger( 'destroy.dot' );
  41. }
  42. $dot.data( 'dotdotdot-style', $dot.attr( 'style' ) || '' );
  43. $dot.css( 'word-wrap', 'break-word' );
  44. if ($dot.css( 'white-space' ) === 'nowrap')
  45. {
  46. $dot.css( 'white-space', 'normal' );
  47. }
  48. $dot.bind_events = function()
  49. {
  50. $dot.bind(
  51. 'update.dot',
  52. function( e, c )
  53. {
  54. $dot.removeClass("is-truncated");
  55. e.preventDefault();
  56. e.stopPropagation();
  57. switch( typeof opts.height )
  58. {
  59. case 'number':
  60. opts.maxHeight = opts.height;
  61. break;
  62. case 'function':
  63. opts.maxHeight = opts.height.call( $dot[ 0 ] );
  64. break;
  65. default:
  66. opts.maxHeight = getTrueInnerHeight( $dot );
  67. break;
  68. }
  69. opts.maxHeight += opts.tolerance;
  70. if ( typeof c != 'undefined' )
  71. {
  72. if ( typeof c == 'string' || ('nodeType' in c && c.nodeType === 1) )
  73. {
  74. c = $('<div />').append( c ).contents();
  75. }
  76. if ( c instanceof $ )
  77. {
  78. orgContent = c;
  79. }
  80. }
  81. $inr = $dot.wrapInner( '<div class="dotdotdot" />' ).children();
  82. $inr.contents()
  83. .detach()
  84. .end()
  85. .append( orgContent.clone( true ) )
  86. .find( 'br' )
  87. .replaceWith( ' <br /> ' )
  88. .end()
  89. .css({
  90. 'height' : 'auto',
  91. 'width' : 'auto',
  92. 'border' : 'none',
  93. 'padding' : 0,
  94. 'margin' : 0
  95. });
  96. var after = false,
  97. trunc = false;
  98. if ( conf.afterElement )
  99. {
  100. after = conf.afterElement.clone( true );
  101. after.show();
  102. conf.afterElement.detach();
  103. }
  104. if ( test( $inr, opts ) )
  105. {
  106. if ( opts.wrap == 'children' )
  107. {
  108. trunc = children( $inr, opts, after );
  109. }
  110. else
  111. {
  112. trunc = ellipsis( $inr, $dot, $inr, opts, after );
  113. }
  114. }
  115. $inr.replaceWith( $inr.contents() );
  116. $inr = null;
  117. if ( $.isFunction( opts.callback ) )
  118. {
  119. opts.callback.call( $dot[ 0 ], trunc, orgContent );
  120. }
  121. conf.isTruncated = trunc;
  122. return trunc;
  123. }
  124. ).bind(
  125. 'isTruncated.dot',
  126. function( e, fn )
  127. {
  128. e.preventDefault();
  129. e.stopPropagation();
  130. if ( typeof fn == 'function' )
  131. {
  132. fn.call( $dot[ 0 ], conf.isTruncated );
  133. }
  134. return conf.isTruncated;
  135. }
  136. ).bind(
  137. 'originalContent.dot',
  138. function( e, fn )
  139. {
  140. e.preventDefault();
  141. e.stopPropagation();
  142. if ( typeof fn == 'function' )
  143. {
  144. fn.call( $dot[ 0 ], orgContent );
  145. }
  146. return orgContent;
  147. }
  148. ).bind(
  149. 'destroy.dot',
  150. function( e )
  151. {
  152. e.preventDefault();
  153. e.stopPropagation();
  154. $dot.unwatch()
  155. .unbind_events()
  156. .contents()
  157. .detach()
  158. .end()
  159. .append( orgContent )
  160. .attr( 'style', $dot.data( 'dotdotdot-style' ) || '' )
  161. .removeClass( 'is-truncated' )
  162. .data( 'dotdotdot', false );
  163. }
  164. );
  165. return $dot;
  166. }; // /bind_events
  167. $dot.unbind_events = function()
  168. {
  169. $dot.unbind('.dot');
  170. return $dot;
  171. }; // /unbind_events
  172. $dot.watch = function()
  173. {
  174. $dot.unwatch();
  175. if ( opts.watch == 'window' )
  176. {
  177. var $window = $(window),
  178. _wWidth = $window.width(),
  179. _wHeight = $window.height();
  180. $window.bind(
  181. 'resize.dot' + conf.dotId,
  182. function()
  183. {
  184. if ( _wWidth != $window.width() || _wHeight != $window.height() || !opts.windowResizeFix )
  185. {
  186. _wWidth = $window.width();
  187. _wHeight = $window.height();
  188. if ( watchInt )
  189. {
  190. clearInterval( watchInt );
  191. }
  192. watchInt = setTimeout(
  193. function()
  194. {
  195. $dot.trigger( 'update.dot' );
  196. }, 100
  197. );
  198. }
  199. }
  200. );
  201. }
  202. else
  203. {
  204. watchOrg = getSizes( $dot );
  205. watchInt = setInterval(
  206. function()
  207. {
  208. if ( $dot.is( ':visible' ) )
  209. {
  210. var watchNew = getSizes( $dot );
  211. if ( watchOrg.width != watchNew.width ||
  212. watchOrg.height != watchNew.height )
  213. {
  214. $dot.trigger( 'update.dot' );
  215. watchOrg = watchNew;
  216. }
  217. }
  218. }, 500
  219. );
  220. }
  221. return $dot;
  222. };
  223. $dot.unwatch = function()
  224. {
  225. $(window).unbind( 'resize.dot' + conf.dotId );
  226. if ( watchInt )
  227. {
  228. clearInterval( watchInt );
  229. }
  230. return $dot;
  231. };
  232. var opts = $.extend( true, {}, $.fn.dotdotdot.defaults, o ),
  233. conf = {},
  234. watchOrg = {},
  235. watchInt = null,
  236. $inr = null;
  237. if ( !( opts.lastCharacter.remove instanceof Array ) )
  238. {
  239. opts.lastCharacter.remove = $.fn.dotdotdot.defaultArrays.lastCharacter.remove;
  240. }
  241. if ( !( opts.lastCharacter.noEllipsis instanceof Array ) )
  242. {
  243. opts.lastCharacter.noEllipsis = $.fn.dotdotdot.defaultArrays.lastCharacter.noEllipsis;
  244. }
  245. conf.afterElement = getElement( opts.after, $dot );
  246. conf.isTruncated = false;
  247. conf.dotId = dotId++;
  248. $dot.data( 'dotdotdot', true )
  249. .bind_events()
  250. .trigger( 'update.dot' );
  251. if ( opts.watch )
  252. {
  253. $dot.watch();
  254. }
  255. return $dot;
  256. };
  257. // public
  258. $.fn.dotdotdot.defaults = {
  259. 'ellipsis' : '... ',
  260. 'wrap' : 'word',
  261. 'fallbackToLetter' : true,
  262. 'lastCharacter' : {},
  263. 'tolerance' : 0,
  264. 'callback' : null,
  265. 'after' : null,
  266. 'height' : null,
  267. 'watch' : false,
  268. 'windowResizeFix' : true
  269. };
  270. $.fn.dotdotdot.defaultArrays = {
  271. 'lastCharacter' : {
  272. 'remove' : [ ' ', '\u3000', ',', ';', '.', '!', '?' ],
  273. 'noEllipsis' : []
  274. }
  275. };
  276. $.fn.dotdotdot.debug = function( msg ) {};
  277. // private
  278. var dotId = 1;
  279. function children( $elem, o, after )
  280. {
  281. var $elements = $elem.children(),
  282. isTruncated = false;
  283. $elem.empty();
  284. for ( var a = 0, l = $elements.length; a < l; a++ )
  285. {
  286. var $e = $elements.eq( a );
  287. $elem.append( $e );
  288. if ( after )
  289. {
  290. $elem.append( after );
  291. }
  292. if ( test( $elem, o ) )
  293. {
  294. $e.remove();
  295. isTruncated = true;
  296. break;
  297. }
  298. else
  299. {
  300. if ( after )
  301. {
  302. after.detach();
  303. }
  304. }
  305. }
  306. return isTruncated;
  307. }
  308. function ellipsis( $elem, $d, $i, o, after )
  309. {
  310. var isTruncated = false;
  311. // Don't put the ellipsis directly inside these elements
  312. var notx = 'a, table, thead, tbody, tfoot, tr, col, colgroup, object, embed, param, ol, ul, dl, blockquote, select, optgroup, option, textarea, script, style';
  313. // Don't remove these elements even if they are after the ellipsis
  314. var noty = 'script, .dotdotdot-keep';
  315. $elem
  316. .contents()
  317. .detach()
  318. .each(
  319. function()
  320. {
  321. var e = this,
  322. $e = $(e);
  323. if ( typeof e == 'undefined' )
  324. {
  325. return true;
  326. }
  327. else if ( $e.is( noty ) )
  328. {
  329. $elem.append( $e );
  330. }
  331. else if ( isTruncated )
  332. {
  333. return true;
  334. }
  335. else
  336. {
  337. $elem.append( $e );
  338. if ( after && !$e.is( o.after ) && !$e.find( o.after ).length )
  339. {
  340. $elem[ $elem.is( notx ) ? 'after' : 'append' ]( after );
  341. }
  342. if ( test( $i, o ) )
  343. {
  344. if ( e.nodeType == 3 ) // node is TEXT
  345. {
  346. isTruncated = ellipsisElement( $e, $d, $i, o, after );
  347. }
  348. else
  349. {
  350. isTruncated = ellipsis( $e, $d, $i, o, after );
  351. }
  352. }
  353. if ( !isTruncated )
  354. {
  355. if ( after )
  356. {
  357. after.detach();
  358. }
  359. }
  360. }
  361. }
  362. );
  363. $d.addClass("is-truncated");
  364. return isTruncated;
  365. }
  366. function ellipsisElement( $e, $d, $i, o, after )
  367. {
  368. var e = $e[ 0 ];
  369. if ( !e )
  370. {
  371. return false;
  372. }
  373. var txt = getTextContent( e ),
  374. space = ( txt.indexOf(' ') !== -1 ) ? ' ' : '\u3000',
  375. separator = ( o.wrap == 'letter' ) ? '' : space,
  376. textArr = txt.split( separator ),
  377. position = -1,
  378. midPos = -1,
  379. startPos = 0,
  380. endPos = textArr.length - 1;
  381. // Only one word
  382. if ( o.fallbackToLetter && startPos == 0 && endPos == 0 )
  383. {
  384. separator = '';
  385. textArr = txt.split( separator );
  386. endPos = textArr.length - 1;
  387. }
  388. while ( startPos <= endPos && !( startPos == 0 && endPos == 0 ) )
  389. {
  390. var m = Math.floor( ( startPos + endPos ) / 2 );
  391. if ( m == midPos )
  392. {
  393. break;
  394. }
  395. midPos = m;
  396. setTextContent( e, textArr.slice( 0, midPos + 1 ).join( separator ) + o.ellipsis );
  397. $i.children()
  398. .each(
  399. function()
  400. {
  401. $(this).toggle().toggle();
  402. }
  403. );
  404. if ( !test( $i, o ) )
  405. {
  406. position = midPos;
  407. startPos = midPos;
  408. }
  409. else
  410. {
  411. endPos = midPos;
  412. // Fallback to letter
  413. if (o.fallbackToLetter && startPos == 0 && endPos == 0 )
  414. {
  415. separator = '';
  416. textArr = textArr[ 0 ].split( separator );
  417. position = -1;
  418. midPos = -1;
  419. startPos = 0;
  420. endPos = textArr.length - 1;
  421. }
  422. }
  423. }
  424. if ( position != -1 && !( textArr.length == 1 && textArr[ 0 ].length == 0 ) )
  425. {
  426. txt = addEllipsis( textArr.slice( 0, position + 1 ).join( separator ), o );
  427. setTextContent( e, txt );
  428. }
  429. else
  430. {
  431. var $w = $e.parent();
  432. $e.detach();
  433. var afterLength = ( after && after.closest($w).length ) ? after.length : 0;
  434. if ( $w.contents().length > afterLength )
  435. {
  436. e = findLastTextNode( $w.contents().eq( -1 - afterLength ), $d );
  437. }
  438. else
  439. {
  440. e = findLastTextNode( $w, $d, true );
  441. if ( !afterLength )
  442. {
  443. $w.detach();
  444. }
  445. }
  446. if ( e )
  447. {
  448. txt = addEllipsis( getTextContent( e ), o );
  449. setTextContent( e, txt );
  450. if ( afterLength && after )
  451. {
  452. $(e).parent().append( after );
  453. }
  454. }
  455. }
  456. return true;
  457. }
  458. function test( $i, o )
  459. {
  460. return $i.innerHeight() > o.maxHeight;
  461. }
  462. function addEllipsis( txt, o )
  463. {
  464. while( $.inArray( txt.slice( -1 ), o.lastCharacter.remove ) > -1 )
  465. {
  466. txt = txt.slice( 0, -1 );
  467. }
  468. if ( $.inArray( txt.slice( -1 ), o.lastCharacter.noEllipsis ) < 0 )
  469. {
  470. txt += o.ellipsis;
  471. }
  472. return txt;
  473. }
  474. function getSizes( $d )
  475. {
  476. return {
  477. 'width' : $d.innerWidth(),
  478. 'height': $d.innerHeight()
  479. };
  480. }
  481. function setTextContent( e, content )
  482. {
  483. if ( e.innerText )
  484. {
  485. e.innerText = content;
  486. }
  487. else if ( e.nodeValue )
  488. {
  489. e.nodeValue = content;
  490. }
  491. else if (e.textContent)
  492. {
  493. e.textContent = content;
  494. }
  495. }
  496. function getTextContent( e )
  497. {
  498. if ( e.innerText )
  499. {
  500. return e.innerText;
  501. }
  502. else if ( e.nodeValue )
  503. {
  504. return e.nodeValue;
  505. }
  506. else if ( e.textContent )
  507. {
  508. return e.textContent;
  509. }
  510. else
  511. {
  512. return "";
  513. }
  514. }
  515. function getPrevNode( n )
  516. {
  517. do
  518. {
  519. n = n.previousSibling;
  520. }
  521. while ( n && n.nodeType !== 1 && n.nodeType !== 3 );
  522. return n;
  523. }
  524. function findLastTextNode( $el, $top, excludeCurrent )
  525. {
  526. var e = $el && $el[ 0 ], p;
  527. if ( e )
  528. {
  529. if ( !excludeCurrent )
  530. {
  531. if ( e.nodeType === 3 )
  532. {
  533. return e;
  534. }
  535. if ( $.trim( $el.text() ) )
  536. {
  537. return findLastTextNode( $el.contents().last(), $top );
  538. }
  539. }
  540. p = getPrevNode( e );
  541. while ( !p )
  542. {
  543. $el = $el.parent();
  544. if ( $el.is( $top ) || !$el.length )
  545. {
  546. return false;
  547. }
  548. p = getPrevNode( $el[0] );
  549. }
  550. if ( p )
  551. {
  552. return findLastTextNode( $(p), $top );
  553. }
  554. }
  555. return false;
  556. }
  557. function getElement( e, $i )
  558. {
  559. if ( !e )
  560. {
  561. return false;
  562. }
  563. if ( typeof e === 'string' )
  564. {
  565. e = $(e, $i);
  566. return ( e.length )
  567. ? e
  568. : false;
  569. }
  570. return !e.jquery
  571. ? false
  572. : e;
  573. }
  574. function getTrueInnerHeight( $el )
  575. {
  576. var h = $el.innerHeight(),
  577. a = [ 'paddingTop', 'paddingBottom' ];
  578. for ( var z = 0, l = a.length; z < l; z++ )
  579. {
  580. var m = parseInt( $el.css( a[ z ] ), 10 );
  581. if ( isNaN( m ) )
  582. {
  583. m = 0;
  584. }
  585. h -= m;
  586. }
  587. return h;
  588. }
  589. // override jQuery.html
  590. var _orgHtml = $.fn.html;
  591. $.fn.html = function( str )
  592. {
  593. if ( str != undef && !$.isFunction( str ) && this.data( 'dotdotdot' ) )
  594. {
  595. return this.trigger( 'update', [ str ] );
  596. }
  597. return _orgHtml.apply( this, arguments );
  598. };
  599. // override jQuery.text
  600. var _orgText = $.fn.text;
  601. $.fn.text = function( str )
  602. {
  603. if ( str != undef && !$.isFunction( str ) && this.data( 'dotdotdot' ) )
  604. {
  605. str = $( '<div />' ).text( str ).html();
  606. return this.trigger( 'update', [ str ] );
  607. }
  608. return _orgText.apply( this, arguments );
  609. };
  610. })( jQuery );
  611. /*
  612. ## Automatic parsing for CSS classes
  613. Contributed by [Ramil Valitov](https://github.com/rvalitov)
  614. ### The idea
  615. You can add one or several CSS classes to HTML elements to automatically invoke "jQuery.dotdotdot functionality" and some extra features. It allows to use jQuery.dotdotdot only by adding appropriate CSS classes without JS programming.
  616. ### Available classes and their description
  617. * dot-ellipsis - automatically invoke jQuery.dotdotdot to this element. This class must be included if you plan to use other classes below.
  618. * dot-resize-update - automatically update if window resize event occurs. It's equivalent to option `watch:'window'`.
  619. * dot-timer-update - automatically update if window resize event occurs. It's equivalent to option `watch:true`.
  620. * dot-load-update - automatically update after the window has beem completely rendered. Can be useful if your content is generated dynamically using using JS and, hence, jQuery.dotdotdot can't correctly detect the height of the element before it's rendered completely.
  621. * dot-height-XXX - available height of content area in pixels, where XXX is a number, e.g. can be `dot-height-35` if you want to set maximum height for 35 pixels. It's equivalent to option `height:'XXX'`.
  622. ### Usage examples
  623. *Adding jQuery.dotdotdot to element*
  624. <div class="dot-ellipsis">
  625. <p>Lorem Ipsum is simply dummy text.</p>
  626. </div>
  627. *Adding jQuery.dotdotdot to element with update on window resize*
  628. <div class="dot-ellipsis dot-resize-update">
  629. <p>Lorem Ipsum is simply dummy text.</p>
  630. </div>
  631. *Adding jQuery.dotdotdot to element with predefined height of 50px*
  632. <div class="dot-ellipsis dot-height-50">
  633. <p>Lorem Ipsum is simply dummy text.</p>
  634. </div>
  635. */
  636. jQuery(document).ready(function($) {
  637. //We only invoke jQuery.dotdotdot on elements that have dot-ellipsis class
  638. $(".dot-ellipsis").each(function(){
  639. //Checking if update on window resize required
  640. var watch_window=$(this).hasClass("dot-resize-update");
  641. //Checking if update on timer required
  642. var watch_timer=$(this).hasClass("dot-timer-update");
  643. //Checking if height set
  644. var height=0;
  645. var classList = $(this).attr('class').split(/\s+/);
  646. $.each(classList, function(index, item) {
  647. var matchResult = item.match(/^dot-height-(\d+)$/);
  648. if(matchResult !== null)
  649. height = Number(matchResult[1]);
  650. });
  651. //Invoking jQuery.dotdotdot
  652. var x = new Object();
  653. if (watch_timer)
  654. x.watch=true;
  655. if (watch_window)
  656. x.watch='window';
  657. if (height>0)
  658. x.height=height;
  659. $(this).dotdotdot(x);
  660. });
  661. });
  662. //Updating elements (if any) on window.load event
  663. jQuery(window).load(function(){
  664. jQuery(".dot-ellipsis.dot-load-update").trigger("update.dot");
  665. });