From 9aa0a7ed58a615c96a35d0716b73bba3344ad69e Mon Sep 17 00:00:00 2001 From: Florian Staudacher Date: Mon, 12 Mar 2012 21:32:22 +0100 Subject: [PATCH 1/5] extracted "show more" part from #2941, tests still WIP --- public/javascripts/app/views/comment_view.js | 6 +- public/javascripts/app/views/content_view.js | 11 +++- .../app/views/stream_object_view.js | 29 --------- public/javascripts/app/views/stream_view.js | 30 +++++++++ public/stylesheets/sass/application.sass | 20 ++++++ .../javascripts/app/views/stream_view_spec.js | 63 +++---------------- 6 files changed, 74 insertions(+), 85 deletions(-) diff --git a/public/javascripts/app/views/comment_view.js b/public/javascripts/app/views/comment_view.js index 5c04227cb..c85faf7a2 100644 --- a/public/javascripts/app/views/comment_view.js +++ b/public/javascripts/app/views/comment_view.js @@ -4,8 +4,10 @@ app.views.Comment = app.views.Content.extend({ className : "comment media", - events : { - "click .comment_delete": "destroyModel" + events : function() { + return _.extend(app.views.Content.prototype.events, { + "click .comment_delete": "destroyModel" + }); }, presenter : function() { diff --git a/public/javascripts/app/views/content_view.js b/public/javascripts/app/views/content_view.js index d8cde6e45..0037e3eee 100644 --- a/public/javascripts/app/views/content_view.js +++ b/public/javascripts/app/views/content_view.js @@ -1,7 +1,8 @@ app.views.Content = app.views.StreamObject.extend({ events: { - "click .oembed .thumb": "showOembedContent" + "click .oembed .thumb": "showOembedContent", + "click .expander": "expandPost" }, presenter : function(){ @@ -41,7 +42,15 @@ app.views.Content = app.views.StreamObject.extend({ var paramSeparator = ( /\\?/.test(embedHTML.attr("href")) ) ? "&" : "?"; embedHTML.attr("src", embedHTML.attr("src") + paramSeparator + "autoplay=1"); oembed.html( embedHTML ); + }, + + expandPost: function(evt) { + var el = $(this.el).find('.collapsible'); + el.removeClass('collapsed').addClass('opened'); + el.animate({'height':el.data('orig-height')}, 550); + $(evt.currentTarget).hide(); } + }); app.views.StatusMessage = app.views.Content.extend({ diff --git a/public/javascripts/app/views/stream_object_view.js b/public/javascripts/app/views/stream_object_view.js index 054cdee88..1f46e732b 100644 --- a/public/javascripts/app/views/stream_object_view.js +++ b/public/javascripts/app/views/stream_object_view.js @@ -1,34 +1,5 @@ app.views.StreamObject = app.views.Base.extend({ - postRenderTemplate : function() { - // collapse long posts - this.$(".collapsible").expander({ - slicePoint: 400, - widow: 12, - expandPrefix: "", - expandText: Diaspora.I18n.t("show_more"), - userCollapse: false, - beforeExpand: function() { - if ($(this).find('.summary').length == 0) { // Sigh. See comments in the spec. - var readMoreDiv = $(this).find('.read-more'); - var lastElementBeforeReadMore = readMoreDiv.prev(); - var firstElementAfterReadMore = readMoreDiv.next().children().first(); - - if (lastElementBeforeReadMore.is('p')) { - lastElementBeforeReadMore.append(firstElementAfterReadMore.html()); - firstElementAfterReadMore.remove(); - - } else if (lastElementBeforeReadMore.is('ul') && firstElementAfterReadMore.is('ul')) { - var firstBullet = firstElementAfterReadMore.children().first(); - lastElementBeforeReadMore.find('li').last().append(firstBullet.html()); - firstBullet.remove(); - } - readMoreDiv.remove(); - } - } - }); - }, - destroyModel: function(evt) { if (evt) { evt.preventDefault(); diff --git a/public/javascripts/app/views/stream_view.js b/public/javascripts/app/views/stream_view.js index 0c3b71d74..b3095b540 100644 --- a/public/javascripts/app/views/stream_view.js +++ b/public/javascripts/app/views/stream_view.js @@ -16,6 +16,7 @@ app.views.Stream = Backbone.View.extend({ setupEvents : function(){ this.stream.bind("fetched", this.removeLoader, this) + this.stream.bind("fetched", this.postRender, this) this.stream.bind("allPostsLoaded", this.unbindInfScroll, this) this.collection.bind("add", this.addPost, this); if(window.app.user()) { @@ -52,6 +53,35 @@ app.views.Stream = Backbone.View.extend({ return this; }, + + postRender : function() { + // collapse long posts + var collHeight = 190, + collElem = $(this.el).find(".collapsible"); + + _.each(collElem, function(elem) { + var elem = $(elem), + oembed = elem.find(".oembed"), + addHeight = 0; + + if( $.trim(oembed.html()) != "" ) { + addHeight = oembed.height(); + } + + // only collapse if height exceeds collHeight+20% + if( elem.height() > ((collHeight*1.2)+addHeight) && !elem.is(".opened") ) { + elem.data("orig-height", elem.height() ); + elem + .height( Math.max(collHeight, addHeight) ) + .addClass("collapsed") + .append( + $('
') + .addClass('expander') + .text( Diaspora.I18n.t("show_more") ) + ); + } + }); + }, appendLoader: function(){ $("#paginate").html($("", { diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass index 7c8622063..73850a289 100644 --- a/public/stylesheets/sass/application.sass +++ b/public/stylesheets/sass/application.sass @@ -1828,6 +1828,26 @@ ul#press_logos :color #777 .collapsible + :overflow hidden + :position relative + + .expander + :position absolute + :bottom 0 + :left 0 + :right 0 + :height 30px + :text-align center + :line-height 48px + :font-size .8em + :color $grey + :text-shadow 0 0 7px #FFF + :cursor pointer + :border-bottom 2px solid #DDD + @include border-radius(0, 0, 3px, 3px) + @include linear-gradient(rgba(255,255,255,0) , #EEE, 0%, 95%) + :background-color transparent + .oembed :background url('/images/ajax-loader2.gif') no-repeat center center diff --git a/spec/javascripts/app/views/stream_view_spec.js b/spec/javascripts/app/views/stream_view_spec.js index b1b8b1630..68cd67f1c 100644 --- a/spec/javascripts/app/views/stream_view_spec.js +++ b/spec/javascripts/app/views/stream_view_spec.js @@ -45,64 +45,21 @@ describe("app.views.Stream", function() { beforeEach(function() { this.statusMessage = this.stream.posts.models[0]; this.statusElement = $(this.view.$(".stream_element")[0]); - readMoreLink = this.statusElement.find('.read-more a'); + readMoreLink = this.statusElement.find('.expander'); readMoreLink.text("read more"); + + $(this.view.el).find('.collapsible').css('width', 400); // make content narrow like in real stream + setFixtures(this.view.el); + this.view.postRender(); }); it('expands the post', function() { - expect(this.statusElement.find('.collapsible .details').attr('style')).toContain('display: none;'); + // TODO + var textElement = this.statusElement.find('.collapsible'); + expect(textElement.hasClass('collapsed')).toBeTruthy(); readMoreLink.click(); - expect(this.statusElement.find('.collapsible .details').attr('style')).not.toContain('display: none;'); - }); - - describe('differences between firefox and webkit/IE', function() { - // Firefox creates 2 divs - one with the summary and one with the whole post. - // It hides the summary and shows the whole post when you click show more. Works great! - // Webkit and IE also create 2 divs, but they split the post - the 1st has the summary and the 2nd has the rest - // of the post. When you click read more, it just shows the 2nd div. This leaves whitespace in odd places. - // So there's a callback that this is testing, that fixes the whitespace on webkit & IE. - var weAreOnFirefox; - - beforeEach(function() { - weAreOnFirefox = this.statusElement.find('.collapsible .summary').length > 0; - }); - - it('removes the read-more div on webkit/IE but leaves it on firefox', function() { - expect(this.statusElement.find('.read-more').length).toEqual(1); - readMoreLink.click(); - if (weAreOnFirefox === true) { - expect(this.statusElement.find('.read-more').length).toEqual(1); - } else { - expect(this.statusElement.find('.read-more').length).toEqual(0); - } - }); - - it('collapses p elements on webkit/IE but leaves them alone on firefox', function() { - expect(this.statusElement.find('.collapsible p').length).toEqual(2); - readMoreLink.click(); - if (weAreOnFirefox === true) { - expect(this.statusElement.find('.collapsible p').length).toEqual(2); - } else { - expect(this.statusElement.find('.collapsible p').length).toEqual(1); - } - }); - - it('collapses li elements on webkit/IE but leaves them alone on firefox', function() { - this.statusMessage = this.stream.posts.models[3]; - this.statusElement = $(this.view.$(".stream_element")[3]); - readMoreLink = this.statusElement.find('.read-more a'); - readMoreLink.text("read more"); - - if (weAreOnFirefox === true) { - expect(this.statusElement.find('.collapsible li').length).toEqual(12); - readMoreLink.click(); - expect(this.statusElement.find('.collapsible li').length).toEqual(12); - } else { - expect(this.statusElement.find('.collapsible li').length).toEqual(9); - readMoreLink.click(); - expect(this.statusElement.find('.collapsible li').length).toEqual(8); - } - }); + expect(textElement.hasClass('collapsed')).toBeFalsy(); + expect(textElement.hasClass('opened')).toBeTruthy(); }); }); From 813b706f8efdc3534bb81e6a168b972f20d1eed2 Mon Sep 17 00:00:00 2001 From: Florian Staudacher Date: Wed, 14 Mar 2012 01:50:24 +0100 Subject: [PATCH 2/5] added little fix for extra long content --- public/javascripts/app/views/content_view.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/javascripts/app/views/content_view.js b/public/javascripts/app/views/content_view.js index 0037e3eee..0f1aa2fc2 100644 --- a/public/javascripts/app/views/content_view.js +++ b/public/javascripts/app/views/content_view.js @@ -47,10 +47,12 @@ app.views.Content = app.views.StreamObject.extend({ expandPost: function(evt) { var el = $(this.el).find('.collapsible'); el.removeClass('collapsed').addClass('opened'); - el.animate({'height':el.data('orig-height')}, 550); + el.animate({'height':el.data('orig-height')}, 550, function() { + el.css('height','auto'); + }); $(evt.currentTarget).hide(); } - + }); app.views.StatusMessage = app.views.Content.extend({ From 5dae27c1706033f20d1939371ee4cc04d9cc7812 Mon Sep 17 00:00:00 2001 From: Florian Staudacher Date: Fri, 16 Mar 2012 02:00:50 +0100 Subject: [PATCH 3/5] finally some working tests for "show more" --- features/show_more.feature | 24 +++++++++++++++++++ features/step_definitions/posts_steps.rb | 21 +++++++++++++++- .../javascripts/app/views/stream_view_spec.js | 24 ------------------- 3 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 features/show_more.feature diff --git a/features/show_more.feature b/features/show_more.feature new file mode 100644 index 000000000..e842267e7 --- /dev/null +++ b/features/show_more.feature @@ -0,0 +1,24 @@ +@javascript +Feature: collapsing and expanding long posts + In order to tame the lengths of posts in my stream + As a rocket scientist + I want long posts to be collapsed and expand on click + + Background: + Given a user with username "bob" + And I sign in as "bob@bob.bob" + And I am on the home page + + Scenario: post a very long message + Given I post an extremely long status message + And I go to the home page + + Then the post should be collapsed + + Scenario: expand a very long message + Given I post an extremely long status message + And I go to the home page + And I expand the post + + Then the post should be expanded + diff --git a/features/step_definitions/posts_steps.rb b/features/step_definitions/posts_steps.rb index 3e81c8840..bae24539a 100644 --- a/features/step_definitions/posts_steps.rb +++ b/features/step_definitions/posts_steps.rb @@ -2,6 +2,17 @@ Then /^the post "([^"]*)" should be marked nsfw$/ do |text| assert_nsfw(text) end +Then /^the post should be collapsed$/ do + find(".collapsible").should have_css(".expander") + find(".collapsible").has_selector?(".collapsed") +end + +Then /^the post should be expanded$/ do + find(".expander").should_not be_visible + find(".collapsible").has_no_selector?(".collapsed") + find(".collapsible").has_selector?(".opened") +end + Then /^I should see an uploaded image within the photo drop zone$/ do find("#photodropzone img")["src"].should include("uploads/images") end @@ -32,6 +43,10 @@ When /^I click on the first block button/ do find(".block_user").click end +When /^I expand the post$/ do + find(".expander").click + wait_until{ !find(".expander").visible? } +end Then /^I should see "([^"]*)" as the first post in my stream$/ do |text| first_post_text.should include(text) @@ -43,4 +58,8 @@ end When /^I click the publisher and post "([^"]*)"$/ do |text| click_and_post(text) -end \ No newline at end of file +end + +When /^I post an extremely long status message$/ do + click_and_post("I am a very interesting message " * 64) +end diff --git a/spec/javascripts/app/views/stream_view_spec.js b/spec/javascripts/app/views/stream_view_spec.js index 68cd67f1c..7f14ae523 100644 --- a/spec/javascripts/app/views/stream_view_spec.js +++ b/spec/javascripts/app/views/stream_view_spec.js @@ -39,30 +39,6 @@ describe("app.views.Stream", function() { }); }); - describe('clicking read more', function() { - var readMoreLink; - - beforeEach(function() { - this.statusMessage = this.stream.posts.models[0]; - this.statusElement = $(this.view.$(".stream_element")[0]); - readMoreLink = this.statusElement.find('.expander'); - readMoreLink.text("read more"); - - $(this.view.el).find('.collapsible').css('width', 400); // make content narrow like in real stream - setFixtures(this.view.el); - this.view.postRender(); - }); - - it('expands the post', function() { - // TODO - var textElement = this.statusElement.find('.collapsible'); - expect(textElement.hasClass('collapsed')).toBeTruthy(); - readMoreLink.click(); - expect(textElement.hasClass('collapsed')).toBeFalsy(); - expect(textElement.hasClass('opened')).toBeTruthy(); - }); - }); - describe("infScroll", function() { // NOTE: inf scroll happens at 500px From 27997e95250675056bafce417b1f39fcb05b85f8 Mon Sep 17 00:00:00 2001 From: Florian Staudacher Date: Fri, 16 Mar 2012 22:25:53 +0100 Subject: [PATCH 4/5] refactored test method into helpers --- features/step_definitions/posts_steps.rb | 10 +++------- features/support/publishing_cuke_helpers.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/features/step_definitions/posts_steps.rb b/features/step_definitions/posts_steps.rb index bae24539a..ee7e2854f 100644 --- a/features/step_definitions/posts_steps.rb +++ b/features/step_definitions/posts_steps.rb @@ -3,14 +3,11 @@ Then /^the post "([^"]*)" should be marked nsfw$/ do |text| end Then /^the post should be collapsed$/ do - find(".collapsible").should have_css(".expander") - find(".collapsible").has_selector?(".collapsed") + first_post_collapsed? end Then /^the post should be expanded$/ do - find(".expander").should_not be_visible - find(".collapsible").has_no_selector?(".collapsed") - find(".collapsible").has_selector?(".opened") + first_post_expanded? end Then /^I should see an uploaded image within the photo drop zone$/ do @@ -44,8 +41,7 @@ When /^I click on the first block button/ do end When /^I expand the post$/ do - find(".expander").click - wait_until{ !find(".expander").visible? } + expand_first_post end Then /^I should see "([^"]*)" as the first post in my stream$/ do |text| diff --git a/features/support/publishing_cuke_helpers.rb b/features/support/publishing_cuke_helpers.rb index 9895813a4..8f1762434 100644 --- a/features/support/publishing_cuke_helpers.rb +++ b/features/support/publishing_cuke_helpers.rb @@ -17,6 +17,22 @@ module PublishingCukeHelpers ') end + def expand_first_post + find(".stream_element:first .expander").click + wait_until{ !find(".expander").visible? } + end + + def first_post_collapsed? + find(".stream_element:first .collapsible").should have_css(".expander") + find(".stream_element:first .collapsible").has_selector?(".collapsed") + end + + def first_post_expanded? + find(".stream_element:first .expander").should_not be_visible + find(".stream_element:first .collapsible").has_no_selector?(".collapsed") + find(".stream_element:first .collapsible").has_selector?(".opened") + end + def first_post_text find('.stream_element:first .post-content').text() end From af008ab713916af0b6bda04dcb759c150f54e594 Mon Sep 17 00:00:00 2001 From: Maxwell Salzberg Date: Mon, 19 Mar 2012 19:13:15 -0700 Subject: [PATCH 5/5] remove unused expander plugin --- config/assets.yml | 1 - public/javascripts/vendor/jquery.expander.js | 338 ------------------- 2 files changed, 339 deletions(-) delete mode 100644 public/javascripts/vendor/jquery.expander.js diff --git a/config/assets.yml b/config/assets.yml index 9d346d01a..8bf27bd77 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -21,7 +21,6 @@ javascripts: - public/javascripts/vendor/jquery.autoresize.js - public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js - public/javascripts/vendor/jquery.charcount.js - - public/javascripts/vendor/jquery.expander.js - public/javascripts/vendor/jquery.placeholder.js - public/javascripts/vendor/timeago.js - public/javascripts/vendor/facebox.js diff --git a/public/javascripts/vendor/jquery.expander.js b/public/javascripts/vendor/jquery.expander.js deleted file mode 100644 index 812057095..000000000 --- a/public/javascripts/vendor/jquery.expander.js +++ /dev/null @@ -1,338 +0,0 @@ -/*! - * jQuery Expander Plugin v1.3 - * - * Date: Sat Sep 17 00:37:34 2011 EDT - * Requires: jQuery v1.3+ - * - * Copyright 2011, Karl Swedberg - * Dual licensed under the MIT and GPL licenses (just like jQuery): - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * - * - * -*/ - -(function($) { - $.expander = { - version: '1.3', - defaults: { - // the number of characters at which the contents will be sliced into two parts. - slicePoint: 100, - - // whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false) - preserveWords: true, - - // widow: a threshold of sorts for whether to initially hide/collapse part of the element's contents. - // If after slicing the contents in two there are fewer words in the second part than - // the value set by widow, we won't bother hiding/collapsing anything. - widow: 4, - - // text displayed in a link instead of the hidden part of the element. - // clicking this will expand/show the hidden/collapsed text - expandText: 'read more', - expandPrefix: '… ', - - // class names for summary element and detail element - summaryClass: 'summary', - detailClass: 'details', - - // class names for around "read-more" link and "read-less" link - moreClass: 'read-more', - lessClass: 'read-less', - - // number of milliseconds after text has been expanded at which to collapse the text again. - // when 0, no auto-collapsing - collapseTimer: 0, - - // effects for expanding and collapsing - expandEffect: 'fadeIn', - expandSpeed: 250, - collapseEffect: 'fadeOut', - collapseSpeed: 200, - - // allow the user to re-collapse the expanded text. - userCollapse: true, - - // text to use for the link to re-collapse the text - userCollapseText: 'read less', - userCollapsePrefix: ' ', - - - // all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called - - onSlice: null, // function() {} - beforeExpand: null, // function() {}, - afterExpand: null, // function() {}, - onCollapse: null // function(byUser) {} - } - }; - - $.fn.expander = function(options) { - - var opts = $.extend({}, $.expander.defaults, options), - rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i, - rAmpWordEnd = /(&(?:[^;]+;)?|\w+)$/, - rOpenCloseTag = /<\/?(\w+)[^>]*>/g, - rOpenTag = /<(\w+)[^>]*>/g, - rCloseTag = /<\/(\w+)>/g, - rTagPlus = /^<[^>]+>.?/, - delayedCollapse; - - this.each(function() { - var i, l, tmp, summTagLess, summOpens, summCloses, lastCloseTag, detailText, - $thisDetails, $readMore, - openTagsForDetails = [], - closeTagsForsummaryText = [], - defined = {}, - thisEl = this, - $this = $(this), - $summEl = $([]), - o = $.meta ? $.extend({}, opts, $this.data()) : opts, - hasDetails = !!$this.find('.' + o.detailClass).length, - hasBlocks = !!$this.find('*').filter(function() { - var display = $(this).css('display'); - return (/^block|table|list/).test(display); - }).length, - el = hasBlocks ? 'div' : 'span', - detailSelector = el + '.' + o.detailClass, - moreSelector = 'span.' + o.moreClass, - expandSpeed = o.expandSpeed || 0, - allHtml = $.trim( $this.html() ), - allText = $.trim( $this.text() ), - summaryText = allHtml.slice(0, o.slicePoint); - - // bail out if we've already set up the expander on this element - if ( $.data(this, 'expander') ) { - return; - } - $.data(this, 'expander', true); - - // determine which callback functions are defined - $.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) { - defined[val] = $.isFunction(o[val]); - }); - - // back up if we're in the middle of a tag or word - summaryText = backup(summaryText); - - // summary text sans tags length - summTagless = summaryText.replace(rOpenCloseTag,'').length; - - // add more characters to the summary, one for each character in the tags - while (summTagless < o.slicePoint) { - newChar = allHtml.charAt(summaryText.length); - if (newChar == '<') { - newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0]; - } - summaryText += newChar; - summTagless++; - } - - summaryText = backup(summaryText, o.preserveWords); - - // separate open tags from close tags and clean up the lists - summOpens = summaryText.match(rOpenTag) || []; - summCloses = summaryText.match(rCloseTag) || []; - - // filter out self-closing tags - tmp = []; - $.each(summOpens, function(index, val) { - if ( !rSelfClose.test(val) ) { - tmp.push(val); - } - }); - summOpens = tmp; - - // strip close tags to just the tag name - l = summCloses.length; - for (i = 0; i < l; i++) { - summCloses[i] = summCloses[i].replace(rCloseTag, '$1'); - } - - // tags that start in summary and end in detail need: - // a). close tag at end of summary - // b). open tag at beginning of detail - $.each(summOpens, function(index, val) { - var thisTagName = val.replace(rOpenTag, '$1'); - var closePosition = $.inArray(thisTagName, summCloses); - if (closePosition === -1) { - openTagsForDetails.push(val); - closeTagsForsummaryText.push(''); - - } else { - summCloses.splice(closePosition, 1); - } - }); - - // reverse the order of the close tags for the summary so they line up right - closeTagsForsummaryText.reverse(); - - // create necessary summary and detail elements if they don't already exist - if ( !hasDetails ) { - - // end script if detail has fewer words than widow option - detailText = allHtml.slice(summaryText.length); - if ( detailText.split(/\s+/).length < o.widow && !hasDetails ) { - return; - } - - // otherwise, continue... - lastCloseTag = closeTagsForsummaryText.pop() || ''; - summaryText += closeTagsForsummaryText.join(''); - detailText = openTagsForDetails.join('') + detailText; - - } else { - // assume that even if there are details, we still need readMore/readLess/summary elements - // (we already bailed out earlier when readMore el was found) - // but we need to create els differently - - // remove the detail from the rest of the content - detailText = $this.find(detailSelector).remove().html(); - - // The summary is what's left - summaryText = $this.html(); - - // allHtml is the summary and detail combined (this is needed when content has block-level elements) - allHtml = summaryText + detailText; - - lastCloseTag = ''; - } - o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o); - - if (hasBlocks) { - detailText = allHtml; - } - summaryText += lastCloseTag; - - // onSlice callback - o.summary = summaryText; - o.details = detailText; - o.lastCloseTag = lastCloseTag; - - if (defined.onSlice) { - // user can choose to return a modified options object - // one last chance for user to change the options. sneaky, huh? - // but could be tricky so use at your own risk. - tmp = o.onSlice.call(thisEl, o); - - // so, if the returned value from the onSlice function is an object with a details property, we'll use that! - o = tmp && tmp.details ? tmp : o; - } - - // build the html with summary and detail and use it to replace old contents - var html = buildHTML(o, hasBlocks); - $this.html( html ); - - // set up details and summary for expanding/collapsing - $thisDetails = $this.find(detailSelector); - $readMore = $this.find(moreSelector); - $thisDetails.hide(); - $readMore.find('a').unbind('click.expander').bind('click.expander', expand); - - $summEl = $this.find('div.' + o.summaryClass); - - if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) { - $this - .find(detailSelector) - .append('' + o.userCollapsePrefix + '' + o.userCollapseText + ''); - } - - $this - .find('span.' + o.lessClass + ' a') - .unbind('click.expander') - .bind('click.expander', function(event) { - event.preventDefault(); - clearTimeout(delayedCollapse); - var $detailsCollapsed = $(this).closest(detailSelector); - reCollapse(o, $detailsCollapsed); - if (defined.onCollapse) { - o.onCollapse.call(thisEl, true); - } - }); - - function expand(event) { - event.preventDefault(); - $readMore.hide(); - $summEl.hide(); - if (defined.beforeExpand) { - o.beforeExpand.call(thisEl); - } - - $thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() { - $thisDetails.css({zoom: ''}); - if (defined.afterExpand) {o.afterExpand.call(thisEl);} - delayCollapse(o, $thisDetails, thisEl); - }); - } - - }); // this.each - - function buildHTML(o, blocks) { - var el = 'span', - summary = o.summary; - if ( blocks ) { - el = 'div'; - // tuck the moreLabel inside the last close tag - summary = summary.replace(/(<\/[^>]+>)\s*$/, o.moreLabel + '$1'); - - // and wrap it in a div - summary = '
' + summary + '
'; - } else { - summary += o.moreLabel; - } - - return [ - summary, - '<', - el + ' class="' + o.detailClass + '"', - '>', - o.details, - '' - ].join(''); - } - - function buildMoreLabel(o) { - var ret = '' + o.expandPrefix; - ret += '' + o.expandText + ''; - return ret; - } - - function backup(txt, preserveWords) { - if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) { - txt = txt.slice( 0, txt.lastIndexOf('<') ); - } - if (preserveWords) { - txt = txt.replace(rAmpWordEnd,''); - } - return txt; - } - - function reCollapse(o, el) { - el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() { - var prevMore = el.prev('span.' + o.moreClass).show(); - if (!prevMore.length) { - el.parent().children('div.' + o.summaryClass).show() - .find('span.' + o.moreClass).show(); - } - }); - } - - function delayCollapse(option, $collapseEl, thisEl) { - if (option.collapseTimer) { - delayedCollapse = setTimeout(function() { - reCollapse(option, $collapseEl); - if ( $.isFunction(option.onCollapse) ) { - option.onCollapse.call(thisEl, false); - } - }, option.collapseTimer); - } - } - - return this; - }; - - // plugin defaults - $.fn.expander.defaults = $.expander.defaults; -})(jQuery);