1
YUI.add('gallery-ellipsis', function(Y) {
4
* Ellipsis plugin (YUI) - For when text is too l ...
6
* @fileOverview A slightly smarter way of truncating text
7
* @author Dan Beam <dan@danbeam.org>
8
* @param {object} conf - configuration objects to override the defaults
9
* @return {Node} the Node passed to the method
11
* Copyright (c) 2010 Dan Beam
12
* Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
14
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
var // the allowable difference when comparing floating point numbers
26
// floating point comparison
27
fp_equals = function (a, b) { return Math.abs(a - b) <= fp_epsilon; },
28
fp_greater = function (a, b) { return a - b >= fp_epsilon; },
29
fp_lesser = function (a, b) { return a - b <= fp_epsilon; },
31
// do a quick feature test to see if native text-overflow: ellipsis is supported
34
// remember some native styles if we have support
36
'white-space' : 'nowrap',
40
// determine whether we want to use currentStyle instead of some buggy .getComputedStyle() results
43
// add this on all Y.Node instances (but only if imported
44
Y.DOM.ellipsis = function (node, conf) {
46
// homogenize conf to object
49
// augment our conf object with some default settings
52
'ellipsis' : '\u2026',
54
// for stuff we *really* don't want to wrap, increase this number just in case
57
// target number of lines to wrap
60
// whether or not to remember the original text to able to de-truncate
63
// should we use native browser support when it exists? (on by default)
68
// console.log(Y.one(node).getComputedStyle('lineHeight'));
69
// console.log(Y.one(node).getComputedStyle('fontSize'));
71
// the element we're trying to truncate
72
var yEl = Y.one(node),
74
// the name of the field we use to store using .setData()
75
dataAttrName = 'ellipsis-original-text',
78
originalText = conf.remember && yEl.getData(dataAttrName) || yEl.get('text'),
80
// keep the current length of the text so far
81
currentLength = originalText.length,
83
// the number of characters to increment or decrement the text by
84
charIncrement = currentLength,
86
// copy the element so we can string length invisibly
87
clone = Y.one(document.createElement(yEl.get('nodeName'))),
89
// some current values used to cache .getComputedStyle() accesses and compare to our goals
90
lineHeight, targetHeight, currentHeight, lastKnownGood;
93
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
94
// @ NOTE: I'm intentionally ignoring padding as .getComputedStyle('height') @
95
// @ NOTE: and .getComputedStyle('width') both ignore this as well. @
96
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
98
// copy styles to clone object
100
'overflow' : 'hidden', // only at first
101
'position' : 'absolute',
102
'visibility' : 'hidden',
106
'width' : currentStyle ? node.offsetWidth : yEl.getComputedStyle('width'),
107
'fontSize' : currentStyle ? node.currentStyle.fontSize : yEl.getComputedStyle('fontSize'), /* weird IE7 + reset bug */
108
'fontFamily' : yEl.getComputedStyle('fontFamily'),
109
'fontWeight' : yEl.getComputedStyle('fontWeight'),
110
'letterSpacing' : yEl.getComputedStyle('letterSpacing'),
111
'lineHeight' : yEl.getComputedStyle('lineHeight')
114
// insert some text to get the line-height (because .getComputedStyle('lineHeight') can be "normal" sometimes!)
115
clone.set('text', 'some sample text');
117
// unfortunately, we must insert into the DOM, :(
118
Y.one('body').append(clone);
120
// get the height of the node with only 1 character of text (should be 1 line)
121
lineHeight = parseFloat(clone.getComputedStyle('height'));
123
// if we have the native support for text-overflow and we only want 1 line with the same style ellipsis
124
if (Y.DOM.ellipsis.nativeSupport && conf['native'] && 1 == conf.lines && '\u2026' === conf.ellipsis) {
125
// console.log('using native!');
127
yEl.setStyles(nativeStyles);
128
// this is needed to trigger the overflow in some browser (*cough* Opera *cough*)
129
yEl.setStyle('height', lineHeight + 'px');
130
// exit early and clean-up
135
// set overflow back to visible
136
clone.setStyle('overflow', 'visible');
138
// compute how high the node should be if it's the right number of lines
139
targetHeight = conf.lines * lineHeight;
141
// insert the original text, in case we've already truncated
142
clone.set('text', originalText);
144
// ok, now that we have a node in the DOM with the right text, measure it's height
145
currentHeight = parseFloat(clone.getComputedStyle('height'));
147
// console.log('lineHeight', lineHeight);
148
// console.log('currentHeight', currentHeight);
149
// console.log('targetHeight', targetHeight);
150
// console.log('originalText.length', originalText.length);
151
// console.log('yEl.get(\'text\').length', yEl.get('text').length);
153
// quick sanity check
154
if (fp_lesser(currentHeight, targetHeight) && originalText.length === yEl.get('text').length) {
155
// console.log('truncation not necessary!');
160
// now, let's start looping through and slicing the text as necessary
161
for (; charIncrement >= 1; ) {
163
// increment decays by half every time
164
charIncrement = Math.floor(charIncrement / 2);
166
// if the height is too big, remove some chars, else add some
167
currentLength += fp_greater(currentHeight, targetHeight) ? -charIncrement : +charIncrement;
169
// try text at current length
170
clone.set('text', originalText.slice(0, currentLength - conf.ellipsis.length) + conf.ellipsis);
172
// compute the current height
173
currentHeight = parseFloat(clone.getComputedStyle('height'));
175
// we only want to store values that aren't too big
176
if (fp_equals(currentHeight, targetHeight) || fp_lesser(currentHeight, targetHeight)) {
177
lastKnownGood = currentLength;
180
// console.log('currentLength', currentLength);
181
// console.log('currentHeight', currentHeight);
182
// console.log('targetHeight' , targetHeight );
183
// console.log('charIncrement', charIncrement);
184
// console.log('lastKnownGood', lastKnownGood);
191
// set the original text if we want to ever want to expand past the current truncation
192
if (conf.remember && !yEl.getData(dataAttrName)) {
193
yEl.setData(dataAttrName, originalText);
196
// console.log('originalText.length', originalText.length);
197
// console.log('clone.get(\'text\').length', clone.get('text').length);
198
// console.log('conf.ellipsis.length', conf.ellipsis.length);
200
// if the text matches
201
if (originalText.length === (clone.get('text').length - conf.ellipsis.length)) {
202
// this means we *de-truncated* and can fit fully in the new space
203
// console.log('de-truncated!');
204
yEl.set('text', originalText);
206
// this should never happen, but it doesn't hurt to check
207
else if ('undefined' !== typeof lastKnownGood) {
208
// do this thing, already!
209
yEl.set('text', originalText.slice(0, lastKnownGood - conf.ellipsis.length - conf.fudge) + conf.ellipsis);
212
// return myself for chainability
217
Y.Node.importMethod(Y.DOM, 'ellipsis');
218
Y.NodeList.importMethod(Y.Node.prototype, 'ellipsis');
220
// must wait to append hidden node
221
Y.on('domready', function () {
223
// create a hidden node and try to style it
225
hidden = Y.Node.create('<div style="visibility:hidden;position:absolute;white-space:nowrap;overflow:hidden;"></div>'),
226
rules = ['textOverflow', 'OTextOverflow'];
228
// pseudo feature detection to detect browsers with currentStyle but without a more standards-ish implementation (currently IE6-8)
229
currentStyle = !!(document.body.currentStyle && (window.CSSCurrentStyleDeclaration || !window.CSSStyleDeclaration));
231
Y.each(rules, function (rule) {
232
hidden.setStyle(rule, 'ellipsis');
236
Y.one('body').appendChild(hidden);
238
// deep clone the node (include attributes)
239
cloned = hidden.cloneNode(true);
241
Y.some(rules, function (rule) {
242
if ('ellipsis' === cloned.getStyle(rule)) {
244
nativeStyles[nativeRule] = 'ellipsis';
245
Y.DOM.ellipsis.nativeSupport = true;
252
hidden = cloned = null;
257
}, 'gallery-2011.04.13-22-38' ,{requires:['base','node']});