Expand-collapse script for Blogger blogs

This is the expand/collapse post script that was originally written for the Tsunami Help/SEA-EAT blog in December 2004. It has since then, been used on a number of collaborative blogs — Mumbai Help, Katrina Help and Quake Help being some notable ones. If you are looking for a working example, please visit one of these blogs. Also, please remember that this code works only on Blogger/Blogspot blogs. I am sure it can be tweaked to work on WordPress, Movable Type and other publishing platforms, but do so at your own risk. I will be on standby with generous offers of tissue and sympathies, but not much else.

While working on the Tsunami Help blog, we needed a way to show/hide posts on the blog. This functionality had to address criteria that were unique ‘cos of the nature and magnitude of that blog. Existing bloggerhacks didn’t exactly solve the problem either. Here’s why they did not work for us, and the JavaScript-based solution ultimately written to solve the problem. If you are not interested in the long story of the issues we dealt with, you can skip past it and move to the solution directly. Of course reading the story will guarantee you some brownie points which might come in handy if you expect me to answer questions about this code, in the future :)

The Background

  1. The TsunamiHelp blog was an extremely active blog in its first month, with a new post being made every minute, at peak time. A lot of the information posted was time-sensitive and required to remain on the main page leading to approximately 80-100 posts being displayed on the main page at any given time. This meant the page was extremely long and took forever to scroll through.

  2. We considered using the Bloggerhack that creates a [+/-] show/hide this post link for each post. On clicking the link, the reader gets an expanded post to view on the same page. However this method doesn’t excerpt the post, it either hides or displays the entire post. Since the nature of the information posted was varied, posts ranged from 50 to 1000+ words in size. The ideal solution should have displayed summaries for long posts, but shown short posts in full.

  3. We then looked at using the Bloggerhack that allows you to display only an excerpt of the post and places a Read more link somewhere in the post to take you to the post page. However, the downside to this hack is that it requires a <div> tag to be inserted within each post. With 40+ bloggers collaborating on TsunamiHelp, instructing each of them to include <div> and </div> tags at appropriate locations was a near-impossible task.

  4. The high frequency of posting meant that taking people *away* from the main page to a post page could lead to a reader losing their location on the main page. So it was important to provide a way to expand/collapse the post on the main page itself. The reader would see only an initial part of it with a link at the bottom that says something like [+] Show more. If they decide to view the rest of the post, they would click on the link and the entire post is made visible with the link at the bottom changing to something like [-] Show less. A simple toggle function.

  5. We wanted a way to control how much of the beginning text in the post is visible. Plus, the cutoff point should be at a logical place like the end of a paragraph, not in the middle of a word or sentence, or even worse, in the middle of a hyperlink. In addition, we wanted to be able to change the actual text of the [+] Show more / [-] Show less link in a global location, not post by post (as was the case with the Blogger hack).

The Solution

Some notable things to know about this solution —

  1. This is a template-level solution instead of post-level. It automatically takes care of all posts, both past and future, without the need to add extra HTML and without depending on individual authors, as in the case of a group blog.

  2. It allows you to set the number of characters you want to truncate a post on. The default is 500. Also, when truncating, it checks for the first whitespace after it, so truncation doesn’t happen in the middle of a word. If the truncation takes place in the middle of a link, it goes to the end of it, so an unclosed HTML tag doesn’t break the display of the page. This is also true of numbered lists and tables. If the break happens in the middle of a <ol></ol> section or a <table></table> section, it skips to the end of it.

  3. It takes into account a minimum amount of post to hide. For example if a post is 510 characters long. The ‘expand post’ will appear for just 10 characters, which doesn’t make much sense. So there is a minimum length set on the collapse portion, which defaults to 200. To make it clear, if a post is at least 700 characters long, then truncation will take place, at the 500th character.

  4. An ellipsis (…) is added at the end of the truncation indicating that there is more text to be read. When the post is expanded the ‘…’ is automatically removed.

  5. The expand/collapse appears only if a post truncates. The text of the expand/collapse changes to ‘expand’ when the post is collapsed and ‘collapse’ when the post is expanded. If a post is less than 500 characters, then there will be no expand/collapse text displayed. The actual ‘expand/collapse’ text can also be modified by the user. The defaults are — ‘[+] Show more’ and ‘[-] Show less’.

  6. The focus of the post shifts to the title of the post when one clicks on expand and collapse. This way, on collapsing a post one doesn’t lose their position on the page.

