/*!
* Smart truncation jQuery plugin
*
* Copyright (c) 2013 Florian Plank (http://www.polarblau.com/)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* USAGE:
*
* $('.text li').smartTruncation();
* $('.text-2 li').smartTruncation({
* "truncateCenter" : true
* });
* $('.files li').smartTruncation({
* protectExtensions" : true
* });
*
*/
(function($) {
$.fn.smartTruncation = function(options) {
var settings = $.merge(options || {}, {
"protectExtensions" : false,
"truncateCenter" : false
});
return $(this).each(function() {
var $this = $(this);
// cache
if (!$(window).data('smarttruncation.sizecache')) $(window).data('smarttruncation.sizecache', {});
var fontAttributes = {
'fontSize' : $this.css('fontSize'),
'fontFamily': $this.css('fontFamily'),
'fontWeight': $this.css('fontWeight'),
'fontStyle' : $this.css('fontStyle')
};
// use font properties for cachekey
var cacheKey = (function(attributes) {
var key = "";
for (key in attributes) key += attributes[key];
return key;
})(fontAttributes);
var sizes = {};
// has text with identical font properties been measure before?
if ($(window).data('smarttruncation.sizecache')[cacheKey]) {
sizes = $(window).data('smarttruncation.sizecache')[cacheKey];
} else {
// let's get the width of the most common characters
// in the current font-size
var letters = "a b c d e f g h i j k l m n o p q r s t u v w x y z".split(" ");
var numbers = "1 2 3 4 5 6 7 8 9 0".split(" ");
var other = "! ยง $ % & / ( ) = ? @ * ' + # - ; , : . < >".split(" ");
var all = letters.concat([" ", '"', "'"]).concat(numbers).concat(other).concat($.map(letters, function(letter) {
return letter.toUpperCase();
}));
// build a test container
var $testWrapper = $('').css($.extend({
'visibility': 'hidden'
}, fontAttributes)).appendTo('body');
// place each character into the container and take it's width
$.each(all, function(i, e) {
sizes[e] = $testWrapper.text(e).width();
});
$testWrapper.remove();
// cache the result for re-use
$(window).data('smarttruncation.sizecache')[cacheKey] = sizes;
}
// wrap content in a inline element to get exact width
var $wrapper = $this
.wrapInner('', {
'class' : 'smarttruncation-wrapper'
})
.find('span').css({
'whiteSpace': 'nowrap'
});
var origText = $.trim($wrapper.text());
var outerWidth = $wrapper.width();
var tracking = parseInt($this.css('letterSpacing'), 10) || -1;
// keep extension visibile if file name
var extension = "";
var fileName = origText;
if (settings.protectExtensions) {
var str = origText.split('.');
extension = str.pop();
fileName = str.join('.');
}
// truncate if necessary and append ellipsis
var update = function() {
// how much do we need to shave off including the ellipsis we want to append?
var diff = $this.width() - outerWidth - 3 * sizes['.'];
var safety;
// do we need to truncate
if (diff <= 0) {
// split the string into separate characters
var chunks = fileName.split("");
// do we truncate from the inside out?
if (settings.truncateCenter) {
// cut the string in two, left holds one half, chunks the other
var left = chunks.slice(0, Math.floor(chunks.length/2));
var right = chunks;
var lengthLeft = left.length;
var lengthRight = right.length;
// take one character at time from which ever side is bigger off
while (diff <= 0) {
var next = lengthLeft > lengthRight? left[--lengthLeft] : left[--lengthRight];
// update the difference between wanted and actual size by checking the size
// of the character from the sizes dictionary and add tracking
// use the letter "h" in case the current character does not exist in the
// sizes dictionary
diff = diff + (sizes[next] || sizes['h']) + tracking;
}
// put the truncated text back plus ellipsis and file extension
$wrapper.text(
$.trim(left.slice(0, lengthLeft).join(""))
+ "..."
+ $.trim(right.slice(right.length - lengthRight).join(""))
+ extension
);
// fallback: sometimes (3-5%) the string still doesn't fit:
// insure that text stays within bounds under all circumstances by popping
// one letter at time, while switching sides, trying to fit every time
if ($wrapper.width() > $this.width()) {
while ($wrapper.width() > $this.width()) {
lengthLeft > lengthRight? lengthLeft-- : lengthRight--;
$wrapper.text(
$.trim(left.slice(0, lengthLeft).join(""))
+ "..."
+ $.trim(right.slice(right.length - lengthRight).join(""))
+ extension
);
}
} else {
// allow for a little bit room, if we check for absolutes,
// the browser will get caught in a loop and the sky will come down
// let's use 40% of the fontsize
safety = parseInt(fontAttributes.fontSize, 10) * 0.4;
if ($wrapper.width() + safety < $this.width()) {
while ($wrapper.width() + safety < $this.width()) {
lengthLeft < lengthRight? lengthLeft++ : lengthRight++;
$wrapper.text(
$.trim(left.slice(0, lengthLeft).join(""))
+ "..."
+ $.trim(right.slice(right.length - lengthRight).join(""))
+ extension
);
}
}
}
// we only truncate the end, possibly keeping the file extension
} else {
var length = chunks.length;
while (diff <= 0) {
// update the difference between wanted and actual size by checking the size
// of the character from the sizes dictionary and add tracking
// use the letter "h" in case the current character does not exist in the
// sizes dictionary
diff = diff + (sizes[--length] || sizes['h']) + tracking / 2;
}
$wrapper.text($.trim(chunks.slice(0, length).join("")) + "..." + extension);
// fallback: sometimes (3-5%) the string still doesn't fit:
// insure that text stays within bounds under all circumstances by popping
// one letter at time, trying to fit every time
if ($wrapper.width() > $this.width()) {
while ($wrapper.width() > $this.width()) {
$wrapper.text($.trim(chunks.slice(0, --length).join("")) + "..." + extension);
}
} else {
// allow for a little bit room, if we check for absolutes,
// the browser will get caught in a loop and the sky will come down
// let's use 40% of the fontsize
safety = parseInt(fontAttributes.fontSize, 10) * 0.4;
if ($wrapper.width() + safety < $this.width()) {
while ($wrapper.width() + safety < $this.width()) {
$wrapper.text($.trim(chunks.slice(0, ++length).join("")) + "..." + extension);
}
}
}
}
} else {
$wrapper.text(origText);
}
};
// call if window resized
$(window).bind('resize.smarttruncation', function() {
update();
});
// initialize
update();
});
};
})(jQuery);