2018-07-09 02:40:30 +00:00
|
|
|
/*!
|
|
|
|
* @preserve
|
|
|
|
*
|
|
|
|
* Readmore.js jQuery plugin
|
|
|
|
* Author: @jed_foster
|
|
|
|
* Project home: http://jedfoster.github.io/Readmore.js
|
|
|
|
* Licensed under the MIT license
|
|
|
|
*
|
|
|
|
* Debounce function from http://davidwalsh.name/javascript-debounce-function
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* global jQuery */
|
|
|
|
|
|
|
|
(function(factory) {
|
|
|
|
if (typeof define === 'function' && define.amd) {
|
|
|
|
// AMD
|
|
|
|
define(['jquery'], factory);
|
|
|
|
} else if (typeof exports === 'object') {
|
|
|
|
// CommonJS
|
|
|
|
module.exports = factory(require('jquery'));
|
|
|
|
} else {
|
|
|
|
// Browser globals
|
|
|
|
factory(jQuery);
|
|
|
|
}
|
|
|
|
}(function($) {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var readmore = 'readmore',
|
|
|
|
defaults = {
|
|
|
|
speed: 100,
|
|
|
|
collapsedHeight: 200,
|
|
|
|
heightMargin: 16,
|
|
|
|
moreLink: '<a href="#">Read More</a>',
|
|
|
|
lessLink: '<a href="#">Close</a>',
|
|
|
|
embedCSS: true,
|
|
|
|
blockCSS: 'display: block; width: 100%;',
|
|
|
|
startOpen: false,
|
|
|
|
|
|
|
|
// callbacks
|
2018-12-15 05:42:52 +00:00
|
|
|
blockProcessed: function() {},
|
|
|
|
beforeToggle: function() {},
|
|
|
|
afterToggle: function() {}
|
2018-07-09 02:40:30 +00:00
|
|
|
},
|
|
|
|
cssEmbedded = {},
|
|
|
|
uniqueIdCounter = 0;
|
|
|
|
|
|
|
|
function debounce(func, wait, immediate) {
|
|
|
|
var timeout;
|
|
|
|
|
|
|
|
return function() {
|
|
|
|
var context = this, args = arguments;
|
|
|
|
var later = function() {
|
|
|
|
timeout = null;
|
|
|
|
if (! immediate) {
|
|
|
|
func.apply(context, args);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var callNow = immediate && !timeout;
|
|
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
timeout = setTimeout(later, wait);
|
|
|
|
|
|
|
|
if (callNow) {
|
|
|
|
func.apply(context, args);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function uniqueId(prefix) {
|
|
|
|
var id = ++uniqueIdCounter;
|
|
|
|
|
2018-12-15 05:42:52 +00:00
|
|
|
return String(prefix === null ? 'rmjs-' : prefix) + id;
|
2018-07-09 02:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function setBoxHeights(element) {
|
|
|
|
var el = element.clone().css({
|
|
|
|
height: 'auto',
|
|
|
|
width: element.width(),
|
|
|
|
maxHeight: 'none',
|
|
|
|
overflow: 'hidden'
|
|
|
|
}).insertAfter(element),
|
|
|
|
expandedHeight = el.outerHeight(),
|
|
|
|
cssMaxHeight = parseInt(el.css({maxHeight: ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10),
|
|
|
|
defaultHeight = element.data('defaultHeight');
|
|
|
|
|
|
|
|
el.remove();
|
|
|
|
|
|
|
|
var collapsedHeight = cssMaxHeight || element.data('collapsedHeight') || defaultHeight;
|
|
|
|
|
|
|
|
// Store our measurements.
|
|
|
|
element.data({
|
|
|
|
expandedHeight: expandedHeight,
|
|
|
|
maxHeight: cssMaxHeight,
|
|
|
|
collapsedHeight: collapsedHeight
|
|
|
|
})
|
|
|
|
// and disable any `max-height` property set in CSS
|
|
|
|
.css({
|
|
|
|
maxHeight: 'none'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var resizeBoxes = debounce(function() {
|
|
|
|
$('[data-readmore]').each(function() {
|
|
|
|
var current = $(this),
|
|
|
|
isExpanded = (current.attr('aria-expanded') === 'true');
|
|
|
|
|
|
|
|
setBoxHeights(current);
|
|
|
|
|
|
|
|
current.css({
|
|
|
|
height: current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
function embedCSS(options) {
|
|
|
|
if (! cssEmbedded[options.selector]) {
|
|
|
|
var styles = ' ';
|
|
|
|
|
|
|
|
if (options.embedCSS && options.blockCSS !== '') {
|
|
|
|
styles += options.selector + ' + [data-readmore-toggle], ' +
|
|
|
|
options.selector + '[data-readmore]{' +
|
|
|
|
options.blockCSS +
|
|
|
|
'}';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Include the transition CSS even if embedCSS is false
|
|
|
|
styles += options.selector + '[data-readmore]{' +
|
|
|
|
'transition: height ' + options.speed + 'ms;' +
|
|
|
|
'overflow: hidden;' +
|
|
|
|
'}';
|
|
|
|
|
|
|
|
(function(d, u) {
|
|
|
|
var css = d.createElement('style');
|
|
|
|
css.type = 'text/css';
|
|
|
|
|
|
|
|
if (css.styleSheet) {
|
|
|
|
css.styleSheet.cssText = u;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
css.appendChild(d.createTextNode(u));
|
|
|
|
}
|
|
|
|
|
|
|
|
d.getElementsByTagName('head')[0].appendChild(css);
|
|
|
|
}(document, styles));
|
|
|
|
|
|
|
|
cssEmbedded[options.selector] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function Readmore(element, options) {
|
|
|
|
this.element = element;
|
|
|
|
|
|
|
|
this.options = $.extend({}, defaults, options);
|
|
|
|
|
|
|
|
embedCSS(this.options);
|
|
|
|
|
|
|
|
this._defaults = defaults;
|
|
|
|
this._name = readmore;
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
|
|
|
|
// IE8 chokes on `window.addEventListener`, so need to test for support.
|
|
|
|
if (window.addEventListener) {
|
|
|
|
// Need to resize boxes when the page has fully loaded.
|
|
|
|
window.addEventListener('load', resizeBoxes);
|
|
|
|
window.addEventListener('resize', resizeBoxes);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
window.attachEvent('load', resizeBoxes);
|
|
|
|
window.attachEvent('resize', resizeBoxes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Readmore.prototype = {
|
|
|
|
init: function() {
|
|
|
|
var current = $(this.element);
|
|
|
|
|
|
|
|
current.data({
|
|
|
|
defaultHeight: this.options.collapsedHeight,
|
|
|
|
heightMargin: this.options.heightMargin
|
|
|
|
});
|
|
|
|
|
|
|
|
setBoxHeights(current);
|
|
|
|
|
|
|
|
var collapsedHeight = current.data('collapsedHeight'),
|
|
|
|
heightMargin = current.data('heightMargin');
|
|
|
|
|
|
|
|
if (current.outerHeight(true) <= collapsedHeight + heightMargin) {
|
|
|
|
// The block is shorter than the limit, so there's no need to truncate it.
|
2018-12-15 05:42:52 +00:00
|
|
|
if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
|
|
|
|
this.options.blockProcessed(current, false);
|
|
|
|
}
|
2018-07-09 02:40:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var id = current.attr('id') || uniqueId(),
|
|
|
|
useLink = this.options.startOpen ? this.options.lessLink : this.options.moreLink;
|
|
|
|
|
|
|
|
current.attr({
|
|
|
|
'data-readmore': '',
|
|
|
|
'aria-expanded': this.options.startOpen,
|
|
|
|
'id': id
|
|
|
|
});
|
|
|
|
|
|
|
|
current.after($(useLink)
|
|
|
|
.on('click', (function(_this) {
|
|
|
|
return function(event) {
|
|
|
|
_this.toggle(this, current[0], event);
|
|
|
|
};
|
|
|
|
})(this))
|
|
|
|
.attr({
|
2018-12-15 05:42:52 +00:00
|
|
|
'data-readmore-toggle': id,
|
2018-07-09 02:40:30 +00:00
|
|
|
'aria-controls': id
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (! this.options.startOpen) {
|
|
|
|
current.css({
|
|
|
|
height: collapsedHeight
|
|
|
|
});
|
|
|
|
}
|
2018-12-15 05:42:52 +00:00
|
|
|
|
|
|
|
if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
|
|
|
|
this.options.blockProcessed(current, true);
|
|
|
|
}
|
2018-07-09 02:40:30 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggle: function(trigger, element, event) {
|
|
|
|
if (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! trigger) {
|
2018-12-15 05:42:52 +00:00
|
|
|
trigger = $('[aria-controls="' + this.element.id + '"]')[0];
|
2018-07-09 02:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (! element) {
|
2018-12-15 05:42:52 +00:00
|
|
|
element = this.element;
|
2018-07-09 02:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var $element = $(element),
|
|
|
|
newHeight = '',
|
|
|
|
newLink = '',
|
|
|
|
expanded = false,
|
|
|
|
collapsedHeight = $element.data('collapsedHeight');
|
|
|
|
|
|
|
|
if ($element.height() <= collapsedHeight) {
|
|
|
|
newHeight = $element.data('expandedHeight') + 'px';
|
|
|
|
newLink = 'lessLink';
|
|
|
|
expanded = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newHeight = collapsedHeight;
|
|
|
|
newLink = 'moreLink';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire beforeToggle callback
|
|
|
|
// Since we determined the new "expanded" state above we're now out of sync
|
|
|
|
// with our true current state, so we need to flip the value of `expanded`
|
2018-12-15 05:42:52 +00:00
|
|
|
if (this.options.beforeToggle && typeof this.options.beforeToggle === 'function') {
|
|
|
|
this.options.beforeToggle(trigger, $element, ! expanded);
|
|
|
|
}
|
2018-07-09 02:40:30 +00:00
|
|
|
|
|
|
|
$element.css({'height': newHeight});
|
|
|
|
|
|
|
|
// Fire afterToggle callback
|
|
|
|
$element.on('transitionend', (function(_this) {
|
|
|
|
return function() {
|
2018-12-15 05:42:52 +00:00
|
|
|
if (_this.options.afterToggle && typeof _this.options.afterToggle === 'function') {
|
|
|
|
_this.options.afterToggle(trigger, $element, expanded);
|
|
|
|
}
|
2018-07-09 02:40:30 +00:00
|
|
|
|
|
|
|
$(this).attr({
|
|
|
|
'aria-expanded': expanded
|
|
|
|
}).off('transitionend');
|
2018-12-15 05:42:52 +00:00
|
|
|
};
|
2018-07-09 02:40:30 +00:00
|
|
|
})(this));
|
|
|
|
|
|
|
|
$(trigger).replaceWith($(this.options[newLink])
|
|
|
|
.on('click', (function(_this) {
|
|
|
|
return function(event) {
|
|
|
|
_this.toggle(this, element, event);
|
|
|
|
};
|
|
|
|
})(this))
|
|
|
|
.attr({
|
2018-12-15 05:42:52 +00:00
|
|
|
'data-readmore-toggle': $element.attr('id'),
|
2018-07-09 02:40:30 +00:00
|
|
|
'aria-controls': $element.attr('id')
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
$(this.element).each(function() {
|
|
|
|
var current = $(this);
|
|
|
|
|
|
|
|
current.attr({
|
|
|
|
'data-readmore': null,
|
|
|
|
'aria-expanded': null
|
|
|
|
})
|
|
|
|
.css({
|
|
|
|
maxHeight: '',
|
|
|
|
height: ''
|
|
|
|
})
|
|
|
|
.next('[data-readmore-toggle]')
|
|
|
|
.remove();
|
|
|
|
|
|
|
|
current.removeData();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
$.fn.readmore = function(options) {
|
|
|
|
var args = arguments,
|
|
|
|
selector = this.selector;
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
if (typeof options === 'object') {
|
|
|
|
return this.each(function() {
|
|
|
|
if ($.data(this, 'plugin_' + readmore)) {
|
|
|
|
var instance = $.data(this, 'plugin_' + readmore);
|
|
|
|
instance.destroy.apply(instance);
|
|
|
|
}
|
|
|
|
|
|
|
|
options.selector = selector;
|
|
|
|
|
|
|
|
$.data(this, 'plugin_' + readmore, new Readmore(this, options));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
|
|
|
|
return this.each(function () {
|
|
|
|
var instance = $.data(this, 'plugin_' + readmore);
|
|
|
|
if (instance instanceof Readmore && typeof instance[options] === 'function') {
|
|
|
|
instance[options].apply(instance, Array.prototype.slice.call(args, 1));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
}));
|
|
|
|
|