Why is my word chopped in half?
-
16-06-2021 - |
Question
I am trying to chop text to a fixed length, but I don't want to chop words at half, so I need to do something like this:
function fixedLength(str, len, bol) {
var i, l = str.length, left = 0, right = l - 1, rtn, tmp;
tmp = str.charAt(len);
if (bol || tmp === " " || tmp === "") {
rtn = str.substr(0, len);
} else {
tmp = len - 1;
for (i = tmp; i > -1; i--) {
if (str.charAt(i) === " ") {
left = i;
break;
}
}
for (i = tmp; i < l; i++) {
if (str.charAt(i) === " ") {
right = i;
break;
}
}
rtn = str.substr(0, ((Math.abs(left - tmp) <= Math.abs(right - tmp)) ? left : right));
}
return rtn + "...";
}
But when I use it with this:
var str = "the quick brown fox jumped over the lazy dog";
for (var i = 0; i < 45; i++) {
document.write("i:" + i + " - " + fixedLength(str, i) + "<br>");
}
Everyone seems to work correctly except in this line "i:43 - the quick brown fox jumped over the lazy do..."
, the word "dog" is chopped in half (Demo)
I can't find the flaw, every time I change something, I add a new bugs
Solution
Didn't check your code, but you could write your code more simple:
function fixedLength(str, len, bol) {
while(!bol && str[len] && str[len] !== ' ') {
len--;
}
return str.substr(0, len) + '...';
}
And the demo.
OTHER TIPS
In case you're interested, the bug in your original code was in the final assignment to the return value, where you compared tmp - left and tmp - right. The problem is that in the case of the last word in the string, "dog", right never gets re-assigned after its initial value is set to l - 1; and so your algorithm acts as if a space were found at index 44 when in fact there is a g there.
You could try to use regex to find the last space
character.
function fixedLength(str, len, bol) {
if(str.length <= len) {
return str;
}
var rtn = str.substr(0, len).match(/.* /);
if(rtn == null) {
rtn = "";
} else {
rtn = rtn + "...";
}
return rtn;
}
Demo here: http://jsfiddle.net/R8qMQ/2/
I also added a verification, if the input string is already in the max allowed length, simply return it. If there are no words that can be split, return an empty string instead of NULL.
I would go with regex since I could also add other characters in the future that I might consider as word delimiters (e.g. .
or ;
or maybe REGEX b
- word delimiter).
rtn = str.substr(0, ((Math.abs(left - tmp + 1) <= Math.abs(right - tmp)) ? left : right));
I didn't debug it with a debugger but using your Demo was very helpful.
I tried refactoring your code in a very understandable manner:
function prettyCut(word, length) {
//account for ellipses
length -= 3;
//break down into lengths
var units = word.split(" ").map(function(word) {
return word.length+1;
});
//map to accumulated sums
var runningSum = 0;
var sums = units.map(function(length, index) {
return (runningSum += length)
});
//find difference from goal of each sum
var differences = sums.map(function(sum) {
return (length-sum)>0?(length-sum):100;
});
//minimize and return
var closest = Math.min.apply(this, differences);
return closest==0?word:word.substr(0, length-closest)+"...";
}
prettyCut("the quick brown fox jumped over the lazy dog", 45);