To recap, here are the variables the user can modify —

  1. The number of characters to truncate on — default is 500
  2. The minimum number of characters in the text to hide — default is 200
  3. The actual expand/collapse text — default is ‘[+] Show more’ and ‘[-] Show less’
  4. The styling of the expand/collapse text

The Code

The following mix of CSS and JavaScript code provides the controls for you to do this. The procedure for adding this functionality to your Blogger blog template is as follows:

  1. In the <head> … </head> section of your blog template, add the following styles after the end of all your style definitions.
    <!-- Begin styles for show/hide post content -->
    <style type="text/css">
    
    .showpostcontent {
      display:inline;
    } 
    .showhideposttext {
    } 
    .showhidestring {
      text-align:right;
      font-weight: normal;
      font-size: 100%;
      font-family: Trebuchet MS, Verdana, Arial, Helvetica, sans-serif; 
      padding:0px 10px 0px 0px;
    }
    .showhidelink {
      color:#990000;
      letter-spacing:0.1em;
    }
    </style>
    <!-- End styles for show/hide post content -->
    

    The .showhidestring and .showhidelink style classes control the display of the “[+] Show more” and “[-] Show less” links. You can change these styles to use a different font style, color etc. to suit the design/look of your blog.

  2. In the <head> … </head> section of your blog template, add the following JavaScript after the end of all other JavaScript declarations.
    <!-- Begin JavaScript for show/hide post content -->
    
    <script type="text/javascript">
    <!-- //
    
    ///////////////////////////////////////////////////////////////////////////////
    // Description: JavaScript to show/hide post content for Blogger blogs
    // URI: http://www.meghalomania.com/
    // Author: Yumnyum
    // Author URI: http://www.meghalomania.com/
    // Version: 1.0
    ///////////////////////////////////////////////////////////////////////////////
    
    // define the hide style
    if (document.getElementById)
    {
      document.write("<style type=\"text/css\">");
      document.write(".hidepostcontent { display:none; }");
      document.write("</style>");
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // important constants
    ///////////////////////////////////////////////////////////////////////////////
    
    // these determine the position at which to cut off display when
    // hiding post content.
    //
    // minLen      the minimum number of characters (including HTML
    //             syntax) in the post body that will be visible (i.e.
    //             the cutoff - point will be after minLen characters)
    //
    // minHide     the minimum number of characters (including HTML
    //             syntax) in the post body after the cutoff point
    //
    // If the total number of characters in the post body is less than
    // minLen + minHide, then there will be no automatic hiding of content
    // for that post.
    var minLen = 500;
    var minHide = 200;
    
    // display text for show / hide links in the page
    var showMoreTxt = "[+] Show more";
    var showLessTxt = "[-] Show less";
    
    ///////////////////////////////////////////////////////////////////////////////
    // general utility functions
    ///////////////////////////////////////////////////////////////////////////////
    
    // concat a string several times
    function addStrRepeat(str, count)
    {
      var src = "";
      for (var i = 0; i < count; i++)
      {
        src += str;
      }
      return src;
    }
    
    // add an indexOf function to the Array class (this does inefficient
    // linear search, but for the task at hand it should be sufficient)
    if (!Array.prototype.indexOf)
    {
      function Array_indexOf(x)
      {
        for (var i = 0; i < this.length; i++)
        {
          if (this[i] == x)
          {
            return i;
          }
        }
        return -1;
      }
      Array.prototype.indexOf = Array_indexOf;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // functions to manipulate the post body
    ///////////////////////////////////////////////////////////////////////////////
    
    // generate the html code that enables user to switch show / hide mode
    function writeToggleStr(postid, bShowContent)
    {
      var postURL = "#" + postid.substr(4);
      var prefStr = "<a class=\"showhidelink\" href=\"";
      prefStr += postURL;
      prefStr += "\" onclick=javascript:toggleVisible('" + postid + "',";
      var expandStr = prefStr + "true);>" + showMoreTxt + "</a>";
      var collapseStr = prefStr + "false);>" + showLessTxt + "</a>";
    
      whichpost = document.getElementById(postid); 
      children = whichpost.childNodes;
      for (var i = 0; i < children.length; i++)
      {
        if (children[i].tagName == "DIV" && children[i].className == "showhidestring")
        {
          children[i].innerHTML = bShowContent ? collapseStr : expandStr;
          break;
        }
      }
    }
    
    // generate the div containing the post text
    function getPostBody(postid)
    {
      var postBody = null;
      whichpost = document.getElementById(postid);
      children = whichpost.childNodes;
      for (var i = 0; i < children.length; i++)
      {
        if (children[i].tagName == "DIV" && children[i].className == "showhideposttext")
        {
          postBody = children[i];
          break;
        }
      }
      return postBody;
    }
    
    // display the code to enable toggling show/hide in a post
    function displayToggle(postid)
    {
      if (document.getElementById)
      {
        if (limitDisplay(postid))
        {
          writeToggleStr(postid, false);
        }
      }
    }
    
    // add the ellipsis and if specified, the hide tag
    function addHideStr(postBody, breakPos)
    {
      if (breakPos[0] == -1)
      {
        return false;
      }
    
      var postStr = postBody.innerHTML;
    
      var dispStr = postStr.substr(0, breakPos[0]);
      var hideStr = postStr.substr(breakPos[0]);
    
      var newStr = dispStr;
      newStr += "<div class=\"showpostcontent\"> ...</div>";
      //newStr += addStrRepeat("</br>", breakPos[2]);
      //newStr += addStrRepeat("</p>", breakPos[1]);
    
      if (breakPos[3])
      {
        newStr += "<div class=\"hidepostcontent\">";
        //newStr += addStrRepeat("<p>", breakPos[1]);
        //newStr += addStrRepeat("<br>", breakPos[2]);
        newStr += hideStr + "</div>";
      }
      else
      {
        newStr += hideStr;
      }
    
      postBody.innerHTML = newStr;
      return true;
    }
    
    // find the position in the post body where to cutoff text -- it
    // returns an array called breakPos with four elements
    //   breakPos[0] -- cutoff position (-1 indicates no cutoff)
    //   breakPos[1] -- number of open <p> tags
    //   breakPos[2] -- number of open <br> tags
    //   breakPos[3] -- flag to indicate whether to add the 'hide content'
    //                  div (false means the user might have manually
    //                  inserted one, and so we shouldn't be adding
    //                  another)
    function findBreakPos(postBody)
    {
      var breakPos = new Array(-1, 0, 0, false); // break, #p, #br, bAddHideStr
    
      var postStr = postBody.innerHTML;
      var lowPostStr = postStr.toLowerCase();
      var posHidden = lowPostStr.indexOf("hidepostcontent");
      if (posHidden != -1)
      {
        posHidden = lowPostStr.substr(0, posHidden).lastIndexOf("<div");
      }
      if (posHidden != -1)
      {
        breakPos[0] = posHidden;
        return breakPos;
      }
    
      var totLen = lowPostStr.length;
      if (totLen < minLen + minHide)
      {
        return breakPos;
      }
    
      breakPos[3] = true; // add the hide tag
      breakPos[0] = 0;
    
      var tagsList = new Array();
      while (breakPos[0] < minLen || tagsList.length != 0)
      {
        var curStr = lowPostStr.substr(breakPos[0]);
        var tagPos = curStr.indexOf("<");
        if (tagPos == -1)
        {
          break;
        }
    
        var remStr = curStr.substr(tagPos + 1);
        var endTagPos = remStr.indexOf(">");
        var closeTagPos = remStr.indexOf("/>");
        var spacePos = remStr.indexOf(" ");
        var namePos = spacePos != -1 && spacePos < endTagPos ? spacePos : endTagPos;
        if (closeTagPos == endTagPos - 1 && namePos == endTagPos)
        {
          namePos--;
        }
        var tagName = remStr.substr(0, namePos);
        var bTagEnd = tagName.indexOf("/") == 0;
        if (bTagEnd)
        {
          tagName = tagName.substr(1);
        }
    
        if (breakPos[0] + tagPos > minLen && tagsList.length == 0 && 
            (tagName == "p" || tagName == "br"))
        {
          breakPos[0] += tagPos;
          break;
        }
    
        breakPos[0] += tagPos + endTagPos + 2;
    
        if (closeTagPos == endTagPos - 1)
        {
          continue;
        }
    
        if (tagName == "p")
        {
          breakPos[1] += bTagEnd ? -1 : 1;
        }
        else if (tagName == "br")
        {
          breakPos[2] += bTagEnd ? -1 : 1;
        }
        else if (bTagEnd)
        {
          ind = tagsList.indexOf(tagName);
          if (ind != -1)
          {
            tagsList.splice(ind, 1);
          }
        }
        else
        {
          tagsList.push(tagName);
        }
      }
    
      if (breakPos[0] < minLen || totLen - breakPos[0] < minHide)
      {
        breakPos[0] = -1;
      }
    
      return breakPos;
    }
    
    // hide text starting at the appropriate place
    
    function limitDisplay(postid)
    
    {
      var postBody = getPostBody(postid);
      if (postBody)
      {
        return addHideStr(postBody, findBreakPos(postBody));
      }
      return false;
    }
    
    // switch between show and hide mode
    function swapStyles(divBody)
    {  
      var children = divBody.childNodes;
      for (var i = 0; i < children.length; i++)
      {
        if (children[i].tagName == "DIV")
        {
          if (children[i].className == "hidepostcontent")
          {
            children[i].className = "showpostcontent";
          }
          else if (children[i].className == "showpostcontent")
          {
            children[i].className = "hidepostcontent";
          }
        }
      }
    }
    
    // main function to change show/hide mode
    function toggleVisible(postid, bShowContent)
    {
      var postBody = getPostBody(postid);
      if (postBody)
      {
        swapStyles(postBody);
        writeToggleStr(postid, bShowContent);
      }
    }
    
    // -->
    </script>
    
    <!-- End JavaScript for show/hide post content -->
    

    The “important constants” section of the code above provides the controls to adjust the extent of showing / hiding post content as well as its indicators displayed on the page.

  3. And finally the actual HTML section of your template. The part of your blog template that specifies the post body typically looks like:
        <div class="post-body">
        <div>
          <$BlogItemBody$>
        </div>
        </div>
    

    Change this to the following code:

        <!-- Begin post body -->
        <div class="post-body">
         <div id="post<$BlogItemNumber$>">
          <div class="showhideposttext">
           <$BlogItemBody$>
          </div>
          <div class="showhidestring">
           <script type="text/javascript">
           displayToggle("post<$BlogItemNumber$>");
           </script>
          </div>
         </div>
        </div>
        <!-- End post body -->
    

    The additional <div id=”post<$BlogItemNumber$>”> tag sets a unique identifier for each post which is used by the JavaScript code to manipulate its contents. The <div class=”showhideposttext”> tag makes it convenient to separate the actual text of the post from the additional JavaScript calls, which are similarly enclosed by the <div class=”showhidestring”> tag.

  4. Save your template and publish it.

    You are all set. Note that if you preview the blog after making these changes to your template you will be able to see the posts with their contents partially hidden (as expected) and the “[+] Show more” link. However, clicking on these links in the Preview mode will not fully demonstrate the show / hide post content feature — that will only happen in the the final, published blog.

30 thoughts on “Expand-collapse script for Blogger blogs

  1. Pingback: A walk in the clouds.. » The iceberg

  2. Chetan

    Actually, WordPress has two ways of doing this: if you use <?php the_content(); ?> tag (most themes have this default), then you can use the ‘more’ link in posts individually. But, if you want excerpts instead for all posts, like your script (above; excellent for Blogger users, btw) does, then you can use the_excerpt tag. This tag picks-up any entered excerpt for a post (from the Excerpt box in Dashboard > Write). And if the user doesn’t fill the excerpt, then WordPress just picks the post and truncates after certain characters.

    The ‘more’ tag is special too, because it’s not just permalink, but also attaches an anchor link. So when you click on ‘more’, it would take you to the point where the post in excerpt got truncated (instead of just the individual post page).

  3. aNTi

    [Megha] Thanx mom ;) will help me tweak mine up. And btw, I got the php script to run now. Moved the file to a different server and also changed file properties to allow “execute”!

  4. Narto - Indonesia

    I have used your expand/collapse script, but why I cant use inside my post? If I want to justify my alignment, how to do that?

    Your tsunami blog is all-the-way great. More Indonesian need care from the world.

    Sorry for my bad english

  5. aiRah

    hi there.. I’m just hoping if you could help me install it on my blogger template. I can’t seem to figure out how to do it.. i mean, i copied/pasted it on the right tags but after I’ve clicked “publish post” and “view blog”… there seems no difference.. i mean, i can’t see the [+} expand post anywhere…

    ahhhh.. Help! please…

    thanks in advance! :)

  6. matt

    Thanks a bunch. But it’s adding an extra line break at the truncation point when I expand. Any fix for this?

  7. eternity

    Why is it that for some posts that meet all the requirement the hack doesn’t work? Some very long posts are not collapsed as it should be. Please help. Thanks.

  8. brutus

    In my blog it doesn1t work eather. In fact it only works in the shortest entry. Some of the non working entries have pictures just in the biginning, some others don’t.
    Please help me, your mod is great, but for now it does’t work.
    Thanks in advance.

  9. Leif Hansen

    Hello, thanks for the great script.

    Unfortunately, I’ve had the same problem as the above two folks -oddly it works for the first four posts, then skips some of the middle ones (even though they are over the min char #), and then it works again for the last couple…any suggestions as to why?
    Thanks!
    -Leif
    bleedingpurplepodcast.blogspot.com

  10. John F

    I love this script. But, I have had a similar problem as those described above. I have found it to be related to particular HTML tags that seem to confuse the scripts. For example, if I embed an image near the beginning of the post, it skips the post. But if I move the image outside the min/max character range, then it works for that post. Similar issues seen with other tags. I can work around it by re-arranging the content of the post, but would be interested if there are fixes.

  11. Venkatesh

    Hi,
    Thanks a ton for sharing this with all of us here. I would like to know if you have made a new version considering the fact that blogger has been updated. I would really appreciate if you would kindly post a version for the new blogger.

    Regards,

    Venkatesh

  12. Eyes

    Hi,

    I have tried to get your code to work, and I am sure it is a short-coming of mine that is causing the problem.

    I would be honored if you could potentially assist me! I have the code in my template now if you could take a look. It’s much appreciated! Nothing shows up, and I don’t know why.

    Best!
    EFL

  13. Pingback: Herald’s Weblog

  14. Jon

    Ditto on some updated code for the new Blogger. Try as I might, I simply cannot get your code to work, and as I am won to long winded posts, your script would be a great add-on to my blog.

    Thanks for any help.

    Jon.

  15. Ellen

    I think all your reasons for writing this script are bang on. I’ve been searching for something similar.

    Actually, I’d like to use it on my site (not a blog) — I was thinking of applying it to where the would be as if it was the blog title/URL and the would be as if it was the post. I can think of a number of direct applications for this (such as FAQ pages or items in a catalogue).

    I know nothing of scripting .. how would I go about modifying this to work as I’m suggesting? Is it even possible?

    Please feel free to contact me directly to point me in the right direction. I would SO appreciate it.

    I’m sorry to leave this as a comment, but I couldn’t find any other method of contacting you on the site. I guess you got tired of the nasty emails? Go ahead and delete this from the comments (or edit it) as you please.

  16. Ellen

    OOOPS, sorry …

    I was looking for something to apply to where the is equivalent to the Blog Title/URL and the is equivalent to the post content/text.

    Just in case that still doesn’t post properly: A definition list – dl – … where the definition title – dt – … and the definition – dd – …

  17. Guido Trombetta

    Hi!
    the soultion you’ve found is simply perfect and it’s what I’ve been looking for…
    I haven’t found anything like this on the web. Unfortunately I cannot make it works on my blog… Probably because the Blogger template is changed deeply in the meanwhile..
    If you could help me also with the new template it would be really really really great!
    Thanks in advancee
    Guido

  18. Priyam

    But with this code in post pages too we have shortened posts.

    Instead of replacing the code (in step 3) what if we enclose it in itempage tag
    and the new code in mainpage tag

  19. agitsan

    i have copy all script into template script but some thing happen like this
    can you help me the following error bellow

    Your template could not be parsed as it is not well-formed. Please make sure all XML elements are closed properly.
    XML error message: The string “–” is not permitted within comments.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>