4

I have a string as shown below which I want to truncate it after a particular character limit and display an ellipsis.

"This is a long string"

On truncation, I want the string to display something like this:

This is a long (...)

or

This is a (...)

or

This is (...)

The above examples don't cut the words. What I don't want is:

This is a lon (...)

or

This is a long s (...)

The above examples don't work.

I am using the following function in react to truncate a string. The problem with the function is that it sometimes cut the words. The value of length I am using is 175. For 175 character limit, sometimes it cut the words and sometimes it doesn't.

export const wordsTruncate = (words, length) => {
    const j = words.split('');
    let result = j.filter((z, a) => a <= (length - 1));

    return result.join("");
}

I am wondering what changes I need to make in the function above so that it doesn't cut the words as shown in the examples above.

flash
  • 1,455
  • 11
  • 61
  • 132
  • What do you do if there's a massive word like 600000 random characters? That aside, it seems easier to use `.slice()` here -- `words.slice(0, length).split(/\s+/)`, say? Your `.split("")` just splits on letters, not words. – ggorlen May 24 '21 at 02:03
  • @ggorlen thanks for the answer. I am wondering if you can explain me in an example. – flash May 24 '21 at 02:07
  • I'm not sure about your application of this, but if you are displaying the text on a webpage. The CSS rules will handle this for you. – Matthew Kwong May 24 '21 at 02:47
  • 2
    Does this answer your question? [Shorten string without cutting words in JavaScript](https://stackoverflow.com/questions/5454235/shorten-string-without-cutting-words-in-javascript) – Jehong Ahn May 24 '21 at 02:56
  • @ggorlen I tried your answer but it didn't work. – flash May 27 '21 at 16:49
  • What about it doesn't work? I'm not sure what your specification is, exactly. If you use the above code, pop off the last element and join it back together, add an ellipsis. – ggorlen May 27 '21 at 16:57
  • @ggorlen My apologies for not clear in the earlier comment. I pasted your code in my codebase but it didn't work. It has removed all the white space between the characters. This is the code I have pasted `export const wordsTruncate = (words, length) => { const j = words.slice(0, length).split(/\s+/) let result = j.filter((z, a) => a <= (length - 1)); return result.join(""); }` – flash May 27 '21 at 17:13
  • That's not what I'm suggesting--`filter` is probably useless here, but please take the above dupe suggestion: [Shorten string without cutting words in JavaScript](https://stackoverflow.com/questions/5454235/shorten-string-without-cutting-words-in-javascript). There are many edge cases that are unclear to me: does the appended elipses count as part of the total character limit, for example? How should leading spaces be treated? What if there is only a single word and the cutoff limit is less than that word's length? Do you exceed the limit and preserve the word or return nothing? (etc) – ggorlen May 27 '21 at 17:21
  • I will answer your questions. **Q:** Does the appended elipses count as part of the total character limit, for example? How should leading spaces be treated? **A:** Appended ellipsis will not count as a part of the total character limit. Spaces will be counted in the character limit. **Q:** Do you exceed the limit and preserve the word or return nothing **A:** The whole point of adding ellipsis is when there are longer descriptions then it should be truncated into an ellipsis. The ellipsis will be a link that will open the description in a new page (Its a next task) . – flash May 27 '21 at 18:38
  • Can you give an **example** of this question ? What if there is only a single word and the cutoff limit is less than that word's length? – flash May 27 '21 at 18:42

7 Answers7

1

You might use a dynamic pattern, where you can specify a minimum number of chars at the start of the string for which you want the ellipsis to work, and the length of the number of characters.

^(?!\s)(.{3,10}\S)(?!\S).*

Regex demo

  • ^ Start ofs tring
  • (?!\s) Assert not a whitespace char at the start
  • (.{3,10}\S) Capture group 1 (Denoted by m[1] in the example code), match 3 - 10 times any char followed by matching a non whitespace char at the end
  • (?!\S) Negative lookahead, assert a whitespace boundary to the right
  • .* Match the rest of the line

const wordsTruncate = (s, minNrOfChars, length) => {
  if (minNrOfChars > length || minNrOfChars < 1 || length < 1) return s;
  minNrOfChars--;
  length--;
  const regex = new RegExp(`^(?!\\s)(.{${minNrOfChars},${length}\}\\S)(?!\\S).*`);
  const m = s.match(regex);
  return m ? `${m[1]} (...)` : s;
}

const strings = [
  "This is a long string",
  "a b c d e f g h",
  "a b c d e       ",
  "a b",
  "this isatest",
  "thisisatesttesttest",
  "A bcdefghifkkle",
  "a",
  "ab",
  "abc",
  "abcd",
  "abcde"
];

strings.forEach(s => {
  console.log(`"${s}" --> ${wordsTruncate(s, 3, 10)}`);
});
The fourth bird
  • 154,723
  • 16
  • 55
  • 70
1

Try this. hope this works for you.

const wordsTruncate = (words, length) => {
  words = words.trim(); //you need to decied wheather you do this or not
  length -= 6; // because ' (...)'.length === 6
  if (length >= words.length) return words;

  let oldResult = /\s/.test(words.charAt(length));
  for (let i = length - 1; i > -1; i--) {
    const currentRusult = /\s/.test(words.charAt(i))

    //check if oldresult is white space and current is not.
    //which means current character is end of word
    if (oldResult && !currentRusult) {
      return `${words.substr(0, i + 1)} (...)`;
    }
    oldResult = currentRusult;
  }
  // you need to decide whether you will just return truncated or original
  // in case you want original just return word
  return '(...)';
}

console.log(wordsTruncate('This is long text blabla blasdf test.', 32));
console.log(wordsTruncate('This is', 22));
console.log(wordsTruncate('This is', 7));
console.log(wordsTruncate('This is', 13));
console.log(wordsTruncate('This is ', 13));
console.log(wordsTruncate('Thisadsfasdfasdfasdfasdf', 22));

So length should be final length of truncated string.

eg: 'This is long text blabla (...)'.length

Zhang TianYu
  • 1,163
  • 5
  • 11
1

if you are using lodash in your react project, which most of us do, use it's truncate method like so

import truncate from 'lodash/truncate'

// ...

truncate('This is a long string', {
  'length': 20,
  'separator': ' ',
  'omission': ' ...',
})

// result: 'This is a long ...'
Liviu
  • 241
  • 3
  • 5
0

Try something like this:

export const wordsTruncate = (str, length) => {
  const words = str.split(' ');
  const result = words.reduce((acc, it) => {
    const parcial = acc + ' ' + it;
    return parcial.length >= length ? acc : parcial;
  }, '');

  return result + ' (...)';
};
Esdras Amora
  • 11
  • 1
  • 2
0

Hope this works for you

const truncateWord = (string, length) => {
  if (string.length < length) {
    return `${string} (...)`;
  }
  return `${string.substring(0, string.substring(0, length).lastIndexOf(" "))} (...)`;
};

console.log(truncateWord("The quick brown fox jumps over the lazy dog", 10));


Result

The quick ...
0

[The value of length I am using is 175. For 175 character limit, sometimes it cut the words and sometimes it doesn't.]

You should check whether the 176th character is a space (" ") or not.

Ezani
  • 535
  • 3
  • 18
0

I hope this works for you.

function truncateWords(string, maxLen) {
    if (maxLen >= string.length) return string;
    if (string[maxLen - 6] == ' ') {
        return string.slice(0, maxLen - 5).split(' ').join(' ') + ' (...)';
    }
    return string.slice(0, maxLen - 5).split(' ').slice(0, -1).join(' ') + ' (...)';
}
const strings = [
  "abcdefghijklmno",
  "This is a long string",
  "a b c d e f g h",
  "a b c d e f g h i",
  "a b c d e       ",
  "a b",
  "this isatest",
  "thisisatesttesttest",
  "A bcdefghifkkle",
  "a",
  "ab",
  "abc",
  "abcd",
  "abcde"
];
strings.forEach(function(string) {
  console.log(truncateWords(string, 15));
});

/* result
abcdefghijklmno
This is a  (...)
a b c d e f g h
a b c d e  (...)
a b c d e  (...)
a b
this isatest
 (...)
A bcdefghifkkle
a
ab
abc
abcd
abcde
*/
First Arachne
  • 786
  • 1
  • 8
  • 18