text: /*!
   * jQuery Expander Plugin v1.4.3
   *
   * Date: Tue Jun 19 11:54:00 2012 EDT
   * Requires: jQuery v1.3+
   *
   * Copyright 2011, Karl Swedberg
   * Dual licensed under the MIT and GPL licenses (just like jQuery):
   * http://www.opensource.org/licenses/mit-license.php
   * http://www.gnu.org/licenses/gpl.html
   *
   *
   *
   *
  */
  
  (function($) {
    $.expander = {
      version: '1.4.3',
      defaults: {
        // the number of characters at which the contents will be sliced into two parts.
        slicePoint: 100,
  
        // whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false)
        preserveWords: true,
  
        // a threshold of sorts for whether to initially hide/collapse part of the element's contents.
        // If after slicing the contents in two there are fewer words in the second part than
        // the value set by widow, we won't bother hiding/collapsing anything.
        widow: 4,
  
        // text displayed in a link instead of the hidden part of the element.
        // clicking this will expand/show the hidden/collapsed text
        expandText: 'read more',
        expandPrefix: '&hellip; ',
  
        expandAfterSummary: false,
  
        // class names for summary element and detail element
        summaryClass: 'summary',
        detailClass: 'details',
  
        // class names for <span> around "read-more" link and "read-less" link
        moreClass: 'read-more',
        lessClass: 'read-less',
  
        // number of milliseconds after text has been expanded at which to collapse the text again.
        // when 0, no auto-collapsing
        collapseTimer: 0,
  
        // effects for expanding and collapsing
        expandEffect: 'slideDown',
        expandSpeed: 250,
        collapseEffect: 'slideUp',
        collapseSpeed: 200,
  
        // allow the user to re-collapse the expanded text.
        userCollapse: true,
  
        // text to use for the link to re-collapse the text
        userCollapseText: 'read less',
        userCollapsePrefix: ' ',
  
  
        // all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called
  
        onSlice: null, // function() {}
        beforeExpand: null, // function() {},
        afterExpand: null, // function() {},
        onCollapse: null // function(byUser) {}
      }
    };
  
    $.fn.expander = function(options) {
      var meth = 'init';
  
      if (typeof options == 'string') {
        meth = options;
        options = {};
      }
  
      var opts = $.extend({}, $.expander.defaults, options),
          rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i,
          rAmpWordEnd = opts.wordEnd || /(&(?:[^;]+;)?|[a-zA-Z\u00C0-\u0100]+)$/,
          rOpenCloseTag = /<\/?(\w+)[^>]*>/g,
          rOpenTag = /<(\w+)[^>]*>/g,
          rCloseTag = /<\/(\w+)>/g,
          rLastCloseTag = /(<\/[^>]+>)\s*$/,
          rTagPlus = /^<[^>]+>.?/,
          delayedCollapse;
  
      var methods = {
        init: function() {
          this.each(function() {
            var i, l, tmp, newChar, summTagless, summOpens, summCloses,
                lastCloseTag, detailText, detailTagless,
                $thisDetails, $readMore,
                openTagsForDetails = [],
                closeTagsForsummaryText = [],
                defined = {},
                thisEl = this,
                $this = $(this),
                $summEl = $([]),
                o = $.extend({}, opts, $this.data('expander') || $.meta && $this.data() || {}),
                hasDetails = !!$this.find('.' + o.detailClass).length,
                hasBlocks = !!$this.find('*').filter(function() {
                  var display = $(this).css('display');
                  return (/^block|table|list/).test(display);
                }).length,
                el = hasBlocks ? 'div' : 'span',
                detailSelector = el + '.' + o.detailClass,
                moreSelector = 'span.' + o.moreClass,
                expandSpeed = o.expandSpeed || 0,
                allHtml = $.trim( $this.html() ),
                allText = $.trim( $this.text() ),
                summaryText = allHtml.slice(0, o.slicePoint);
  
            // bail out if we've already set up the expander on this element
            if ( $.data(this, 'expanderInit') ) {
              return;
            }
  
            $.data(this, 'expanderInit', true);
  
            // determine which callback functions are defined
            $.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) {
              defined[val] = $.isFunction(o[val]);
            });
  
            // back up if we're in the middle of a tag or word
            summaryText = backup(summaryText);
  
            // summary text sans tags length
            summTagless = summaryText.replace(rOpenCloseTag, '').length;
  
            // add more characters to the summary, one for each character in the tags
            while (summTagless < o.slicePoint) {
              newChar = allHtml.charAt(summaryText.length);
              if (newChar == '<') {
                newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0];
              }
              summaryText += newChar;
              summTagless++;
            }
  
            summaryText = backup(summaryText, o.preserveWords);
  
            // separate open tags from close tags and clean up the lists
            summOpens = summaryText.match(rOpenTag) || [];
            summCloses = summaryText.match(rCloseTag) || [];
  
            // filter out self-closing tags
            tmp = [];
            $.each(summOpens, function(index, val) {
              if ( !rSelfClose.test(val) ) {
                tmp.push(val);
              }
            });
            summOpens = tmp;
  
            // strip close tags to just the tag name
            l = summCloses.length;
            for (i = 0; i < l; i++) {
              summCloses[i] = summCloses[i].replace(rCloseTag, '$1');
            }
  
            // tags that start in summary and end in detail need:
            // a). close tag at end of summary
            // b). open tag at beginning of detail
            $.each(summOpens, function(index, val) {
              var thisTagName = val.replace(rOpenTag, '$1');
              var closePosition = $.inArray(thisTagName, summCloses);
              if (closePosition === -1) {
                openTagsForDetails.push(val);
                closeTagsForsummaryText.push('</' + thisTagName + '>');
  
              } else {
                summCloses.splice(closePosition, 1);
              }
            });
  
            // reverse the order of the close tags for the summary so they line up right
            closeTagsForsummaryText.reverse();
  
            // create necessary summary and detail elements if they don't already exist
            if ( !hasDetails ) {
  
              // end script if there is no detail text or if detail has fewer words than widow option
              detailText = allHtml.slice(summaryText.length);
              detailTagless = $.trim( detailText.replace(rOpenCloseTag, '') );
  
              if ( detailTagless === '' || detailTagless.split(/\s+/).length < o.widow ) {
                return;
              }
              // otherwise, continue...
              lastCloseTag = closeTagsForsummaryText.pop() || '';
              summaryText += closeTagsForsummaryText.join('');
              detailText = openTagsForDetails.join('') + detailText;
  
            } else {
              // assume that even if there are details, we still need readMore/readLess/summary elements
              // (we already bailed out earlier when readMore el was found)
              // but we need to create els differently
  
              // remove the detail from the rest of the content
              detailText = $this.find(detailSelector).remove().html();
  
              // The summary is what's left
              summaryText = $this.html();
  
              // allHtml is the summary and detail combined (this is needed when content has block-level elements)
              allHtml = summaryText + detailText;
  
              lastCloseTag = '';
            }
            o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o);
  
            if (hasBlocks) {
              detailText = allHtml;
            }
            summaryText += lastCloseTag;
  
            // onSlice callback
            o.summary = summaryText;
            o.details = detailText;
            o.lastCloseTag = lastCloseTag;
  
            if (defined.onSlice) {
              // user can choose to return a modified options object
              // one last chance for user to change the options. sneaky, huh?
              // but could be tricky so use at your own risk.
              tmp = o.onSlice.call(thisEl, o);
  
            // so, if the returned value from the onSlice function is an object with a details property, we'll use that!
              o = tmp && tmp.details ? tmp : o;
            }
  
            // build the html with summary and detail and use it to replace old contents
            var html = buildHTML(o, hasBlocks);
  
            $this.html( html );
  
            // set up details and summary for expanding/collapsing
            $thisDetails = $this.find(detailSelector);
            $readMore = $this.find(moreSelector);
            $thisDetails[o.collapseEffect](0);
            $readMore.find('a').unbind('click.expander').bind('click.expander', expand);
  
            $summEl = $this.find('div.' + o.summaryClass);
  
            if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) {
              $this
              .find(detailSelector)
              .append('<span class="' + o.lessClass + '">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>');
            }
  
            $this
            .find('span.' + o.lessClass + ' a')
            .unbind('click.expander')
            .bind('click.expander', function(event) {
              event.preventDefault();
              clearTimeout(delayedCollapse);
              var $detailsCollapsed = $(this).closest(detailSelector);
              reCollapse(o, $detailsCollapsed);
              if (defined.onCollapse) {
                o.onCollapse.call(thisEl, true);
              }
            });
  
            function expand(event) {
              event.preventDefault();
              $readMore.hide();
              $summEl.hide();
              if (defined.beforeExpand) {
                o.beforeExpand.call(thisEl);
              }
  
              $thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() {
                $thisDetails.css({zoom: ''});
                if (defined.afterExpand) {o.afterExpand.call(thisEl);}
                delayCollapse(o, $thisDetails, thisEl);
              });
            }
  
          }); // this.each
        },
        destroy: function() {
          if ( !this.data('expander') ) {
            return;
          }
          this.removeData('expander');
          this.each(function() {
            var $this = $(this),
                o = $.meta ? $.extend({}, opts, $this.data()) : opts,
                details = $this.find('.' + o.detailClass).contents();
  
            $this.find('.' + o.moreClass).remove();
            $this.find('.' + o.summaryClass).remove();
            $this.find('.' + o.detailClass).after(details).remove();
            $this.find('.' + o.lessClass).remove();
  
          });
        }
      };
  
      // run the methods (almost always "init")
      if ( methods[meth] ) {
        methods[ meth ].call(this);
      }
  
      // utility functions
      function buildHTML(o, blocks) {
        var el = 'span',
            summary = o.summary;
        if ( blocks ) {
          el = 'div';
          // if summary ends with a close tag, tuck the moreLabel inside it
          if ( rLastCloseTag.test(summary) && !o.expandAfterSummary) {
            summary = summary.replace(rLastCloseTag, o.moreLabel + '$1');
          } else {
          // otherwise (e.g. if ends with self-closing tag) just add moreLabel after summary
          // fixes #19
            summary += o.moreLabel;
          }
  
          // and wrap it in a div
          summary = '<div class="' + o.summaryClass + '">' + summary + '</div>';
        } else {
          summary += o.moreLabel;
        }
  
        return [
          summary,
          '<',
            el + ' class="' + o.detailClass + '"',
          '>',
            o.details,
          '</' + el + '>'
          ].join('');
      }
  
      function buildMoreLabel(o) {
        var ret = '<span class="' + o.moreClass + '">' + o.expandPrefix;
        ret += '<a href="#">' + o.expandText + '</a></span>';
        return ret;
      }
  
      function backup(txt, preserveWords) {
        if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) {
          txt = txt.slice( 0, txt.lastIndexOf('<') );
        }
        if (preserveWords) {
          txt = txt.replace(rAmpWordEnd,'');
        }
  
        return $.trim(txt);
      }
  
      function reCollapse(o, el) {
        el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() {
          var prevMore = el.prev('span.' + o.moreClass).show();
          if (!prevMore.length) {
            el.parent().children('div.' + o.summaryClass).show()
              .find('span.' + o.moreClass).show();
          }
        });
      }
  
      function delayCollapse(option, $collapseEl, thisEl) {
        if (option.collapseTimer) {
          delayedCollapse = setTimeout(function() {
            reCollapse(option, $collapseEl);
            if ( $.isFunction(option.onCollapse) ) {
              option.onCollapse.call(thisEl, false);
            }
          }, option.collapseTimer);
        }
      }
  
      return this;
    };
  
    // plugin defaults
    $.fn.expander.defaults = $.expander.defaults;
  })(jQuery);
  
  