123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- /*!
- * @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
- beforeToggle: function(){},
- afterToggle: function(){}
- },
- 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;
- return String(prefix == null ? 'rmjs-' : prefix) + id;
- }
- 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.
- 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({
- 'data-readmore-toggle': '',
- 'aria-controls': id
- }));
- if (! this.options.startOpen) {
- current.css({
- height: collapsedHeight
- });
- }
- }
- },
- toggle: function(trigger, element, event) {
- if (event) {
- event.preventDefault();
- }
- if (! trigger) {
- trigger = $('[aria-controls="' + _this.element.id + '"]')[0];
- }
- if (! element) {
- element = _this.element;
- }
- 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`
- this.options.beforeToggle(trigger, $element, ! expanded);
- $element.css({'height': newHeight});
- // Fire afterToggle callback
- $element.on('transitionend', (function(_this) {
- return function() {
- _this.options.afterToggle(trigger, $element, expanded);
- $(this).attr({
- 'aria-expanded': expanded
- }).off('transitionend');
- }
- })(this));
- $(trigger).replaceWith($(this.options[newLink])
- .on('click', (function(_this) {
- return function(event) {
- _this.toggle(this, element, event);
- };
- })(this))
- .attr({
- 'data-readmore-toggle': '',
- '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));
- }
- });
- }
- };
- }));
|