views:

1482

answers:

3

Hi There,

My question is very similar to "http://stackoverflow.com/questions/2104653/trim-text-to-340-chars" but in jQuery. It sounded very straight forward but when I searched around I couldn't find any reference for it.

Ok, I have a div $('#content') I want to trim the text to 'x' amount of characters lets say '600' BUT I don't want it to break the word it self! Like NOT 'The Ques...' BUT 'The Questions...'.

What happens to the rest of the text? Well, I hide it and will show it on request! But wait, it should first remove the '...' and show the text right after where it hid.

Here is the sample structure for $('#content'):

<div id="content">
    <p>Once upon a midnight dreary, while I pondered weak and weary,Over many a quaint and curious volume of forgotten lore.</p>
    <p>Writing example text is very boring.</p>
    <p>Specially when you are dumb enough not to copy and paste. Oh!</p>
    <p>Once it sheltered the rogues and rapscallions of the British Empire; now Kangaroo Island is ready and waiting for some derring-do of your own. Venture into the rugged wilds of the island, traversing untamed bushland and pristine beaches seeking out seal, penguin and sea lion colonies. Meet the land loving locals - koalas, goannas, echidnas and the island's own species of kangaroo. </p>
</div>

How it should load:

Once upon a midnight dreary, while I pondered weak and weary,Over many a quaint and curious volume of forgotten lore.

Writing example text is... [Read More]

After click on 'Read More':

Once upon a midnight dreary, while I pondered weak and weary,Over many a quaint and curious volume of forgotten lore.

Writing example text is very boring.

Specially when you are dumb enough not to copy and paste. Oh!

Once it sheltered the rogues and rapscallions of the British Empire; now Kangaroo Island is ready and waiting for some derring-do of your own. Venture into the rugged wilds of the island, traversing untamed bushland and pristine beaches seeking out seal, penguin and sea lion colonies. Meet the land loving locals - koalas, goannas, echidnas and the island's own species of kangaroo.


UPDATE: I have found these two plug-ins that do basically same job as this best answer. However the best answer has some functionalities that those plug-ins don't have and vice versa!

+5  A: 

The following works for limiting the amount of text if you don't mind losing the paragraph tags.

<script type="text/javascript">
var shortText = $("#content").text()    // get the text within the div
    .trim()    // remove leading and trailing spaces
    .substring(0, 600)    // get first 600 characters
    .split(" ") // separate characters into an array of words
    .slice(0, -1)    // remove the last full or partial word
    .join(" ") + "..."; // combine into a single string and append "..."
</script>
Tim R
@Tim: Thanks it did exactly what you said it would do. It is just a matter of getting the rest of the text and enable expand/collapse. I will update the question with your bits if no ones have any answer.
Mazzi
@Tim: is `split().slice().join()` faster then `trim().substring(0, lastIndexOf(' '))` ?
Mendy
@Mendy Good point. lastIndexOf should be faster due to less iterations over the entire string.
Tim R
+2  A: 

This code presumes that tags will always be balanced, and that the only tag without a closer will be <br /> (though this could be easily remedied if needed).

#content {
    width: 800px;
    clear:both;
    clip:auto;
    overflow: hidden;
}
.revealText {
    background: white; /* Strange problem in ie8 where the sliding animation goes too far
                            if revealText doesn't have a background color!  */
}
.hiddenText {

}
.readMore {
    cursor: pointer;
    color: blue;
}
.ellipsis {
    color: black;
}

