-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathjquery.smartEllipsis.js
More file actions
210 lines (189 loc) · 8.97 KB
/
jquery.smartEllipsis.js
File metadata and controls
210 lines (189 loc) · 8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/**
* @preserve
* jQuerySmartEllipsis
* https://github.com/nothrem/jQuerySmartEllipsis
* Adds ellipsis character to the text. Supports both single-line and multi-line text, does not break words unless specified.
* Code based on StackOverflow forum: http://stackoverflow.com/questions/536814/insert-ellipsis-into-html-tag-if-content-too-wide
* Code improved for better performance and extended by Nothrem Sinsky: https://github.com/nothrem
* (c) 2014 - 2023
*/
(function ($) {
if ('function' !== typeof Function.prototype.bind) {
/**
* Sets scope for the method call
*
* @param scope {Object} (optional, default: window) scope for the method
* @returns {Function} new function with defined scope
*/
Function.prototype.bind = function (scope) {
var
f = this,
args = Array.prototype.splice.call(arguments, 1);
return function () {
return f.apply(scope || window, args.concat(arguments));
};
};
}
if (!String.prototype.lastIndexByFunction) {
/**
* Alternative of String.lastIndexOf() that uses function to check if found position is correct. Returns last matching position in the string. May be slower than indexByFunction().
*
* @param {Function} func Function to search for a position. The function is called in the scope of the string and is given a param with current searching position. It should return 0 if the position is OK or position/negative value of how wrong was the position (typically +1 or -1).
* @param {Mixed} params This parameter will be passed in comparision function (as second parameter) as-is.
* @return {Number} Position in the String. Returns -1 if no position found.
*/
String.prototype.lastIndexByFunction = function (func, params) {
var
low = 0,
high = this.length - 1,
best = -1,
mid,
result;
while (low <= high) {
mid = Math.floor((low + high) / 2);
result = func.call(this, mid, params);
if (result < 0) {
high = mid + result;
} else if (result > 0) {
low = mid + result;
} else {
best = mid; //found an occurance somewhere in the middle
low = mid + 1; //there might be another closer to the end
}
}
return best;
};
}
//Allow to handle event 'show' when an element gets visible
$.each(['show', 'toggleClass', 'addClass', 'removeClass'], function () {
var
oldFn = $.fn[this], //get the old function, e.g. $.fn.show or $.fn.hide
/**
* Run all handlers registered for a show event. Must be called in the scope of element to trigger the event handler on.
*/
triggerShowEvent = function () {
// trigger the show msg
$(this).triggerHandler('show');
};
/**
* Function to override original jQuery method and watch for elements that become visible after its call.
*/
$.fn[this] = function () {
var
hidden = this.find(':hidden').add(this.filter(':hidden')), // get the items that are currently hidden
result = oldFn.apply(this, arguments); // run the original function
// for all the hidden elements that are now visible
hidden.filter(':visible').each(triggerShowEvent);
return result;
};
});
var
tooTall = function () {
return this.tempElement.height() > this.el.height();
},
tooWide = function () {
return this.tempElement.width() > this.el.width();
},
ellipsis = function (str, len, breakWords) {
if (true !== breakWords) {
var spacePos = str.lastIndexOf(' ', len);
if (-1 < spacePos) {
len = spacePos;
}
}
if ('.' === str[len - 1]) { //If the trimmed text ends with a dot (end of sentence)
--len; //...then remove it to prevent creating quadruple-dot (....)
}
return str.slice(0, len).trim() + $($.parseHTML('…')).text(); //use HTML entity for ellipsis but convert it to the actual character
},
search = function (i, params) {
params.tempElement.html(params.ellipsis(this, i, params.breakWords));
if (params.comparator()) {
return -1;
}
return 0;
};
// create the ellipsis function
// when addTooltip = true, add a title attribute with the original text
$.fn.ellipsis = function (addTooltip) {
return this.each(function () {
var
el = $(this),
multiline = ('nowrap' !== el.css('white-space')),
breakWords = ('break-word' === el.css('word-wrap')),
tempElement,
content,
comparator,
len;
if (el.is(':hidden')) { //if this isn't visible,
el.one('show', function () { //then hook up the show event
$(this).ellipsis(addTooltip);
});
return;
}
if (el.css('overflow') === 'hidden') { //element cannot overflow text -> must add ellipsis if needed
content = (el.data('origText') || el.html().trim());
el.html(content); //if the text was already trimmed, make sure the original element is sized to its maximum size.
tempElement = $(this.cloneNode(true))
.hide()
.css('position', 'absolute')
.css('overflow', 'visible')
.width(multiline ? el.width() : 'auto')
.css('max-width', 'none')
.height(multiline ? 'auto' : el.height())
.css('max-height', 'none')
.html(content);
el.after(tempElement);
comparator = (multiline ? tooTall : tooWide).bind({el: el, tempElement: tempElement});
if (!comparator()) { //text fits the element, no need to add ellipsis
tempElement.remove();
return;
}
if (addTooltip) { // if a tooltip was requested...
el.attr('title', $.trim(el.text()).replace(/\s\s+/g, ' '));// trim leading/trailing whitespace and consolidate internal whitespace to a single space
}
len = content.lastIndexByFunction(search, {tempElement: tempElement, comparator: comparator, ellipsis: ellipsis, breakWords: breakWords});
if (-1 < len) {
el
.data('origText', content)
.data('trimText', ellipsis(content, len, breakWords))
.html(el.data('trimText'))
.addClass('ellipsis');
} //else even one letter cannot be displayed with ellipsis, so leave it as-is
tempElement.remove();
}
else //element can overflow
if (el.hasClass('ellipsis') //If the element was already modified (or marked as available for this feature)...
&& el.data('trimText')
&& el.html() !== el.data('origText') //...and still contains trimmed text, return original text to allow it to overflow.
) {
el.html(el.data('origText'));
}
});
};
// ellipsification for items with an ellipsis class
$(document).ready(function () {
$('.ellipsis').ellipsis(true);
});
//support resize event to update existing ellipsificated elements
$(window).off('resize.ellipsis').on('resize.ellipsis', function () {
window.setTimeout($.ellipsis, 1); //postpone after all DOM changes are finished
});
if ('onorientationchange' in window) {
$(window).off('orientationchange.ellipsis').on('orientationchange.ellipsis', function () {
window.setTimeout($.ellipsis, 1); //postpone after all DOM changes are finished
});
}
if ('fullscreenElement' in document) {
$(window).off('fullscreenchange.ellipsis mozfullscreenchange.ellipsis webkitfullscreenchange.ellipsis MSFullscreenChange.ellipsis')
.on('fullscreenchange.ellipsis mozfullscreenchange.ellipsis webkitfullscreenchange.ellipsis MSFullscreenChange.ellipsis', function () {
window.setTimeout($.ellipsis, 1); //postpone after all DOM changes are finished
});
}
/**
* Manually updates all existing ellipsificated elements
*/
$.ellipsis = function () {
$('.ellipsis').ellipsis();
};
}(window.jQuery));