From f6879f02f55d6ff3841b9470cfc7048ff82ce2a6 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 08:25:18 -0800 Subject: [PATCH 01/30] Replace jQuery.animate() with CSS3 transition --- readmore.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/readmore.js b/readmore.js index 1229fb4..4bb122c 100644 --- a/readmore.js +++ b/readmore.js @@ -37,7 +37,13 @@ delete(this.options.maxHeight); if(this.options.embedCSS && ! cssEmbedded) { - var styles = '.readmore-js-toggle, .readmore-js-section { ' + this.options.sectionCSS + ' } .readmore-js-section { overflow: hidden; }'; + var styles = '.readmore-js-toggle, .readmore-js-section { ' + this.options.sectionCSS + ' } .readmore-js-section { ' + + 'transition: height ' + this.options.speed + 'ms;' + + '-webkit-transform: translate3d(0,0,0);' + + '-ms-transform: translate3d(0,0,0);' + + 'transform: translate3d(0,0,0);' + + 'overflow: hidden;' + + '}'; (function(d,u) { var css=d.createElement('style'); @@ -122,15 +128,16 @@ // Fire beforeToggle callback $this.options.beforeToggle(trigger, element, expanded); - $(element).animate({'height': newHeight}, {duration: $this.options.speed, complete: function() { - // Fire afterToggle callback - $this.options.afterToggle(trigger, element, expanded); + $(element).css({"height": newHeight}); - $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).addClass('readmore-js-toggle')); + // Fire afterToggle callback + $(element).on('transitionend', function(e) { + $this.options.afterToggle(trigger, element, expanded); - $(this).removeClass($this.options.collapsedClass + ' ' + $this.options.expandedClass).addClass(sectionClass); - } + $(this).removeClass($this.options.collapsedClass + ' ' + $this.options.expandedClass).addClass(sectionClass); }); + + $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).addClass('readmore-js-toggle')); }, setBoxHeight: function(element) { From 9306da3078d8a0a919414b727133d9c6f2655f62 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 08:33:17 -0800 Subject: [PATCH 02/30] Make plugin play nice with multiple instances Modified plugin initialization and style injection to work with multiple Readmore instances. The 'speed' option can/should vary from instance to instance. This change allows that variation, rather than only using the first speed setting from the first instance. --- readmore.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/readmore.js b/readmore.js index 4bb122c..58630c8 100644 --- a/readmore.js +++ b/readmore.js @@ -24,7 +24,7 @@ beforeToggle: function(){}, afterToggle: function(){} }, - cssEmbedded = false; + cssEmbedded = {}; function Readmore( element, options ) { this.element = element; @@ -36,14 +36,15 @@ delete(this.options.maxHeight); - if(this.options.embedCSS && ! cssEmbedded) { - var styles = '.readmore-js-toggle, .readmore-js-section { ' + this.options.sectionCSS + ' } .readmore-js-section { ' + - 'transition: height ' + this.options.speed + 'ms;' + - '-webkit-transform: translate3d(0,0,0);' + - '-ms-transform: translate3d(0,0,0);' + - 'transform: translate3d(0,0,0);' + - 'overflow: hidden;' + - '}'; + if(this.options.embedCSS && (! cssEmbedded[this.options.selector])) { + var styles = ' ' + this.options.selector + ' + .readmore-js-toggle, ' + this.options.selector + '.readmore-js-section{' + this.options.sectionCSS + '}' + + this.options.selector + '.readmore-js-section{' + + 'transition: height ' + this.options.speed + 'ms;' + + '-webkit-transform: translate3d(0,0,0);' + + '-ms-transform: translate3d(0,0,0);' + + 'transform: translate3d(0,0,0);' + + 'overflow: hidden;' + + '}'; (function(d,u) { var css=d.createElement('style'); @@ -57,7 +58,7 @@ d.getElementsByTagName('head')[0].appendChild(css); }(document, styles)); - cssEmbedded = true; + cssEmbedded[this.options.selector] = true; } this._defaults = defaults; @@ -176,8 +177,9 @@ } }; - $.fn[readmore] = function( options ) { - var args = arguments; + $.fn.readmore = function( options ) { + var args = arguments, + selector = this.selector; if (options === undefined || typeof options === 'object') { return this.each(function () { if ($.data(this, 'plugin_' + readmore)) { @@ -185,6 +187,8 @@ instance['destroy'].apply(instance); } + options['selector'] = selector; + $.data(this, 'plugin_' + readmore, new Readmore( this, options )); }); } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { From 52f91deaada1c24bd5d74e6a89e53119cbc25ee0 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 08:48:39 -0800 Subject: [PATCH 03/30] Embed transition CSS regardless of value of `embedCSS` --- demo.html | 3 ++- readmore.js | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/demo.html b/demo.html index 1a90f11..33aab7c 100644 --- a/demo.html +++ b/demo.html @@ -175,7 +175,8 @@

This section is shorter than the Readmore minimum

} }); - $('article').readmore({maxHeight: 240}); + $('article').readmore({maxHeight: 240, speed: 250}); + diff --git a/readmore.js b/readmore.js index 58630c8..ca6f9ca 100644 --- a/readmore.js +++ b/readmore.js @@ -36,15 +36,22 @@ delete(this.options.maxHeight); - if(this.options.embedCSS && (! cssEmbedded[this.options.selector])) { - var styles = ' ' + this.options.selector + ' + .readmore-js-toggle, ' + this.options.selector + '.readmore-js-section{' + this.options.sectionCSS + '}' + - this.options.selector + '.readmore-js-section{' + - 'transition: height ' + this.options.speed + 'ms;' + - '-webkit-transform: translate3d(0,0,0);' + - '-ms-transform: translate3d(0,0,0);' + - 'transform: translate3d(0,0,0);' + - 'overflow: hidden;' + - '}'; + if(! cssEmbedded[this.options.selector]) { + var styles = ' '; + + // Include sectionCSS if embedCSS is true + if(this.options.embedCSS) { + styles += this.options.selector + ' + .readmore-js-toggle, ' + this.options.selector + '.readmore-js-section{' + this.options.sectionCSS + '}' + } + + // Include the transition CSS even if embedCSS is false + styles += this.options.selector + '.readmore-js-section{' + + 'transition: height ' + this.options.speed + 'ms;' + + '-webkit-transform: translate3d(0,0,0);' + + '-ms-transform: translate3d(0,0,0);' + + 'transform: translate3d(0,0,0);' + + 'overflow: hidden;' + + '}'; (function(d,u) { var css=d.createElement('style'); @@ -53,7 +60,7 @@ css.styleSheet.cssText = u; } else { - css.appendChild(d.createTextNode(u)); + css.appendChild(d.createTextNode(u)); } d.getElementsByTagName('head')[0].appendChild(css); }(document, styles)); From 3da0aef83fc823e8008c4a08d8630eea637081e0 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 11:47:05 -0800 Subject: [PATCH 04/30] Remove CSS transform properties, seems excessive and unnecessary --- readmore.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/readmore.js b/readmore.js index ca6f9ca..15803a2 100644 --- a/readmore.js +++ b/readmore.js @@ -47,9 +47,6 @@ // Include the transition CSS even if embedCSS is false styles += this.options.selector + '.readmore-js-section{' + 'transition: height ' + this.options.speed + 'ms;' + - '-webkit-transform: translate3d(0,0,0);' + - '-ms-transform: translate3d(0,0,0);' + - 'transform: translate3d(0,0,0);' + 'overflow: hidden;' + '}'; From 4cdf9a4ba6f5b41361072a436e77add54c5e39f6 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 11:47:38 -0800 Subject: [PATCH 05/30] Debounce resizeBoxes method Closes #37 and #55 --- readmore.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/readmore.js b/readmore.js index 15803a2..549a576 100644 --- a/readmore.js +++ b/readmore.js @@ -3,6 +3,8 @@ * Author: @jed_foster * Project home: jedfoster.github.io/Readmore.js * Licensed under the MIT license + * + * Debounce function from http://davidwalsh.name/javascript-debounce-function */ ;(function($) { @@ -26,6 +28,21 @@ }, cssEmbedded = {}; + 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 Readmore( element, options ) { this.element = element; @@ -103,7 +120,7 @@ } }); - $(window).on('resize', function(event) { + window.addEventListener('resize', function(event) { $this.resizeBoxes(); }); }, @@ -154,7 +171,7 @@ element.data('expandedHeight', height); }, - resizeBoxes: function() { + resizeBoxes: debounce(function() { var $this = this; $('.readmore-js-section').each(function() { @@ -166,7 +183,7 @@ current.css('height', current.data('expandedHeight')); } }); - }, + }, 100), destroy: function() { var $this = this; From bc105c6f96f8a4617e3397855955ff512d3f196e Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 17:31:33 -0800 Subject: [PATCH 06/30] Use data-* and aria-* selectors instead of classes Replaced `.readmore-js-section` with `data-readmore-js-section` and `.readmore-js-toggle` with `data-readmore-js-toggle`. Replaced `expandedClass` and `collapsedClass` with `aria-expanded="true|false"`. --- readmore.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/readmore.js b/readmore.js index 549a576..33b2c61 100644 --- a/readmore.js +++ b/readmore.js @@ -19,8 +19,6 @@ embedCSS: true, sectionCSS: 'display: block; width: 100%;', startOpen: false, - expandedClass: 'readmore-js-expanded', - collapsedClass: 'readmore-js-collapsed', // callbacks beforeToggle: function(){}, @@ -58,11 +56,11 @@ // Include sectionCSS if embedCSS is true if(this.options.embedCSS) { - styles += this.options.selector + ' + .readmore-js-toggle, ' + this.options.selector + '.readmore-js-section{' + this.options.sectionCSS + '}' + styles += this.options.selector + ' + [readmore-js-toggle], ' + this.options.selector + '[data-readmore-js-section]{' + this.options.sectionCSS + '}' } // Include the transition CSS even if embedCSS is false - styles += this.options.selector + '.readmore-js-section{' + + styles += this.options.selector + '[data-readmore-js-section]{' + 'transition: height ' + this.options.speed + 'ms;' + 'overflow: hidden;' + '}'; @@ -109,10 +107,11 @@ return true; } else { - current.addClass('readmore-js-section ' + $this.options.collapsedClass).data('collapsedHeight', maxHeight); var useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - current.after($(useLink).on('click', function(event) { $this.toggleSlider(this, current, event) }).addClass('readmore-js-toggle')); + current.attr({'data-readmore-js-section': '', 'aria-expanded': false}).data('collapsedHeight', maxHeight); + + current.after($(useLink).on('click', function(event) { $this.toggleSlider(this, current, event) }).attr('data-readmore-js-toggle', '')); if(!$this.options.startOpen) { current.css({height: maxHeight}); @@ -130,36 +129,36 @@ event.preventDefault(); var $this = this, - newHeight = newLink = sectionClass = '', + $element = $(element), + $trigger = $(trigger), + newHeight = newLink = '', expanded = false, - collapsedHeight = $(element).data('collapsedHeight'); + collapsedHeight = $element.data('collapsedHeight'); - if ($(element).height() <= collapsedHeight) { - newHeight = $(element).data('expandedHeight') + 'px'; + if ($element.height() <= collapsedHeight) { + newHeight = $element.data('expandedHeight') + 'px'; newLink = 'lessLink'; expanded = true; - sectionClass = $this.options.expandedClass; } else { newHeight = collapsedHeight; newLink = 'moreLink'; - sectionClass = $this.options.collapsedClass; } // Fire beforeToggle callback $this.options.beforeToggle(trigger, element, expanded); - $(element).css({"height": newHeight}); + $element.css({'height': newHeight}); // Fire afterToggle callback - $(element).on('transitionend', function(e) { + $element.on('transitionend', function(e) { $this.options.afterToggle(trigger, element, expanded); - $(this).removeClass($this.options.collapsedClass + ' ' + $this.options.expandedClass).addClass(sectionClass); + $(this).attr('aria-expanded', expanded); }); - $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).addClass('readmore-js-toggle')); + $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).attr('data-readmore-js-toggle', '')); }, setBoxHeight: function(element) { @@ -174,12 +173,12 @@ resizeBoxes: debounce(function() { var $this = this; - $('.readmore-js-section').each(function() { + $('[data-readmore-js-section]').each(function() { var current = $(this); $this.setBoxHeight(current); - if(current.height() > current.data('expandedHeight') || (current.hasClass($this.options.expandedClass) && current.height() < current.data('expandedHeight')) ) { + if(current.height() > current.data('expandedHeight') || (current.attr('aria-expanded') && current.height() < current.data('expandedHeight')) ) { current.css('height', current.data('expandedHeight')); } }); @@ -191,7 +190,7 @@ $(this.element).each(function() { var current = $(this); - current.removeClass('readmore-js-section ' + $this.options.collapsedClass + ' ' + $this.options.expandedClass).css({'max-height': '', 'height': 'auto'}).next('.readmore-js-toggle').remove(); + current.attr({'data-readmore-js-section': null, 'aria-expanded': null }).css({'max-height': '', 'height': ''}).next('[data-readmore-js-toggle]').remove(); current.removeData(); }); From df44737d6bb9de6774e6c4ac6474875153eb4617 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 17:34:20 -0800 Subject: [PATCH 07/30] Set IDs on sections and use aria-controls on toggles --- readmore.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/readmore.js b/readmore.js index 33b2c61..b533c10 100644 --- a/readmore.js +++ b/readmore.js @@ -24,7 +24,8 @@ beforeToggle: function(){}, afterToggle: function(){} }, - cssEmbedded = {}; + cssEmbedded = {}, + uniqueIdCounter = 0; function debounce(func, wait, immediate) { var timeout; @@ -41,6 +42,11 @@ }; } + function uniqueId(prefix) { + var id = ++uniqueIdCounter; + return String(prefix == null ? 'readmore-js-' : prefix) + id; + } + function Readmore( element, options ) { this.element = element; @@ -107,11 +113,12 @@ return true; } else { + var id = current.attr('id') || uniqueId(), + useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - var useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - current.attr({'data-readmore-js-section': '', 'aria-expanded': false}).data('collapsedHeight', maxHeight); + current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}).data('collapsedHeight', maxHeight); - current.after($(useLink).on('click', function(event) { $this.toggleSlider(this, current, event) }).attr('data-readmore-js-toggle', '')); + current.after($(useLink).on('click', function(event) { $this.toggleSlider(this, current, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); if(!$this.options.startOpen) { current.css({height: maxHeight}); @@ -158,7 +165,7 @@ $(this).attr('aria-expanded', expanded); }); - $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).attr('data-readmore-js-toggle', '')); + $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, setBoxHeight: function(element) { From 1659db32d313d1b7f9a55a95593d3146b72cdf99 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Wed, 31 Dec 2014 17:44:08 -0800 Subject: [PATCH 08/30] Change `toggleSlider` to just `toggle` --- readmore.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readmore.js b/readmore.js index b533c10..3f26170 100644 --- a/readmore.js +++ b/readmore.js @@ -118,7 +118,7 @@ current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}).data('collapsedHeight', maxHeight); - current.after($(useLink).on('click', function(event) { $this.toggleSlider(this, current, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); + current.after($(useLink).on('click', function(event) { $this.toggle(this, current, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); if(!$this.options.startOpen) { current.css({height: maxHeight}); @@ -131,9 +131,9 @@ }); }, - toggleSlider: function(trigger, element, event) { event.preventDefault(); + toggle: function(trigger, element, event) { var $this = this, $element = $(element), @@ -165,7 +165,7 @@ $(this).attr('aria-expanded', expanded); }); - $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggleSlider(this, element, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); + $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, setBoxHeight: function(element) { From fdb3d29dcb9d761c1646bfc386d40e9d43935abd Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Thu, 1 Jan 2015 06:26:13 -0800 Subject: [PATCH 09/30] Allow `toggle` to be called programmatically Can now do something like: ```javascript $('article:nth-of-type(3)').readmore('toggle'); ``` --- readmore.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/readmore.js b/readmore.js index 3f26170..65b13d1 100644 --- a/readmore.js +++ b/readmore.js @@ -131,9 +131,18 @@ }); }, - { - event.preventDefault(); toggle: function(trigger, element, event) { + if(event) { + event.preventDefault(); + } + + if(! trigger) { + trigger = $('[aria-controls="' + this.element.id + '"]')[0]; + } + + if(! element) { + element = this.element; + } var $this = this, $element = $(element), From 5a1692ee4900727f1ede73ac4528b22d378aea95 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Thu, 1 Jan 2015 07:42:23 -0800 Subject: [PATCH 10/30] Wrap call to `init()` in a load event listener This _seems_ to work quite well with images (and presumably with other variable height block-level content.) However, all content is visible while loading. This could be mitigated by calling `resizeBoxes` inside the load event callback, instead of `init`, but then you force the browser through two rounds of repainting. I'm going to leave it as is for now. --- readmore.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/readmore.js b/readmore.js index 65b13d1..61e2f53 100644 --- a/readmore.js +++ b/readmore.js @@ -48,6 +48,8 @@ } function Readmore( element, options ) { + var $this = this; + this.element = element; this.options = $.extend( {}, defaults, options); @@ -89,7 +91,9 @@ this._defaults = defaults; this._name = readmore; - this.init(); + window.addEventListener('load', function(event) { + $this.init(); + }); } Readmore.prototype = { From 7a1ec5d2dd541c48da8e1dd4fb235336acf19f47 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Thu, 1 Jan 2015 07:43:19 -0800 Subject: [PATCH 11/30] Code style cleanup --- readmore.js | 77 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/readmore.js b/readmore.js index 61e2f53..8ef341f 100644 --- a/readmore.js +++ b/readmore.js @@ -7,7 +7,7 @@ * Debounce function from http://davidwalsh.name/javascript-debounce-function */ -;(function($) { +!(function($) { var readmore = 'readmore', defaults = { @@ -29,42 +29,51 @@ 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); + if (! immediate) { + func.apply(context, args); + } }; var callNow = immediate && !timeout; + clearTimeout(timeout); timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); + + if (callNow) { + func.apply(context, args); + } }; } function uniqueId(prefix) { var id = ++uniqueIdCounter; + return String(prefix == null ? 'readmore-js-' : prefix) + id; } - function Readmore( element, options ) { + function Readmore(element, options) { var $this = this; this.element = element; - this.options = $.extend( {}, defaults, options); + this.options = $.extend({}, defaults, options); - $(this.element).data('max-height', this.options.maxHeight); - $(this.element).data('height-margin', this.options.heightMargin); + $(this.element).data({ + 'max-height': this.options.maxHeight, + 'height-margin': this.options.heightMargin + }); delete(this.options.maxHeight); - if(! cssEmbedded[this.options.selector]) { + if (! cssEmbedded[this.options.selector]) { var styles = ' '; - // Include sectionCSS if embedCSS is true - if(this.options.embedCSS) { - styles += this.options.selector + ' + [readmore-js-toggle], ' + this.options.selector + '[data-readmore-js-section]{' + this.options.sectionCSS + '}' + if (this.options.embedCSS) { + styles += this.options.selector + ' + [data-readmore-js-toggle], ' + this.options.selector + '[data-readmore-js-section]{' + this.options.sectionCSS + '}' } // Include the transition CSS even if embedCSS is false @@ -73,15 +82,17 @@ 'overflow: hidden;' + '}'; - (function(d,u) { - var css=d.createElement('style'); + (function(d, u) { + var css = d.createElement('style'); css.type = 'text/css'; - if(css.styleSheet) { - css.styleSheet.cssText = u; + + if (css.styleSheet) { + css.styleSheet.cssText = u; } else { css.appendChild(d.createTextNode(u)); } + d.getElementsByTagName('head')[0].appendChild(css); }(document, styles)); @@ -96,8 +107,8 @@ }); } - Readmore.prototype = { + Readmore.prototype = { init: function() { var $this = this; @@ -106,13 +117,13 @@ maxHeight = (parseInt(current.css('max-height').replace(/[^-\d\.]/g, ''), 10) > current.data('max-height')) ? parseInt(current.css('max-height').replace(/[^-\d\.]/g, ''), 10) : current.data('max-height'), heightMargin = current.data('height-margin'); - if(current.css('max-height') != 'none') { + if (current.css('max-height') != 'none') { current.css('max-height', 'none'); } $this.setBoxHeight(current); - if(current.outerHeight(true) <= maxHeight + heightMargin) { + if (current.outerHeight(true) <= maxHeight + heightMargin) { // The block is shorter than the limit, so there's no need to truncate it. return true; } @@ -122,9 +133,9 @@ current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}).data('collapsedHeight', maxHeight); - current.after($(useLink).on('click', function(event) { $this.toggle(this, current, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); + current.after($(useLink).on('click', function(event) { $this.toggle(this, current, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); - if(!$this.options.startOpen) { + if (! $this.options.startOpen) { current.css({height: maxHeight}); } } @@ -136,21 +147,20 @@ }, toggle: function(trigger, element, event) { - if(event) { + if (event) { event.preventDefault(); } - if(! trigger) { + if (! trigger) { trigger = $('[aria-controls="' + this.element.id + '"]')[0]; } - if(! element) { + if (! element) { element = this.element; } var $this = this, $element = $(element), - $trigger = $(trigger), newHeight = newLink = '', expanded = false, collapsedHeight = $element.data('collapsedHeight'); @@ -160,7 +170,6 @@ newLink = 'lessLink'; expanded = true; } - else { newHeight = collapsedHeight; newLink = 'moreLink'; @@ -178,7 +187,7 @@ $(this).attr('aria-expanded', expanded); }); - $trigger.replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event) }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); + $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, setBoxHeight: function(element) { @@ -198,7 +207,7 @@ $this.setBoxHeight(current); - if(current.height() > current.data('expandedHeight') || (current.attr('aria-expanded') && current.height() < current.data('expandedHeight')) ) { + if (current.height() > current.data('expandedHeight') || (current.attr('aria-expanded') && current.height() < current.data('expandedHeight')) ) { current.css('height', current.data('expandedHeight')); } }); @@ -217,11 +226,12 @@ } }; - $.fn.readmore = function( options ) { + $.fn.readmore = function(options) { var args = arguments, selector = this.selector; + if (options === undefined || typeof options === 'object') { - return this.each(function () { + return this.each(function() { if ($.data(this, 'plugin_' + readmore)) { var instance = $.data(this, 'plugin_' + readmore); instance['destroy'].apply(instance); @@ -229,15 +239,18 @@ options['selector'] = selector; - $.data(this, 'plugin_' + readmore, new Readmore( this, options )); + $.data(this, 'plugin_' + readmore, new Readmore(this, options)); }); - } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { + } + 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 ) ); + instance[options].apply(instance, Array.prototype.slice.call(args, 1)); } }); } }; + })(jQuery); + From d43d3d535dc6d06a1381baa0613997c5f4860977 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Thu, 1 Jan 2015 20:58:35 -0800 Subject: [PATCH 12/30] Remove 'transitionend' event listener when callback completes Prevents multiple `afterToggle` callback calls. --- readmore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readmore.js b/readmore.js index 8ef341f..4ae4f83 100644 --- a/readmore.js +++ b/readmore.js @@ -184,7 +184,7 @@ $element.on('transitionend', function(e) { $this.options.afterToggle(trigger, element, expanded); - $(this).attr('aria-expanded', expanded); + $(this).attr('aria-expanded', expanded).off('transitionend'); }); $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); From cf2f3e0aa1a8f9bce7fac805122069772dc65882 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Thu, 1 Jan 2015 21:06:28 -0800 Subject: [PATCH 13/30] Pass the native element, instead of jQuery object --- readmore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readmore.js b/readmore.js index 4ae4f83..558d8c4 100644 --- a/readmore.js +++ b/readmore.js @@ -133,7 +133,7 @@ current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}).data('collapsedHeight', maxHeight); - current.after($(useLink).on('click', function(event) { $this.toggle(this, current, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); + current.after($(useLink).on('click', function(event) { $this.toggle(this, current[0], event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); if (! $this.options.startOpen) { current.css({height: maxHeight}); From 6dafab2fc19d4019ce1df5a43d53040396c7ccfc Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 10:17:58 -0800 Subject: [PATCH 14/30] Fix jQuery plugin code to allow zero arguments As originally written, it would blow up if `options` arg was undefined. --- readmore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readmore.js b/readmore.js index 558d8c4..b83cb4a 100644 --- a/readmore.js +++ b/readmore.js @@ -230,7 +230,9 @@ var args = arguments, selector = this.selector; - if (options === undefined || typeof options === 'object') { + options = options || {}; + + if (typeof options === 'object') { return this.each(function() { if ($.data(this, 'plugin_' + readmore)) { var instance = $.data(this, 'plugin_' + readmore); From 1616f0f9670ea31890b994aa6e9e0baf4dd346b7 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 14:40:22 -0800 Subject: [PATCH 15/30] Fix value of `expanded` as passed to `beforeToggle()` --- readmore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readmore.js b/readmore.js index b83cb4a..5702c1c 100644 --- a/readmore.js +++ b/readmore.js @@ -176,7 +176,9 @@ } // Fire beforeToggle callback - $this.options.beforeToggle(trigger, element, expanded); + // 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}); From 854c4c6ec08975435cad12f1380eb09b3e3d9878 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 15:00:50 -0800 Subject: [PATCH 16/30] Refactor height calculation and resizing @wip --- readmore.js | 65 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/readmore.js b/readmore.js index 5702c1c..ba3bf6d 100644 --- a/readmore.js +++ b/readmore.js @@ -12,7 +12,7 @@ var readmore = 'readmore', defaults = { speed: 100, - maxHeight: 200, + collapsedHeight: 200, heightMargin: 16, moreLink: 'Read More', lessLink: 'Close', @@ -63,12 +63,10 @@ this.options = $.extend({}, defaults, options); $(this.element).data({ - 'max-height': this.options.maxHeight, - 'height-margin': this.options.heightMargin + 'defaultCollapsedHeight': this.options.collapsedHeight, + 'heightMargin': this.options.heightMargin }); - delete(this.options.maxHeight); - if (! cssEmbedded[this.options.selector]) { var styles = ' '; @@ -113,17 +111,14 @@ var $this = this; $(this.element).each(function() { - var current = $(this), - maxHeight = (parseInt(current.css('max-height').replace(/[^-\d\.]/g, ''), 10) > current.data('max-height')) ? parseInt(current.css('max-height').replace(/[^-\d\.]/g, ''), 10) : current.data('max-height'), - heightMargin = current.data('height-margin'); + var current = $(this); - if (current.css('max-height') != 'none') { - current.css('max-height', 'none'); - } + $this.setBoxHeights(current); - $this.setBoxHeight(current); + var collapsedHeight = current.data('collapsedHeight'), + heightMargin = current.data('heightMargin'); - if (current.outerHeight(true) <= maxHeight + heightMargin) { + if (current.outerHeight(true) <= collapsedHeight + heightMargin) { // The block is shorter than the limit, so there's no need to truncate it. return true; } @@ -131,12 +126,12 @@ var id = current.attr('id') || uniqueId(), useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}).data('collapsedHeight', maxHeight); + current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}); current.after($(useLink).on('click', function(event) { $this.toggle(this, current[0], event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); if (! $this.options.startOpen) { - current.css({height: maxHeight}); + current.css({height: collapsedHeight}); } } }); @@ -192,26 +187,47 @@ $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, - setBoxHeight: function(element) { - var el = element.clone().css({'height': 'auto', 'width': element.width(), 'overflow': 'hidden'}).insertAfter(element), - height = el.outerHeight(true); + setBoxHeights: function(element) { + var el = element.clone().css({ + 'height': 'auto', + 'width': element.width(), + 'max-height': 'none', + 'overflow': 'hidden' + }).insertAfter(element), + expandedHeight = el.outerHeight(true), + cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10); el.remove(); - element.data('expandedHeight', height); + var collapsedHeight = element.data('collapsedHeight') || element.data('defaultCollapsedHeight'); + + if (!cssMaxHeight) { + collapsedHeight = element.data('defaultCollapsedHeight'); + } + else if (cssMaxHeight > collapsedHeight) { + collapsedHeight = cssMaxHeight; + } + + // Store our measurements. + element.data({ + 'expandedHeight': expandedHeight, + 'maxHeight': cssMaxHeight, + 'collapsedHeight': collapsedHeight + }) + // and disable any `max-height` property set in CSS + .css('max-height', 'none'); }, resizeBoxes: debounce(function() { var $this = this; $('[data-readmore-js-section]').each(function() { - var current = $(this); + var current = $(this), + isExpanded = (current.attr('aria-expanded') === 'true'); - $this.setBoxHeight(current); + $this.setBoxHeights(current); - if (current.height() > current.data('expandedHeight') || (current.attr('aria-expanded') && current.height() < current.data('expandedHeight')) ) { - current.css('height', current.data('expandedHeight')); - } + current.css('height', current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )); }); }, 100), @@ -228,6 +244,7 @@ } }; + $.fn.readmore = function(options) { var args = arguments, selector = this.selector; From 1e9459ccb8a9c34428e4d711c108556c3e53feb0 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 15:07:07 -0800 Subject: [PATCH 17/30] Move `setBoxHeights` & `resizeBoxes` out of Readmore object --- readmore.js | 91 ++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/readmore.js b/readmore.js index ba3bf6d..bdb345e 100644 --- a/readmore.js +++ b/readmore.js @@ -55,6 +55,49 @@ return String(prefix == null ? 'readmore-js-' : prefix) + id; } + var setBoxHeights = function(element) { + var el = element.clone().css({ + 'height': 'auto', + 'width': element.width(), + 'max-height': 'none', + 'overflow': 'hidden' + }).insertAfter(element), + expandedHeight = el.outerHeight(true), + cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10); + + el.remove(); + + var collapsedHeight = element.data('collapsedHeight') || element.data('defaultCollapsedHeight'); + + if (!cssMaxHeight) { + collapsedHeight = element.data('defaultCollapsedHeight'); + } + else if (cssMaxHeight > collapsedHeight) { + collapsedHeight = cssMaxHeight; + } + + // Store our measurements. + element.data({ + 'expandedHeight': expandedHeight, + 'maxHeight': cssMaxHeight, + 'collapsedHeight': collapsedHeight + }) + // and disable any `max-height` property set in CSS + .css('max-height', 'none'); + }; + + var resizeBoxes = debounce(function() { + $('[data-readmore-js-section]').each(function() { + var current = $(this), + isExpanded = (current.attr('aria-expanded') === 'true'); + + setBoxHeights(current); + + current.css('height', current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )); + }); + }, 100); + + function Readmore(element, options) { var $this = this; @@ -113,7 +156,7 @@ $(this.element).each(function() { var current = $(this); - $this.setBoxHeights(current); + setBoxHeights(current); var collapsedHeight = current.data('collapsedHeight'), heightMargin = current.data('heightMargin'); @@ -137,7 +180,7 @@ }); window.addEventListener('resize', function(event) { - $this.resizeBoxes(); + resizeBoxes(); }); }, @@ -187,50 +230,6 @@ $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, - setBoxHeights: function(element) { - var el = element.clone().css({ - 'height': 'auto', - 'width': element.width(), - 'max-height': 'none', - 'overflow': 'hidden' - }).insertAfter(element), - expandedHeight = el.outerHeight(true), - cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10); - - el.remove(); - - var collapsedHeight = element.data('collapsedHeight') || element.data('defaultCollapsedHeight'); - - if (!cssMaxHeight) { - collapsedHeight = element.data('defaultCollapsedHeight'); - } - else if (cssMaxHeight > collapsedHeight) { - collapsedHeight = cssMaxHeight; - } - - // Store our measurements. - element.data({ - 'expandedHeight': expandedHeight, - 'maxHeight': cssMaxHeight, - 'collapsedHeight': collapsedHeight - }) - // and disable any `max-height` property set in CSS - .css('max-height', 'none'); - }, - - resizeBoxes: debounce(function() { - var $this = this; - - $('[data-readmore-js-section]').each(function() { - var current = $(this), - isExpanded = (current.attr('aria-expanded') === 'true'); - - $this.setBoxHeights(current); - - current.css('height', current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )); - }); - }, 100), - destroy: function() { var $this = this; From 2df00602dac646566b63d73a42915c82232eb3de Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 15:09:24 -0800 Subject: [PATCH 18/30] Refactor stylesheet embedding as new function --- readmore.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/readmore.js b/readmore.js index bdb345e..54797c9 100644 --- a/readmore.js +++ b/readmore.js @@ -97,29 +97,17 @@ }); }, 100); - - function Readmore(element, options) { - var $this = this; - - this.element = element; - - this.options = $.extend({}, defaults, options); - - $(this.element).data({ - 'defaultCollapsedHeight': this.options.collapsedHeight, - 'heightMargin': this.options.heightMargin - }); - - if (! cssEmbedded[this.options.selector]) { + function embedCSS(options) { + if (! cssEmbedded[options.selector]) { var styles = ' '; - if (this.options.embedCSS) { - styles += this.options.selector + ' + [data-readmore-js-toggle], ' + this.options.selector + '[data-readmore-js-section]{' + this.options.sectionCSS + '}' + if (options.embedCSS) { + styles += options.selector + ' + [data-readmore-js-toggle], ' + options.selector + '[data-readmore-js-section]{' + options.sectionCSS + '}' } // Include the transition CSS even if embedCSS is false - styles += this.options.selector + '[data-readmore-js-section]{' + - 'transition: height ' + this.options.speed + 'ms;' + + styles += options.selector + '[data-readmore-js-section]{' + + 'transition: height ' + options.speed + 'ms;' + 'overflow: hidden;' + '}'; @@ -137,8 +125,24 @@ d.getElementsByTagName('head')[0].appendChild(css); }(document, styles)); - cssEmbedded[this.options.selector] = true; + cssEmbedded[options.selector] = true; } + } + + + function Readmore(element, options) { + var $this = this; + + this.element = element; + + this.options = $.extend({}, defaults, options); + + $(this.element).data({ + 'defaultCollapsedHeight': this.options.collapsedHeight, + 'heightMargin': this.options.heightMargin + }); + + embedCSS(this.options); this._defaults = defaults; this._name = readmore; From 3dd97600a1b14468952ebe4c83461d469bc43fcc Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 15:44:02 -0800 Subject: [PATCH 19/30] Break up some long lines and make JSHint happy --- readmore.js | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/readmore.js b/readmore.js index 54797c9..3a26cd9 100644 --- a/readmore.js +++ b/readmore.js @@ -7,7 +7,9 @@ * Debounce function from http://davidwalsh.name/javascript-debounce-function */ -!(function($) { +/* global jQuery */ + +(function($) { var readmore = 'readmore', defaults = { @@ -52,7 +54,7 @@ function uniqueId(prefix) { var id = ++uniqueIdCounter; - return String(prefix == null ? 'readmore-js-' : prefix) + id; + return String(prefix === null ? 'readmore-js-' : prefix) + id; } var setBoxHeights = function(element) { @@ -102,7 +104,10 @@ var styles = ' '; if (options.embedCSS) { - styles += options.selector + ' + [data-readmore-js-toggle], ' + options.selector + '[data-readmore-js-section]{' + options.sectionCSS + '}' + styles += options.selector + ' + [data-readmore-js-toggle], ' + + options.selector + '[data-readmore-js-section]{' + + options.sectionCSS + + '}'; } // Include the transition CSS even if embedCSS is false @@ -129,7 +134,6 @@ } } - function Readmore(element, options) { var $this = this; @@ -147,7 +151,7 @@ this._defaults = defaults; this._name = readmore; - window.addEventListener('load', function(event) { + window.addEventListener('load', function() { $this.init(); }); } @@ -175,7 +179,9 @@ current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}); - current.after($(useLink).on('click', function(event) { $this.toggle(this, current[0], event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': id})); + current.after($(useLink) + .on('click', function(event) { $this.toggle(this, current[0], event); }) + .attr({'data-readmore-js-toggle': '', 'aria-controls': id})); if (! $this.options.startOpen) { current.css({height: collapsedHeight}); @@ -183,7 +189,7 @@ } }); - window.addEventListener('resize', function(event) { + window.addEventListener('resize', function() { resizeBoxes(); }); }, @@ -203,7 +209,8 @@ var $this = this, $element = $(element), - newHeight = newLink = '', + newHeight = '', + newLink = '', expanded = false, collapsedHeight = $element.data('collapsedHeight'); @@ -225,22 +232,25 @@ $element.css({'height': newHeight}); // Fire afterToggle callback - $element.on('transitionend', function(e) { + $element.on('transitionend', function() { $this.options.afterToggle(trigger, element, expanded); $(this).attr('aria-expanded', expanded).off('transitionend'); }); - $(trigger).replaceWith($($this.options[newLink]).on('click', function(event) { $this.toggle(this, element, event); }).attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); + $(trigger).replaceWith($($this.options[newLink]) + .on('click', function(event) { $this.toggle(this, element, event); }) + .attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); }, destroy: function() { - var $this = this; - $(this.element).each(function() { var current = $(this); - current.attr({'data-readmore-js-section': null, 'aria-expanded': null }).css({'max-height': '', 'height': ''}).next('[data-readmore-js-toggle]').remove(); + current.attr({'data-readmore-js-section': null, 'aria-expanded': null }) + .css({'max-height': '', 'height': ''}) + .next('[data-readmore-js-toggle]') + .remove(); current.removeData(); }); @@ -258,10 +268,10 @@ return this.each(function() { if ($.data(this, 'plugin_' + readmore)) { var instance = $.data(this, 'plugin_' + readmore); - instance['destroy'].apply(instance); + instance.destroy.apply(instance); } - options['selector'] = selector; + options.selector = selector; $.data(this, 'plugin_' + readmore, new Readmore(this, options)); }); From a6445e714f696b7acccbdaa1bfdd360fbf52e20f Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 15:52:09 -0800 Subject: [PATCH 20/30] Add 'use strict' --- readmore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/readmore.js b/readmore.js index 3a26cd9..64717c1 100644 --- a/readmore.js +++ b/readmore.js @@ -10,6 +10,7 @@ /* global jQuery */ (function($) { + 'use strict'; var readmore = 'readmore', defaults = { From 622a38ae1812bf13f570c86f3f3977e6a98af357 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 16:49:06 -0800 Subject: [PATCH 21/30] Minor optimizations --- readmore.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/readmore.js b/readmore.js index 64717c1..6591baf 100644 --- a/readmore.js +++ b/readmore.js @@ -1,7 +1,9 @@ /*! + * @preserve + * * Readmore.js jQuery plugin * Author: @jed_foster - * Project home: jedfoster.github.io/Readmore.js + * Project home: http://jedfoster.github.io/Readmore.js * Licensed under the MIT license * * Debounce function from http://davidwalsh.name/javascript-debounce-function @@ -55,10 +57,10 @@ function uniqueId(prefix) { var id = ++uniqueIdCounter; - return String(prefix === null ? 'readmore-js-' : prefix) + id; + return String(prefix === null ? 'rmjs-' : prefix) + id; } - var setBoxHeights = function(element) { + function setBoxHeights(element) { var el = element.clone().css({ 'height': 'auto', 'width': element.width(), @@ -66,14 +68,15 @@ 'overflow': 'hidden' }).insertAfter(element), expandedHeight = el.outerHeight(true), - cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10); + cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10), + defaultHeight = element.data('defaultHeight'); el.remove(); - var collapsedHeight = element.data('collapsedHeight') || element.data('defaultCollapsedHeight'); + var collapsedHeight = element.data('collapsedHeight') || defaultHeight; if (!cssMaxHeight) { - collapsedHeight = element.data('defaultCollapsedHeight'); + collapsedHeight = defaultHeight; } else if (cssMaxHeight > collapsedHeight) { collapsedHeight = cssMaxHeight; @@ -87,10 +90,10 @@ }) // and disable any `max-height` property set in CSS .css('max-height', 'none'); - }; + } var resizeBoxes = debounce(function() { - $('[data-readmore-js-section]').each(function() { + $('[data-readmore]').each(function() { var current = $(this), isExpanded = (current.attr('aria-expanded') === 'true'); @@ -104,15 +107,15 @@ if (! cssEmbedded[options.selector]) { var styles = ' '; - if (options.embedCSS) { - styles += options.selector + ' + [data-readmore-js-toggle], ' + - options.selector + '[data-readmore-js-section]{' + + if (options.embedCSS && options.sectionCSS !== '') { + styles += options.selector + ' + [data-readmore-toggle], ' + + options.selector + '[data-readmore]{' + options.sectionCSS + '}'; } // Include the transition CSS even if embedCSS is false - styles += options.selector + '[data-readmore-js-section]{' + + styles += options.selector + '[data-readmore]{' + 'transition: height ' + options.speed + 'ms;' + 'overflow: hidden;' + '}'; @@ -143,7 +146,7 @@ this.options = $.extend({}, defaults, options); $(this.element).data({ - 'defaultCollapsedHeight': this.options.collapsedHeight, + 'defaultHeight': this.options.collapsedHeight, 'heightMargin': this.options.heightMargin }); @@ -178,11 +181,11 @@ var id = current.attr('id') || uniqueId(), useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - current.attr({'data-readmore-js-section': '', 'aria-expanded': false, 'id': id}); + current.attr({'data-readmore': '', 'aria-expanded': false, 'id': id}); current.after($(useLink) .on('click', function(event) { $this.toggle(this, current[0], event); }) - .attr({'data-readmore-js-toggle': '', 'aria-controls': id})); + .attr({'data-readmore-toggle': '', 'aria-controls': id})); if (! $this.options.startOpen) { current.css({height: collapsedHeight}); @@ -241,16 +244,16 @@ $(trigger).replaceWith($($this.options[newLink]) .on('click', function(event) { $this.toggle(this, element, event); }) - .attr({'data-readmore-js-toggle': '', 'aria-controls': $element.attr('id')})); + .attr({'data-readmore-toggle': '', 'aria-controls': $element.attr('id')})); }, destroy: function() { $(this.element).each(function() { var current = $(this); - current.attr({'data-readmore-js-section': null, 'aria-expanded': null }) + current.attr({'data-readmore': null, 'aria-expanded': null }) .css({'max-height': '', 'height': ''}) - .next('[data-readmore-js-toggle]') + .next('[data-readmore-toggle]') .remove(); current.removeData(); From b8eb4265859e4bc70a0e3618d7863da4af4b59e0 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 17:13:18 -0800 Subject: [PATCH 22/30] Add .gitignore Not sure why this wasn't in place earlier. --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7295724 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.* +node_modules +bower_components +coverage From c4fc15431c9bb3363a6f4abe89e8af168fd5ac58 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 17:31:18 -0800 Subject: [PATCH 23/30] Add NPM manifest and Gulp task for minification --- gulpfile.js | 14 ++++++++++++++ package.json | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 gulpfile.js create mode 100644 package.json diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..32f7268 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,14 @@ +var gulp = require('gulp'), + uglify = require('gulp-uglify'), + rename = require('gulp-rename'); + +gulp.task('compress', function() { + gulp.src('readmore.js') + .pipe(uglify({ + mangle: true, + compress: true, + preserveComments: 'some' + })) + .pipe(rename('readmore.min.js')) + .pipe(gulp.dest('./')); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..609aad1 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "Readmore.js", + "version": "2.0.0", + "description": "A lightweight jQuery plugin for collapsing and expanding long blocks of text with \"Read more\" and \"Close\" links.", + "main": "readmore.min.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/jedfoster/Readmore.js.git" + }, + "keywords": [ + "css", + "jquery", + "readmore", + "expand", + "collapse" + ], + "author": "Jed Foster ", + "license": "MIT", + "bugs": { + "url": "https://github.com/jedfoster/Readmore.js/issues" + }, + "homepage": "https://github.com/jedfoster/Readmore.js", + "devDependencies": { + "gulp": "^3.8.10", + "gulp-rename": "^1.2.0", + "gulp-uglify": "^1.0.2" + } +} From 935f1bee69f4bdb83c26f51cce05f8b4fa8e116b Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 17:31:50 -0800 Subject: [PATCH 24/30] Bump Bower version and minified script file --- bower.json | 2 +- readmore.min.js | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/bower.json b/bower.json index 31ea545..6cdf55f 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "Readmore.js", "main": "readmore.min.js", - "version": "1.0.0", + "version": "2.0.0", "homepage": "http://jedfoster.com/Readmore.js/", "authors": [ "Jed Foster " diff --git a/readmore.min.js b/readmore.min.js index ae0ee63..d047897 100644 --- a/readmore.min.js +++ b/readmore.min.js @@ -1,7 +1,11 @@ -(function(c){function g(b,a){this.element=b;this.options=c.extend({},h,a);c(this.element).data("max-height",this.options.maxHeight);c(this.element).data("height-margin",this.options.heightMargin);delete this.options.maxHeight;if(this.options.embedCSS&&!k){var d=".readmore-js-toggle, .readmore-js-section { "+this.options.sectionCSS+" } .readmore-js-section { overflow: hidden; }",e=document.createElement("style");e.type="text/css";e.styleSheet?e.styleSheet.cssText=d:e.appendChild(document.createTextNode(d)); -document.getElementsByTagName("head")[0].appendChild(e);k=!0}this._defaults=h;this._name=f;this.init()}var f="readmore",h={speed:100,maxHeight:200,heightMargin:16,moreLink:'Read More',lessLink:'Close',embedCSS:!0,sectionCSS:"display: block; width: 100%;",startOpen:!1,expandedClass:"readmore-js-expanded",collapsedClass:"readmore-js-collapsed",beforeToggle:function(){},afterToggle:function(){}},k=!1;g.prototype={init:function(){var b=this;c(this.element).each(function(){var a= -c(this),d=a.css("max-height").replace(/[^-\d\.]/g,"")>a.data("max-height")?a.css("max-height").replace(/[^-\d\.]/g,""):a.data("max-height"),e=a.data("height-margin");"none"!=a.css("max-height")&&a.css("max-height","none");b.setBoxHeight(a);if(a.outerHeight(!0)<=d+e)return!0;a.addClass("readmore-js-section "+b.options.collapsedClass).data("collapsedHeight",d);a.after(c(b.options.startOpen?b.options.lessLink:b.options.moreLink).on("click",function(c){b.toggleSlider(this,a,c)}).addClass("readmore-js-toggle")); -b.options.startOpen||a.css({height:d})});c(window).on("resize",function(a){b.resizeBoxes()})},toggleSlider:function(b,a,d){d.preventDefault();var e=this;d=newLink=sectionClass="";var f=!1;d=c(a).data("collapsedHeight");c(a).height()<=d?(d=c(a).data("expandedHeight")+"px",newLink="lessLink",f=!0,sectionClass=e.options.expandedClass):(newLink="moreLink",sectionClass=e.options.collapsedClass);e.options.beforeToggle(b,a,f);c(a).animate({height:d},{duration:e.options.speed,complete:function(){e.options.afterToggle(b, -a,f);c(b).replaceWith(c(e.options[newLink]).on("click",function(b){e.toggleSlider(this,a,b)}).addClass("readmore-js-toggle"));c(this).removeClass(e.options.collapsedClass+" "+e.options.expandedClass).addClass(sectionClass)}})},setBoxHeight:function(b){var a=b.clone().css({height:"auto",width:b.width(),overflow:"hidden"}).insertAfter(b),c=a.outerHeight(!0);a.remove();b.data("expandedHeight",c)},resizeBoxes:function(){var b=this;c(".readmore-js-section").each(function(){var a=c(this);b.setBoxHeight(a); -(a.height()>a.data("expandedHeight")||a.hasClass(b.options.expandedClass)&&a.height()o&&(o=i):o=n,e.data({expandedHeight:a,maxHeight:i,collapsedHeight:o}).css("max-height","none")}function n(e){if(!d[e.selector]){var t=" ";e.embedCSS&&""!==e.sectionCSS&&(t+=e.selector+" + [data-readmore-toggle], "+e.selector+"[data-readmore]{"+e.sectionCSS+"}"),t+=e.selector+"[data-readmore]{transition: height "+e.speed+"ms;overflow: hidden;}",function(e,t){var a=e.createElement("style");a.type="text/css",a.styleSheet?a.styleSheet.cssText=t:a.appendChild(e.createTextNode(t)),e.getElementsByTagName("head")[0].appendChild(a)}(document,t),d[e.selector]=!0}}function o(t,a){var i=this;this.element=t,this.options=e.extend({},s,a),e(this.element).data({defaultHeight:this.options.collapsedHeight,heightMargin:this.options.heightMargin}),n(this.options),this._defaults=s,this._name=r,window.addEventListener("load",function(){i.init()})}var r="readmore",s={speed:100,collapsedHeight:200,heightMargin:16,moreLink:'Read More',lessLink:'Close',embedCSS:!0,sectionCSS:"display: block; width: 100%;",startOpen:!1,beforeToggle:function(){},afterToggle:function(){}},d={},h=0,l=t(function(){e("[data-readmore]").each(function(){var t=e(this),a="true"===t.attr("aria-expanded");i(t),t.css("height",t.data(a?"expandedHeight":"collapsedHeight"))})},100);o.prototype={init:function(){var t=this;e(this.element).each(function(){var n=e(this);i(n);var o=n.data("collapsedHeight"),r=n.data("heightMargin");if(n.outerHeight(!0)<=o+r)return!0;var s=n.attr("id")||a(),d=t.options.startOpen?t.options.lessLink:t.options.moreLink;n.attr({"data-readmore":"","aria-expanded":!1,id:s}),n.after(e(d).on("click",function(e){t.toggle(this,n[0],e)}).attr({"data-readmore-toggle":"","aria-controls":s})),t.options.startOpen||n.css({height:o})}),window.addEventListener("resize",function(){l()})},toggle:function(t,a,i){i&&i.preventDefault(),t||(t=e('[aria-controls="'+this.element.id+'"]')[0]),a||(a=this.element);var n=this,o=e(a),r="",s="",d=!1,h=o.data("collapsedHeight");o.height()<=h?(r=o.data("expandedHeight")+"px",s="lessLink",d=!0):(r=h,s="moreLink"),n.options.beforeToggle(t,a,!d),o.css({height:r}),o.on("transitionend",function(){n.options.afterToggle(t,a,d),e(this).attr("aria-expanded",d).off("transitionend")}),e(t).replaceWith(e(n.options[s]).on("click",function(e){n.toggle(this,a,e)}).attr({"data-readmore-toggle":"","aria-controls":o.attr("id")}))},destroy:function(){e(this.element).each(function(){var t=e(this);t.attr({"data-readmore":null,"aria-expanded":null}).css({"max-height":"",height:""}).next("[data-readmore-toggle]").remove(),t.removeData()})}},e.fn.readmore=function(t){var a=arguments,i=this.selector;return t=t||{},"object"==typeof t?this.each(function(){if(e.data(this,"plugin_"+r)){var a=e.data(this,"plugin_"+r);a.destroy.apply(a)}t.selector=i,e.data(this,"plugin_"+r,new o(this,t))}):"string"==typeof t&&"_"!==t[0]&&"init"!==t?this.each(function(){var i=e.data(this,"plugin_"+r);i instanceof o&&"function"==typeof i[t]&&i[t].apply(i,Array.prototype.slice.call(a,1))}):void 0}}(jQuery); \ No newline at end of file From 89b22e8080f06411464f288b6109e75fc81bacd5 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 18:50:20 -0800 Subject: [PATCH 25/30] Tweaks for code consistency and readability --- readmore.js | 62 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/readmore.js b/readmore.js index 6591baf..eabb09f 100644 --- a/readmore.js +++ b/readmore.js @@ -62,13 +62,13 @@ function setBoxHeights(element) { var el = element.clone().css({ - 'height': 'auto', - 'width': element.width(), - 'max-height': 'none', - 'overflow': 'hidden' + height: 'auto', + width: element.width(), + maxHeight: 'none', + overflow: 'hidden' }).insertAfter(element), expandedHeight = el.outerHeight(true), - cssMaxHeight = parseInt(el.css({'max-height': ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10), + cssMaxHeight = parseInt(el.css({maxHeight: ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10), defaultHeight = element.data('defaultHeight'); el.remove(); @@ -84,12 +84,14 @@ // Store our measurements. element.data({ - 'expandedHeight': expandedHeight, - 'maxHeight': cssMaxHeight, - 'collapsedHeight': collapsedHeight + expandedHeight: expandedHeight, + maxHeight: cssMaxHeight, + collapsedHeight: collapsedHeight }) // and disable any `max-height` property set in CSS - .css('max-height', 'none'); + .css({ + maxHeight: 'none' + }); } var resizeBoxes = debounce(function() { @@ -99,7 +101,9 @@ setBoxHeights(current); - current.css('height', current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )); + current.css({ + height: current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') ) + }); }); }, 100); @@ -146,8 +150,8 @@ this.options = $.extend({}, defaults, options); $(this.element).data({ - 'defaultHeight': this.options.collapsedHeight, - 'heightMargin': this.options.heightMargin + defaultHeight: this.options.collapsedHeight, + heightMargin: this.options.heightMargin }); embedCSS(this.options); @@ -181,14 +185,23 @@ var id = current.attr('id') || uniqueId(), useLink = $this.options.startOpen ? $this.options.lessLink : $this.options.moreLink; - current.attr({'data-readmore': '', 'aria-expanded': false, 'id': id}); + current.attr({ + 'data-readmore': '', + 'aria-expanded': false, + 'id': id + }); current.after($(useLink) .on('click', function(event) { $this.toggle(this, current[0], event); }) - .attr({'data-readmore-toggle': '', 'aria-controls': id})); + .attr({ + 'data-readmore-toggle': '', + 'aria-controls': id + })); if (! $this.options.startOpen) { - current.css({height: collapsedHeight}); + current.css({ + height: collapsedHeight + }); } } }); @@ -239,20 +252,31 @@ $element.on('transitionend', function() { $this.options.afterToggle(trigger, element, expanded); - $(this).attr('aria-expanded', expanded).off('transitionend'); + $(this).attr({ + 'aria-expanded': expanded + }).off('transitionend'); }); $(trigger).replaceWith($($this.options[newLink]) .on('click', function(event) { $this.toggle(this, element, event); }) - .attr({'data-readmore-toggle': '', 'aria-controls': $element.attr('id')})); + .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({'max-height': '', 'height': ''}) + current.attr({ + 'data-readmore': null, + 'aria-expanded': null + }) + .css({ + maxHeight: '', + height: '' + }) .next('[data-readmore-toggle]') .remove(); From 67eb45e14c60ec5070dee42237a2aa006794fc90 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 22:22:15 -0800 Subject: [PATCH 26/30] Change sectionCSS to blockCSS --- readmore.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readmore.js b/readmore.js index eabb09f..3a993de 100644 --- a/readmore.js +++ b/readmore.js @@ -22,7 +22,7 @@ moreLink: 'Read More', lessLink: 'Close', embedCSS: true, - sectionCSS: 'display: block; width: 100%;', + blockCSS: 'display: block; width: 100%;', startOpen: false, // callbacks @@ -111,10 +111,10 @@ if (! cssEmbedded[options.selector]) { var styles = ' '; - if (options.embedCSS && options.sectionCSS !== '') { + if (options.embedCSS && options.blockCSS !== '') { styles += options.selector + ' + [data-readmore-toggle], ' + options.selector + '[data-readmore]{' + - options.sectionCSS + + options.blockCSS + '}'; } From c0fc9ec9585aa29c7af80ba418fb1b15135508c9 Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 22:39:46 -0800 Subject: [PATCH 27/30] Tweak the demo --- demo.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo.html b/demo.html index 33aab7c..5e66ef1 100644 --- a/demo.html +++ b/demo.html @@ -160,7 +160,7 @@

This section is shorter than the Readmore minimum

- + @@ -170,12 +170,12 @@

This section is shorter than the Readmore minimum

maxHeight: 390, afterToggle: function(trigger, element, expanded) { if(! expanded) { // The "Close" link was clicked - $('html, body').animate( { scrollTop: element.offset().top }, {duration: 100 } ); + $('html, body').animate({scrollTop: $(element).offset().top}, {duration: 100}); } } }); - $('article').readmore({maxHeight: 240, speed: 250}); + $('article').readmore({speed: 500}); From c11ef2b2ae0a3531d4c5cc50f94595d0dbdde03e Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 22:44:57 -0800 Subject: [PATCH 28/30] No more hipster impsum It was fun while it lasted. --- demo.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/demo.html b/demo.html index 5e66ef1..4996d7a 100644 --- a/demo.html +++ b/demo.html @@ -131,31 +131,31 @@

Demo

Artisanal Narwahls

-

Salvia portland leggings banh mi fanny pack mixtape, authentic bushwick wes anderson intelligentsia artisan typewriter high life they sold out mixtape high life. Marfa ethnic wayfarers brooklyn keytar mixtape. Blue bottle shoreditch gluten-free, mixtape hoodie whatever pinterest viral twee fashion axe high life irony biodiesel tofu.

+

From this distant vantage point, the Earth might not seem of any particular interest. But for us, it's different. Consider again that dot. That's here. That's home. That's us. On it everyone you love, everyone you know, everyone you ever heard of, every human being who ever was, lived out their lives. The aggregate of our joy and suffering, thousands of confident religions, ideologies, and economic doctrines, every hunter and forager, every hero and coward, every creator and destroyer of civilization, every king and peasant, every young couple in love, every mother and father, hopeful child, inventor and explorer, every teacher of morals, every corrupt politician, every "superstar," every "supreme leader," every saint and sinner in the history of our species lived there – on a mote of dust suspended in a sunbeam.

-

Retro church-key thundercats keytar, skateboard irony selvage ethnic freegan banjo pour-over fixie. Raw denim fashion ax eoke locavore disrupt, tonx retro authentic letterpress raw denim stumptown mixtape ugh kale chips flexitarian 90's deep v. Squid fingerstache bespoke wolf DIY. Banjo pour-over shoreditch cardigan try-hard.

+

Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!

-

Skateboard artisan bicycle rights next level vinyl cardigan beard twee, farm-to-table truffaut. Shoreditch freegan cliche thundercats, bushwick VHS intelligentsia selfies ethnic try-hard before they sold out. Marfa terry richardson hella, seitan odd future pug butcher. Wes anderson tousled YOLO cardigan. Typewriter high life carles, artisan gentrify messenger bag single-origin coffee truffaut thundercats cray 90's pour-over seitan. Banksy 8-bit organic, salvia gentrify stumptown wayfarers. Godard echo park before they sold out chambray, skateboard twee mcsweeney's synth hella.

+

Here's how it is: Earth got used up, so we terraformed a whole new galaxy of Earths, some rich and flush with the new technologies, some not so much. Central Planets, them was formed the Alliance, waged war to bring everyone under their rule; a few idiots tried to fight it, among them myself. I'm Malcolm Reynolds, captain of Serenity. Got a good crew: fighters, pilot, mechanic. We even picked up a preacher, and a bona fide companion. There's a doctor, too, took his genius sister out of some Alliance camp, so they're keeping a low profile. You got a job, we can do it, don't much care what it is.

-

Flannel pinterest Austin twee narwhal, hoodie swag wolf photo booth. Hella kale chips marfa occupy pitchfork put a bird on it. Semiotics dreamcatcher selfies beard DIY umami craft beer 3 wolf moon. Try-hard literally mustache polaroid ennui VHS. High life fixie wolf, trust fund twee seitan pinterest blog helvetica sriracha. Tattooed selvage try-hard, biodiesel banjo direct trade echo park kogi tonx bespoke. Pork belly put a bird on it iphone, fixie literally bespoke tonx butcher +1 swag.

+

Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!

Portland Leggings

-

Put a bird on it you probably haven't heard of them DIY, vice photo booth terry richardson pickled vegan cray. Pug DIY blog stumptown 3 wolf moon, blue bottle farm-to-table actually banh mi fanny pack quinoa small batch. Mumblecore raw denim hoodie ethnic. Craft beer sriracha mlkshk, organic bicycle rights twee gentrify messenger bag keffiyeh ethical helvetica tumblr. Gluten-free tousled vinyl pop-up. Butcher keytar pickled literally, cosby sweater chambray authentic. Fixie odd future kale chips, church-key pinterest beard pour-over banksy typewriter dreamcatcher squid blue bottle sartorial.

+

Here's how it is: Earth got used up, so we terraformed a whole new galaxy of Earths, some rich and flush with the new technologies, some not so much. Central Planets, them was formed the Alliance, waged war to bring everyone under their rule; a few idiots tried to fight it, among them myself. I'm Malcolm Reynolds, captain of Serenity. Got a good crew: fighters, pilot, mechanic. We even picked up a preacher, and a bona fide companion. There's a doctor, too, took his genius sister out of some Alliance camp, so they're keeping a low profile. You got a job, we can do it, don't much care what it is.

-

Banksy blog craft beer PBR fap retro wayfarers polaroid narwhal blue bottle mixtape squid YOLO. Pitchfork brunch put a bird on it, fashion axe squid vegan vice pop-up organic blue bottle forage plaid deep v locavore +1. American apparel 3 wolf moon cray tonx single-origin coffee, gluten-free sartorial. Try-hard iphone pork belly bespoke keffiyeh leggings selfies, chillwave gastropub. Master cleanse plaid hella readymade. Keytar cosby sweater truffaut kogi, try-hard flannel chillwave. Truffaut selvage bespoke banjo freegan, ugh williamsburg.

+

I am Duncan Macleod, born 400 years ago in the Highlands of Scotland. I am Immortal, and I am not alone. For centuries, we have waited for the time of the Gathering when the stroke of a sword and the fall of a head will release the power of the Quickening. In the end, there can be only one.

-

Austin single-origin coffee umami vice pork belly, ethical williamsburg flexitarian forage aesthetic street art keytar fashion axe twee. Cliche aesthetic trust fund, williamsburg carles godard four loko. Photo booth authentic cred semiotics, pickled vegan williamsburg mixtape church-key intelligentsia irony umami PBR. Wayfarers fixie keffiyeh hella ugh, dreamcatcher kale chips banksy Austin swag stumptown deep v tattooed. Mlkshk viral seitan small batch squid cosby sweater, jean shorts neutra. Vegan pitchfork banjo readymade, helvetica ethical bushwick banksy artisan sriracha single-origin coffee. Umami viral fashion axe, marfa DIY banh mi wes anderson master cleanse cosby sweater cray selvage organic actually semiotics.

+

From this distant vantage point, the Earth might not seem of any particular interest. But for us, it's different. Consider again that dot. That's here. That's home. That's us. On it everyone you love, everyone you know, everyone you ever heard of, every human being who ever was, lived out their lives. The aggregate of our joy and suffering, thousands of confident religions, ideologies, and economic doctrines, every hunter and forager, every hero and coward, every creator and destroyer of civilization, every king and peasant, every young couple in love, every mother and father, hopeful child, inventor and explorer, every teacher of morals, every corrupt politician, every "superstar," every "supreme leader," every saint and sinner in the history of our species lived there – on a mote of dust suspended in a sunbeam.

-

Yr messenger bag fanny pack small batch, single-origin coffee literally gluten-free farm-to-table 8-bit godard butcher fap actually biodiesel trust fund. Pickled squid cred pug pop-up. Polaroid deep v 90's, +1 godard mumblecore retro next level carles salvia try-hard food truck gluten-free. Four loko post-ironic tofu lomo, narwhal readymade mustache jean shorts letterpress. Leggings put a bird on it farm-to-table jean shorts williamsburg cardigan. Gentrify pug bushwick PBR fixie etsy. PBR banh mi cardigan, cosby sweater master cleanse mixtape fingerstache.

+

Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!

This section is shorter than the Readmore minimum

-

Put a bird on it you probably haven't heard of them DIY, vice photo booth terry richardson pickled vegan cray. Pug DIY blog stumptown 3 wolf moon, blue bottle farm-to-table actually banh mi fanny pack quinoa small batch. Mumblecore raw denim hoodie ethnic. Craft beer sriracha mlkshk, organic bicycle rights twee gentrify messenger bag keffiyeh ethical helvetica tumblr. Gluten-free tousled vinyl pop-up. Butcher keytar pickled literally, cosby sweater chambray authentic. Fixie odd future kale chips, church-key pinterest beard pour-over banksy typewriter dreamcatcher squid blue bottle sartorial.

+

Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!

From 6dcc9a3e9577a2a54ca36d41aa735f208b13cf1d Mon Sep 17 00:00:00 2001 From: Jed Foster Date: Fri, 2 Jan 2015 23:22:01 -0800 Subject: [PATCH 29/30] Update readme and demo --- README.md | 157 +++++++++++++++++++++++++++++++++++++++--------------- demo.html | 139 +++++++++++++++++++++++++++++++---------------- 2 files changed, 208 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 133c83d..0116fbe 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,60 @@ -Readmore.js -======== +# Readmore.js -A smooth, lightweight jQuery plugin for collapsing and expanding long blocks of text with "Read more" and "Close" links. +A smooth, responsive jQuery plugin for collapsing and expanding long blocks of text with "Read more" and "Close" links. -The required markup for Readmore.js is also extremely lightweight and very simple. No need for complicated sets of `div`s or hardcoded class names, just call ``.readmore()`` on the element containing your block of text and Readmore.js takes care of the rest. +The markup Readmore.js requires is so simple, you can probably use it with your existing HTML—there's no need for complicated sets of `div`'s or hardcoded classes, just call `.readmore()` on the element containing your block of text and Readmore.js takes care of the rest. Readmore.js plays well in a responsive environment, too. -Readmore.js is compatible with all versions of jQuery greater than 1.7.0. +Readmore.js is tested with—and supported on—all versions of jQuery greater than 1.9.1. All the "good" browsers are supported, as well as IE10+; IE8 & 9 _should_ work, but are not supported and the experience will not be ideal. -## Example ## - $('article').readmore(); - -Yes, it's that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements. +## Install - $('article').readmore({ - speed: 75, - maxHeight: 500 - }); +Install Readmore.js with Bower: -## The options: ## +``` +$ bower install readmore +``` + +Then include it in your HTML: + +```html + +``` + + +## Use + +```javascript +$('article').readmore(); +``` + +It's that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements. + +```javascript +$('article').readmore({ + speed: 75, + lessLink: 'Read less' +}); +``` -* `speed: 100` (in milliseconds) -* `maxHeight: 200` (in pixels) -* `heightMargin: 16` (in pixels, avoids collapsing blocks that are only slightly larger than `maxHeight`) +### The options: + +* `speed: 100` in milliseconds +* `collapsedHeight: 200` in pixels +* `heightMargin: 16` in pixels, avoids collapsing blocks that are only slightly larger than `collapsedHeight` * `moreLink: 'Read more'` * `lessLink: 'Close'` -* `embedCSS: true` (insert required CSS dynamically, set this to `false` if you include the necessary CSS in a stylesheet) -* `sectionCSS: 'display: block; width: 100%;'` (sets the styling of the blocks, ignored if `embedCSS` is `false`) -* `startOpen: false` (do not immediately truncate, start in the fully opened position) -* `expandedClass: 'readmore-js-expanded'` (class added to expanded blocks) -* `collapsedClass: 'readmore-js-collapsed'` (class added to collapsed blocks) -* `beforeToggle: function() {}` (called after a more or less link is clicked, but *before* the block is collapsed or expanded) -* `afterToggle: function() {}` (called *after* the block is collapsed or expanded) +* `embedCSS: true` insert required CSS dynamically, set this to `false` if you include the necessary CSS in a stylesheet +* `blockCSS: 'display: block; width: 100%;'` sets the styling of the blocks, ignored if `embedCSS` is `false` +* `startOpen: false` do not immediately truncate, start in the fully opened position +* `beforeToggle: function() {}` called after a more or less link is clicked, but *before* the block is collapsed or expanded +* `afterToggle: function() {}` called *after* the block is collapsed or expanded -If the element has a `max-height` CSS property, Readmore.js will use that value rather than the value of the `maxHeight` option. +If the element has a `max-height` CSS property, Readmore.js will use that value rather than the value of the `collapsedHeight` option. ### The callbacks: -The callback functions, `beforeToggle()` and `afterToggle`, both receive the same arguments: `trigger`, `element`, and `expanded`. +The callback functions, `beforeToggle` and `afterToggle`, both receive the same arguments: `trigger`, `element`, and `expanded`. * `trigger`: the "Read more" or "Close" element that was clicked * `element`: the block that is being collapsed or expanded @@ -57,44 +74,98 @@ $('article').readmore({ }); ``` -### Recommended CSS: +### Removing Readmore: + +You can remove the Readmore.js functionality like so: + +```javascript +$('article').readmore('destroy'); +``` + +Or, you can be more surgical by specifying a particular element: + +```javascript +$('article:first').readmore('destroy'); +``` + +### Toggling blocks programmatically: + +You can toggle a block from code: + +```javascript +$('article:nth-of-type(3)').readmore('toggle'); +``` + + +## CSS: -The intention behind Readmore.js is to use CSS for as much functionality as possible. In particular, "collapsing" is achieved by setting `overflow: hidden` on the containing block and changing the `height` property. +Readmore.js is designed to use CSS for as much functionality as possible: collapsed height can be set in CSS with the `max-height` property; "collapsing" is achieved by setting `overflow: hidden` on the containing block and changing the `height` property; and, finally, the expanding/collapsing animation is done with CSS3 transitions. -By default, Readmore.js inserts the following CSS: +By default, Readmore.js inserts the following CSS, in addition to some transition-related rules: ```css -.readmore-js-toggle, .readmore-js-section { +selector + [data-readmore-toggle], selector[data-readmore] { display: block; width: 100%; } -.readmore-js-section { - overflow: hidden; -} ``` -You can override the the first set of rules when you set up Readmore.js like so: +_`selector` would be the element you invoked `readmore()` on, e.g.: `$('selector').readmore()`_ + +You can override the base rules when you set up Readmore.js like so: ```javascript -$('article').readmore({sectionCSS: 'display: inline-block; width: 50%;'}); +$('article').readmore({blockCSS: 'display: inline-block; width: 50%;'}); ``` -If you want to include the necessary styling in your site's stylesheet, you can disable the dynamic embedding by passing `embedCSS: false` in the options hash. +If you want to include the necessary styling in your site's stylesheet, you can disable the dynamic embedding by setting `embedCSS` to `false`: ```javascript $('article').readmore({embedCSS: false}); ``` -## Removing Readmore +### Media queries and other CSS tricks: -You can remove the Readmore functionality like so: +If you wanted to set a `maxHeight` based on lines, you could do so in CSS with something like: -```javascript -$('article').readmore('destroy'); +```css +body { + font: 16px/1.5 sans-serif; +} + +/* Show only 4 lines in smaller screens */ +article { + max-height: 6em; /* (4 * 1.5 = 6) */ +} ``` -Or, you can be more surgical by specifying a particular element: +Then, with a media query you could change the number of lines shown, like so: -```javascript -$('article:first').readmore('destroy'); +```css +/* Show 8 lines on larger screens */ +@media screen and (min-width: 640px) { + article { + max-height: 12em; + } +} ``` + + +## Contributing + +Pull requests are always welcome, but not all suggested features will get merged. Feel free to contact me if you have an idea for a feature. + +Pull requests should include the minified script and this readme and the demo HTML should be updated with descriptions of your new feature. + +You'll need NPM: + +``` +$ npm install +``` + +Which will install the necessary development dependencies. Then, to build the minified script: + +``` +$ gulp compress +``` + diff --git a/demo.html b/demo.html index 4996d7a..f09a382 100644 --- a/demo.html +++ b/demo.html @@ -31,97 +31,146 @@

Readmore.js

-

A smooth, lightweight jQuery plugin for collapsing and expanding long blocks of text with “Read more” and “Close” links.

+

A smooth, responsive jQuery plugin for collapsing and expanding long blocks of text with “Read more” and “Close” links.

-

The required markup for Readmore.js is extremely lightweight. No need for complicated sets of divs or hardcoded class names, just call .readmore() on the element containing your block of text and Readmore.js takes care of the rest.

+

The markup Readmore.js requires is so simple, you can probably use it with your existing HTML—there’s no need for complicated sets of div’s or hardcoded classes, just call .readmore() on the element containing your block of text and Readmore.js takes care of the rest. Readmore.js plays well in a responsive environment, too.

-

Readmore.js is compatible with all versions of jQuery greater than 1.7.0.

+

Readmore.js is tested with—and supported on—all versions of jQuery greater than 1.9.1. All the “good” browsers are supported, as well as IE10+; IE8 & 9 should work, but are not supported and the experience will not be ideal.

-

Example

+

Install

-
$('article').readmore();
+

Install Readmore.js with Bower:

-

Yes, it’s that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements.

+
$ bower install readmore
-
$('article').readmore({
+      

Then include it in your HTML:

+ +
<script src="/bower_components/readmore/readmore.min.js"></script>
+ +

Use

+ +
$('article').readmore();
+ +

It’s that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements.

+ +
$('article').readmore({
   speed: 75,
-  maxHeight: 500
+  lessLink: '<a href="#">Read less</a>'
 });
-

The options:

+

The options:

    -
  • speed: 100 (in milliseconds)
  • -
  • maxHeight: 200 (in pixels)
  • -
  • heightMargin: 16 (in pixels, avoids collapsing blocks that are only slightly larger than maxHeight)
  • -
  • moreLink: '<a href="#">Read more</a>'
  • -
  • lessLink: '<a href="#">Close</a>'
  • -
  • embedCSS: true (insert required CSS dynamically, set this to false if you include the necessary CSS in a stylesheet)
  • -
  • sectionCSS: 'display: block; width: 100%;' (sets the styling of the blocks)
  • -
  • startOpen: false (do not immediately truncate, start in the fully opened position)
  • -
  • expandedClass: 'readmore-js-expanded' (class added to expanded blocks)
  • -
  • collapsedClass: 'readmore-js-collapsed' (class added to collapsed blocks)
  • -
  • beforeToggle: function() {} (called after a more or less link is clicked, but before the block is collapsed or expanded)
  • -
  • afterToggle: function() {} (called after the block is collapsed or expanded)
  • +
  • speed: 100 in milliseconds
  • +
  • collapsedHeight: 200 in pixels
  • +
  • heightMargin: 16 in pixels, avoids collapsing blocks that are only slightly larger than collapsedHeight
  • +
  • moreLink: '<a href="#">Read more</a>'
  • +
  • lessLink: '<a href="#">Close</a>'
  • +
  • embedCSS: true insert required CSS dynamically, set this to false if you include the necessary CSS in a stylesheet
  • +
  • blockCSS: 'display: block; width: 100%;' sets the styling of the blocks, ignored if embedCSS is false
  • +
  • startOpen: false do not immediately truncate, start in the fully opened position
  • +
  • beforeToggle: function() {} called after a more or less link is clicked, but before the block is collapsed or expanded
  • +
  • afterToggle: function() {} called after the block is collapsed or expanded
-

If the element has a max-height CSS property, Readmore.js will use that value rather than the value of the maxHeight option.

+

If the element has a max-height CSS property, Readmore.js will use that value rather than the value of the collapsedHeight option.

The callbacks:

-

The callback functions, beforeToggle() and afterToggle, both receive the same arguments: trigger, element, and expanded.

+

The callback functions, beforeToggle and afterToggle, both receive the same arguments: trigger, element, and expanded.

    -
  • trigger: the "Read more" or "Close" element that was clicked
  • +
  • trigger: the “Read more” or “Close” element that was clicked
  • element: the block that is being collapsed or expanded
  • expanded: Boolean; true means the block is expanded

Callback example:

-

Here's an example of how you could use the afterToggle callback to scroll back to the top of a block when the "Close" link is clicked.

+

Here’s an example of how you could use the afterToggle callback to scroll back to the top of a block when the “Close” link is clicked.

-
$('article').readmore({
+      
$('article').readmore({
   afterToggle: function(trigger, element, expanded) {
-    if(! expanded) { // The "Close" link was clicked
-      $('html, body').animate( { scrollTop: element.offset().top }, {duration: 100 } );
+    if(! expanded) { // The "Close" link was clicked
+      $('html, body').animate( { scrollTop: element.offset().top }, {duration: 100 } );
     }
   }
 });
-

Recommended CSS:

+

Removing Readmore:

+ +

You can remove the Readmore.js functionality like so:

-

The intention behind Readmore.js is to use CSS for as much functionality as possible. In particular, “collapsing” is achieved by setting overflow: hidden on the containing block and changing the height property.

+
$('article').readmore('destroy');
+ +

Or, you can be more surgical by specifying a particular element:

-

By default, Readmore.js inserts the following CSS:

+
$('article:first').readmore('destroy');
-
.readmore-js-toggle, .readmore-js-section {
+      

Toggling blocks programmatically:

+ +

You can toggle a block from code:

+ +
$('article:nth-of-type(3)').readmore('toggle');
+ +

CSS:

+ +

Readmore.js is designed to use CSS for as much functionality as possible: collapsed height can be set in CSS with the max-height property; “collapsing” is achieved by setting overflow: hidden on the containing block and changing the height property; and, finally, the expanding/collapsing animation is done with CSS3 transitions.

+ +

By default, Readmore.js inserts the following CSS, in addition to some transition-related rules:

+ +
selector + [data-readmore-toggle], selector[data-readmore] {
   display: block;
   width: 100%;
-}
-.readmore-js-section {
-  overflow: hidden;
 }
-

You can override the the first set of rules when you set up Readmore.js like so:

+

selector would be the element you invoked readmore() on, e.g.: $('selector').readmore()

-
$('article').readmore({sectionCSS: 'display: inline-block; width: 50%;'});
+

You can override the base rules when you set up Readmore.js like so:

-

If you want to include the necessary styling in your site’s stylesheet, you can disable the dynamic embedding by passing embedCSS: false in the options hash.

+
$('article').readmore({blockCSS: 'display: inline-block; width: 50%;'});
+ +

If you want to include the necessary styling in your site’s stylesheet, you can disable the dynamic embedding by setting embedCSS to false:

$('article').readmore({embedCSS: false});
-

Removing Readmore

+

Media queries and other CSS tricks:

-

You can remove the Readmore functionality like so:

+

If you wanted to set a maxHeight based on lines, you could do so in CSS with something like:

-
$('article').readmore('destroy');
+
body {
+  font: 16px/1.5 sans-serif;
+}
 
-      

Or, you can be more surgical by specifying a particular element:

+/* Show only 4 lines in smaller screens */ +article { + max-height: 6em; /* (4 * 1.5 = 6) */ +}
+ +

Then, with a media query you could change the number of lines shown, like so:

+ +
/* Show 8 lines on larger screens */
+@media screen and (min-width: 640px) {
+  article {
+    max-height: 12em;
+  }
+}
+ +

Contributing

+ +

Pull requests are always welcome, but not all suggested features will get merged. Feel free to contact me if you have an idea for a feature.

+ +

Pull requests should include the minified script and this readme and the demo HTML should be updated with descriptions of your new feature.

+ +

You’ll need NPM:

+ +
$ npm install
+ +

Which will install the necessary development dependencies. Then, to build the minified script:

-
$('article:first').readmore('destroy');
+
$ gulp compress

Demo

@@ -166,8 +215,8 @@

This section is shorter than the Readmore minimum