$('document').ready(function() {

truncate('#content');

$('.readMore').live('click', function() {
    var $hidden = $('.hiddenText');
    if($hidden.is(':hidden')) {
        $hidden.show();
        $(this).insertAfter($('#content')).children('.readMoreText').text(' [Read Less] ').siblings().hide();
    } else {
        $(this).appendTo($('.revealText')).children('.readMoreText').text(' [Read More] ').siblings().show();
        $hidden.hide();
    }
});

$('.readMore').click();

function truncate(element) {
    $(element + ' p').css({display: 'inline'});

    var theText = $(element).html();        // Original Text
    var item;                               // Current tag or text area being iterated
    var convertedText = '<span class="revealText">';    // String that will represent the finished result
    var limit = 154;                        // Max characters (though last word is retained in full)
    var counter = 0;                        // Track how far we've come (compared to limit)
    var lastTag;                            // Hold a reference to the last opening tag
    var lastOpenTags = [];                  // Stores an array of all opening tags (they get removed as tags are closed)
    var nowHiding = false;                  // Flag to set to show that we're now in the hiding phase

    theText = theText.replace(/[\s\n\r]{2,}/g, ' ');            // Consolidate multiple white-space characters down to one. (Otherwise the counter will count each of them.)
    theText = theText.replace(/(<[^<>]+>)/g,'|*|SPLITTER|*|$1|*|SPLITTER|*|');                      // Find all tags, and add a splitter to either side of them.
    theText = theText.replace(/(\|\*\|SPLITTER\|\*\|)(\s*)\|\*\|SPLITTER\|\*\|/g,'$1$2');           // Find consecutive splitters, and replace with one only.
    theText = theText.replace(/^[\s\t\r]*\|\*\|SPLITTER\|\*\||\|\*\|SPLITTER\|\*\|[\s\t\r]*$/g,''); // Get rid of unnecessary splitter (if any) at beginning and end.
    theText = theText.split(/\|\*\|SPLITTER\|\*\|/);            // Split theText where there's a splitter. Now we have an array of tags and words.

    for(var i in theText) {                                     // Iterate over the array of tags and words.
        item = theText[i];                                      // Store current iteration in a variable (for convenience)
        lastTag = lastOpenTags[lastOpenTags.length - 1];        // Store last opening tag in a variable (for convenience)
        if( !item.match(/<[^<>]+>/) ) {                         // If 'item' is not a tag, we have text
            if(lastTag && item.charAt(0) == ' ' && !lastTag[1].match(/span|SPAN/)) item = item.substr(1);   // Remove space from beginning of block elements (like IE does) to make results match cross browser
            if(!nowHiding) {                                        // If we haven't started hiding yet...
                counter += item.length;                             // Add length of text to counter.
                if(counter >= limit) {                              // If we're past the limit...
                    var length = item.length - 1;                   // Store the current item's length (minus one).
                    var position = (length) - (counter - limit);    // Get the position in the text where the limit landed.
                    while(position != length) {                     // As long as we haven't reached the end of the text...
                        if( !!item.charAt(position).match(/[\s\t\n]/) || position == length )   // Check if we have a space, or are at the end.
                            break;                                  // If so, break out of loop.
                        else position++;                            // Otherwise, increment position.
                    }
                    if(position != length) position--;
                    var closeTag = '', openTag = '';                // Initialize open and close tag for last tag.
                    if(lastTag) {                                   // If there was a last tag,
                        closeTag = '</' + lastTag[1] + '>';         // set the close tag to whatever the last tag was,
                        openTag = '<' + lastTag[1] + lastTag[2] + '>';  // and the open tag too.
                    }
                    // Create transition from revealed to hidden with the appropriate tags, and add it to our result string
                    var transition = '<span class="readMore"><span class="ellipsis">...</span><span class="readMoreText"> [Read More] </span></span>' + closeTag + '</span><span class="hiddenText">' + openTag;
                    convertedText += (position == length)   ? (item).substr(0) + transition
                                                                : (item).substr(0,position + 1) + transition + (item).substr(position + 1).replace(/^\s/, '&nbsp;');
                    nowHiding = true;       // Now we're hiding.
                    continue;               // Break out of this iteration.
                }
            }
        } else {                                                // Item wasn't text. It was a tag.
            if(!item.match(/<br>|<BR>/)) {                      // If it is a <br /> tag, ignore it.
                if(!item.match(/\//)) {                         // If it is not a closing tag...
                    lastOpenTags.push(item.match(/<(\w+)(\s*[^>]*)>/));     // Store it as the most recent open tag we've found.
                } else {                                                    // If it is a closing tag.
                    if(item.match(/<\/(\w+)>/)[1] == lastOpenTags[lastOpenTags.length - 1][1]) {    // If the closing tag is a paired match with the last opening tag...
                        lastOpenTags.pop();                                                         // ...remove the last opening tag.
                    }
                    if(item.match(/<\/[pP]>/)) {            // Check if it is a closing </p> tag
                        convertedText += ('<span class="paragraphBreak"><br> <br> </span>');    // If so, add two line breaks to form paragraph
                    }
                }
            }
        }   
        convertedText += (item);            // Add the item to the result string.
    }
    convertedText += ('</span>');           // After iterating over all tags and text, close the hiddenText tag.
    $(element).html(convertedText);         // Update the container with the result.
}
});


<div id="content">
    <p>Once upon a midnight dreary, while I pondered weak and weary,Over many a quaint and curious volume of forgotten lore.</p>
    <p>Writing example text is very boring.</p>
    <p>Specially when you are dumb enough not to copy and paste. Oh!</p>
    <p>Once it sheltered the rogues and rapscallions of the British Empire; now Kangaroo Island is ready and waiting for some derring-do of your own. Venture into the rugged wilds of the island, traversing untamed bushland and pristine beaches seeking out seal, penguin and sea lion colonies. Meet the land loving locals - koalas, goannas, echidnas and the island's own species of kangaroo. </p>
</div>
patrick dw
@patrick: Thanks for your answer, It did not perform completely as I hoped for it to do. Changing the opacity will hide the text but it won't slide the page up and down. Plus the tricky bit I am trying to figure out is that if 100 character reaches inside a '<p>' tag; when you click view more it will remove the '...' and shows the rest of the content as it originally should have. I hope it makes sense.
Mazzi
@Mazzi - I just did the opacity to visually demonstrate the two halves. Since they are separate, it is simple to slide the hidden part up or down. I just didn't implement it. The way this works, it will never split in the middle of a tag. Only characters in the *words* are added to the counter. The counter passes right over the tags. So it shouldn't be an issue.
patrick dw
@Mazzi - Question for you. If you want the text to be hidden via a slideup, then how do you want it to work when the limit ends up in the middle of a line?
patrick dw
@patrick: Sorry didn't realize it was intentionaly left like that. Answer: Here is where I am puzzled. Ideally, if limit reaches in a middle of a line then rest should be replaced with '...' then when you click on 'read more' it should remove the '...' and continues the rest of the text - Just right where it cuts off. Thanks
Mazzi
@patrick: I have also updated the question to make it more clear what the output should produce. Thanks
Mazzi
@Mazzi - Would it be an acceptable solution if the script were to replace the `<p>` tags with `<span>` tags along with a pair of `<br />` tags to make the paragraph? We need this to be inline, but `<p>` tags are block, and you get unexpected results with block tags inside of inline tags.
patrick dw
@patrick: Thanks for your comments, as long as the original look and feel doesn't change we can change tags if wee need to. I will test your code on Monday and let you know. Cheers.
Mazzi
@patrick, Thanks very much for you help, but it still giving me the grief. Do you have the example live somewhere?
Mazzi
@Mazzi - I've been re-working this a bit. Made some changes, eliminated some problems. Will have it posted within a day or so.
patrick dw
@patrick - please let me know when you updated it so I can test it. Cheers
Mazzi
@Mazzi - Fixed some bugs, found more and fixed those, made things run more efficiently. Both the JS and CSS were updated. I'll probably tinker a bit more in the future. Will comment here when I do. Let me know if it works for you.
patrick dw
@patrick - Well done mate, I am still trying to incorporate it into my current template. One quick question: How would you eliminate the sliding effect and make it just hide and show? I tried various ways but it doesn't like it.P.S. You should make this a jQuery plugin.
Mazzi
@Mazzi - Very simple. Just changed the click event handler. Basically, after the truncate() method runs, the text is split into two classes: revealText and hiddenText. From there, you can manipulate them however you wish using jQuery (or whatever).
patrick dw
@patrick - Well Done mate...
Mazzi
A: 

Well, There is a plugin for it. jQuery Expander.

Teja Kantamneni
That seems to be a safe answer for all jquery questions =)
Andrew Backer
@Teja: You think I have not looked at it before I post the question here?! The plugin doesn't do what I want it to do.
Mazzi
@Mazzi Ok Ok. I just posted what I thought will be useful for you. Here at stackoverflow people try to help others by pointing to right resources not read peoples mind before posting answers.
Teja Kantamneni