Merge "Added release notes for 'ContentHandler::runLegacyHooks' removal"
[mediawiki.git] / resources / src / jquery / jquery.autoEllipsis.js
blob8716b694059a0be96adfd3afe22c7094c2175e40
1 /**
2  * @class jQuery.plugin.autoEllipsis
3  */
4 ( function ( $ ) {
6         var
7                 // Cache ellipsed substrings for every string-width-position combination
8                 cache = {},
10                 // Use a separate cache when match highlighting is enabled
11                 matchTextCache = {};
13         // Due to <https://github.com/jscs-dev/jscs-jsdoc/issues/136>
14         // jscs:disable jsDoc
15         /**
16          * Automatically truncate the plain text contents of an element and add an ellipsis
17          *
18          * @param {Object} options
19          * @param {'left'|'center'|'right'} [options.position='center'] Where to remove text.
20          * @param {boolean} [options.tooltip=false] Whether to show a tooltip with the remainder
21          * of the text.
22          * @param {boolean} [options.restoreText=false] Whether to save the text for restoring
23          * later.
24          * @param {boolean} [options.hasSpan=false] Whether the element is already a container,
25          * or if the library should create a new container for it.
26          * @param {string|null} [options.matchText=null] Text to highlight, e.g. search terms.
27          * @return {jQuery}
28          * @chainable
29          */
30         $.fn.autoEllipsis = function ( options ) {
31                 options = $.extend( {
32                         position: 'center',
33                         tooltip: false,
34                         restoreText: false,
35                         hasSpan: false,
36                         matchText: null
37                 }, options );
39                 return this.each( function () {
40                         var $trimmableText,
41                                 text, trimmableText, w, pw,
42                                 l, r, i, side, m,
43                                 // container element - used for measuring against
44                                 $container = $( this );
46                         if ( options.restoreText ) {
47                                 if ( !$container.data( 'autoEllipsis.originalText' ) ) {
48                                         $container.data( 'autoEllipsis.originalText', $container.text() );
49                                 } else {
50                                         $container.text( $container.data( 'autoEllipsis.originalText' ) );
51                                 }
52                         }
54                         // trimmable text element - only the text within this element will be trimmed
55                         if ( options.hasSpan ) {
56                                 $trimmableText = $container.children( options.selector );
57                         } else {
58                                 $trimmableText = $( '<span>' )
59                                         .css( 'whiteSpace', 'nowrap' )
60                                         .text( $container.text() );
61                                 $container
62                                         .empty()
63                                         .append( $trimmableText );
64                         }
66                         text = $container.text();
67                         trimmableText = $trimmableText.text();
68                         w = $container.width();
69                         pw = 0;
71                         // Try cache
72                         if ( options.matchText ) {
73                                 if ( !( text in matchTextCache ) ) {
74                                         matchTextCache[ text ] = {};
75                                 }
76                                 if ( !( options.matchText in matchTextCache[ text ] ) ) {
77                                         matchTextCache[ text ][ options.matchText ] = {};
78                                 }
79                                 if ( !( w in matchTextCache[ text ][ options.matchText ] ) ) {
80                                         matchTextCache[ text ][ options.matchText ][ w ] = {};
81                                 }
82                                 if ( options.position in matchTextCache[ text ][ options.matchText ][ w ] ) {
83                                         $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] );
84                                         if ( options.tooltip ) {
85                                                 $container.attr( 'title', text );
86                                         }
87                                         return;
88                                 }
89                         } else {
90                                 if ( !( text in cache ) ) {
91                                         cache[ text ] = {};
92                                 }
93                                 if ( !( w in cache[ text ] ) ) {
94                                         cache[ text ][ w ] = {};
95                                 }
96                                 if ( options.position in cache[ text ][ w ] ) {
97                                         $container.html( cache[ text ][ w ][ options.position ] );
98                                         if ( options.tooltip ) {
99                                                 $container.attr( 'title', text );
100                                         }
101                                         return;
102                                 }
103                         }
105                         if ( $trimmableText.width() + pw > w ) {
106                                 switch ( options.position ) {
107                                         case 'right':
108                                                 // Use binary search-like technique for efficiency
109                                                 l = 0;
110                                                 r = trimmableText.length;
111                                                 do {
112                                                         m = Math.ceil( ( l + r ) / 2 );
113                                                         $trimmableText.text( trimmableText.slice( 0, m ) + '...' );
114                                                         if ( $trimmableText.width() + pw > w ) {
115                                                                 // Text is too long
116                                                                 r = m - 1;
117                                                         } else {
118                                                                 l = m;
119                                                         }
120                                                 } while ( l < r );
121                                                 $trimmableText.text( trimmableText.slice( 0, l ) + '...' );
122                                                 break;
123                                         case 'center':
124                                                 // TODO: Use binary search like for 'right'
125                                                 i = [ Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 ) ];
126                                                 // Begin with making the end shorter
127                                                 side = 1;
128                                                 while ( $trimmableText.outerWidth() + pw > w && i[ 0 ] > 0 ) {
129                                                         $trimmableText.text( trimmableText.slice( 0, i[ 0 ] ) + '...' + trimmableText.slice( i[ 1 ] ) );
130                                                         // Alternate between trimming the end and begining
131                                                         if ( side === 0 ) {
132                                                                 // Make the begining shorter
133                                                                 i[ 0 ]--;
134                                                                 side = 1;
135                                                         } else {
136                                                                 // Make the end shorter
137                                                                 i[ 1 ]++;
138                                                                 side = 0;
139                                                         }
140                                                 }
141                                                 break;
142                                         case 'left':
143                                                 // TODO: Use binary search like for 'right'
144                                                 r = 0;
145                                                 while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) {
146                                                         $trimmableText.text( '...' + trimmableText.slice( r ) );
147                                                         r++;
148                                                 }
149                                                 break;
150                                 }
151                         }
152                         if ( options.tooltip ) {
153                                 $container.attr( 'title', text );
154                         }
155                         if ( options.matchText ) {
156                                 $container.highlightText( options.matchText );
157                                 matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html();
158                         } else {
159                                 cache[ text ][ w ][ options.position ] = $container.html();
160                         }
162                 } );
163         };
164         // jscs:enable jsDoc
166         /**
167          * @class jQuery
168          * @mixins jQuery.plugin.autoEllipsis
169          */
171 }( jQuery ) );