Created
November 26, 2013 22:53
-
-
Save dejanmarkovic/7667778 to your computer and use it in GitHub Desktop.
This is DIRTY solution. If you want to use normal solution with WordPress use wp_enqueue_script
In order to have jQuery.expander working in WordPress put the plugin code inside the "$(document).ready(function() " part.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Expander - v1.4.7 - 2013-08-30 | |
* http://plugins.learningjquery.com/expander/ | |
* Copyright (c) 2013 Karl Swedberg | |
* Licensed MIT (http://www.opensource.org/licenses/mit-license.php) | |
*/ | |
$(document).ready(function() { | |
(function($) { | |
$.expander = { | |
version: '1.4.7', | |
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: '… ', | |
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) {} | |
afterCollapse: null // function() {} | |
} | |
}; | |
$.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, html, expand, | |
$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, | |
moreClass = o.moreClass + '', | |
lessClass = o.lessClass + '', | |
expandSpeed = o.expandSpeed || 0, | |
allHtml = $.trim( $this.html() ), | |
allText = $.trim( $this.text() ), | |
summaryText = allHtml.slice(0, o.slicePoint); | |
// allow multiple classes for more/less links | |
o.moreSelector = 'span.' + moreClass.split(' ').join('.'); | |
o.lessSelector = 'span.' + lessClass.split(' ').join('.'); | |
// bail out if we've already set up the expander on this element | |
if ( $.data(this, 'expanderInit') ) { | |
return; | |
} | |
$.data(this, 'expanderInit', true); | |
$.data(this, 'expander', o); | |
// determine which callback functions are defined | |
$.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse', 'afterCollapse'], 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(o.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 | |
html = buildHTML(o, hasBlocks); | |
$this.html( html ); | |
// set up details and summary for expanding/collapsing | |
$thisDetails = $this.find(detailSelector); | |
$readMore = $this.find(o.moreSelector); | |
// Hide details span using collapseEffect unless | |
// expandEffect is NOT slideDown and collapseEffect IS slideUp. | |
// The slideUp effect sets span's "default" display to | |
// inline-block. This is necessary for slideDown, but | |
// problematic for other "showing" animations. | |
// Fixes #46 | |
if (o.collapseEffect === 'slideUp' && o.expandEffect !== 'slideDown' || $this.is(':hidden')) { | |
$thisDetails.css({display: 'none'}); | |
} else { | |
$thisDetails[o.collapseEffect](0); | |
} | |
$summEl = $this.find('div.' + o.summaryClass); | |
expand = function(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); | |
}); | |
}; | |
$readMore.find('a').unbind('click.expander').bind('click.expander', expand); | |
if ( o.userCollapse && !$this.find(o.lessSelector).length ) { | |
$this | |
.find(detailSelector) | |
.append('<span class="' + o.lessClass + '">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>'); | |
} | |
$this | |
.find(o.lessSelector + ' 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); | |
} | |
}); | |
}); // this.each | |
}, | |
destroy: function() { | |
this.each(function() { | |
var o, details, | |
$this = $(this); | |
if ( !$this.data('expanderInit') ) { | |
return; | |
} | |
o = $.extend({}, $this.data('expander') || {}, opts); | |
details = $this.find('.' + o.detailClass).contents(); | |
$this.removeData('expanderInit'); | |
$this.removeData('expander'); | |
$this.find(o.moreSelector).remove(); | |
$this.find('.' + o.summaryClass).remove(); | |
$this.find('.' + o.detailClass).after(details).remove(); | |
$this.find(o.lessSelector).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(); | |
} | |
if (o.afterCollapse) {o.afterCollapse.call(el);} | |
}); | |
} | |
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); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment