Commit 062025ee by Greg Price

Add pagination of repsonses to forum threads

Users will first be shown the first 25 responses of a viewed thread and
then have the option of loading 100 more at a time until all responses
have been loaded. This mitigates the performance impact of very large
threads by avoiding the need to render and transfer over the network
all content of a large thread at once. It will also allow students with
slower browsers to interact with large threads even if they cannot load
all of the responses.
parent fba6f2eb
describe "DiscussionThreadView", ->
beforeEach ->
setFixtures(
"""
<script type="text/template" id="thread-template">
<article class="discussion-article">
<div class="response-count"/>
<ol class="responses"/>
<div class="response-pagination"/>
</article>
</script>
<div class="thread-fixture"/>
"""
)
jasmine.Clock.useMock()
@threadData = {
id: "dummy"
}
@thread = new Thread(@threadData)
@view = new DiscussionThreadView({ model: @thread })
@view.setElement($(".thread-fixture"))
spyOn($, "ajax")
# Avoid unnecessary boilerplate
spyOn(@view.showView, "render")
spyOn(@view, "makeWmdEditor")
spyOn(DiscussionThreadView.prototype, "renderResponse")
describe "response count and pagination", ->
setNextResponseContent = (content) ->
$.ajax.andCallFake(
(params) =>
params.success({"content": content})
{always: ->}
)
renderWithContent = (view, content) ->
setNextResponseContent(content)
view.render()
jasmine.Clock.tick(100)
assertRenderedCorrectly = (view, countText, displayCountText, buttonText) ->
expect(view.$el.find(".response-count").text()).toEqual(countText)
if displayCountText
expect(view.$el.find(".response-display-count").text()).toEqual(displayCountText)
else
expect(view.$el.find(".response-display-count").length).toEqual(0)
if buttonText
expect(view.$el.find(".load-response-button").text()).toEqual(buttonText)
else
expect(view.$el.find(".load-response-button").length).toEqual(0)
it "correctly render for a thread with no responses", ->
renderWithContent(@view, {resp_total: 0, children: []})
assertRenderedCorrectly(@view, "0 responses", null, null)
it "correctly render for a thread with one response", ->
renderWithContent(@view, {resp_total: 1, children: [{}]})
assertRenderedCorrectly(@view, "1 response", "Showing all responses", null)
it "correctly render for a thread with one additional page", ->
renderWithContent(@view, {resp_total: 2, children: [{}]})
assertRenderedCorrectly(@view, "2 responses", "Showing first response", "Load all responses")
it "correctly render for a thread with multiple additional pages", ->
renderWithContent(@view, {resp_total: 111, children: [{}, {}]})
assertRenderedCorrectly(@view, "111 responses", "Showing first 2 responses", "Load next 100 responses")
describe "on clicking the load more button", ->
beforeEach ->
renderWithContent(@view, {resp_total: 5, children: [{}]})
assertRenderedCorrectly(@view, "5 responses", "Showing first response", "Load all responses")
it "correctly re-render when all threads have loaded", ->
setNextResponseContent({resp_total: 5, children: [{}, {}, {}, {}]})
@view.$el.find(".load-response-button").click()
assertRenderedCorrectly(@view, "5 responses", "Showing all responses", null)
it "correctly re-render when one page remains", ->
setNextResponseContent({resp_total: 42, children: [{}, {}]})
@view.$el.find(".load-response-button").click()
assertRenderedCorrectly(@view, "42 responses", "Showing first 3 responses", "Load all responses")
it "correctly re-render when multiple pages remain", ->
setNextResponseContent({resp_total: 111, children: [{}, {}]})
@view.$el.find(".load-response-button").click()
assertRenderedCorrectly(@view, "111 responses", "Showing first 3 responses", "Load next 100 responses")
if Backbone?
class @DiscussionThreadView extends DiscussionContentView
INITIAL_RESPONSE_PAGE_SIZE = 25
SUBSEQUENT_RESPONSE_PAGE_SIZE = 100
events:
"click .discussion-submit-post": "submitComment"
"click .add-response-btn": "scrollToAddResponse"
......@@ -11,6 +14,7 @@ if Backbone?
initialize: ->
super()
@createShowView()
@responses = new Comments()
renderTemplate: ->
@template = _.template($("#thread-template").html())
......@@ -18,7 +22,6 @@ if Backbone?
render: ->
@$el.html(@renderTemplate())
@$el.find(".loading").hide()
@delegateEvents()
@renderShowView()
......@@ -27,26 +30,95 @@ if Backbone?
@$("span.timeago").timeago()
@makeWmdEditor "reply-body"
@renderAddResponseButton()
@renderResponses()
@responses.on("add", @renderResponse)
# Without a delay, jQuery doesn't add the loading extension defined in
# utils.coffee before safeAjax is invoked, which results in an error
setTimeout(
=> @loadResponses(INITIAL_RESPONSE_PAGE_SIZE, @$el.find(".responses"), true),
100
)
@
cleanup: ->
if @responsesRequest?
@responsesRequest.abort()
renderResponses: ->
setTimeout(=>
@$el.find(".loading").show()
, 200)
loadResponses: (responseLimit, elem, firstLoad) ->
@responsesRequest = DiscussionUtil.safeAjax
url: DiscussionUtil.urlFor('retrieve_single_thread', @model.get('commentable_id'), @model.id)
data:
resp_skip: @responses.size()
resp_limit: responseLimit if responseLimit
$elem: elem
$loading: elem
takeFocus: true
complete: =>
@responseRequest = null
success: (data, textStatus, xhr) =>
@responsesRequest = null
@$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info'])
comments = new Comments(data['content']['children'])
comments.each @renderResponse
@responses.add(data['content']['children'])
@renderResponseCountAndPagination(data['content']['resp_total'])
@trigger "thread:responses:rendered"
error: =>
if firstLoad
DiscussionUtil.discussionAlert(
gettext("Sorry"),
gettext("We had some trouble loading responses. Please reload the page.")
)
else
DiscussionUtil.discussionAlert(
gettext("Sorry"),
gettext("We had some trouble loading more responses. Please try again.")
)
renderResponseCountAndPagination: (responseTotal) =>
@$el.find(".response-count").html(
interpolate(
ngettext(
"%(numResponses)s response",
"%(numResponses)s responses",
responseTotal
),
{numResponses: responseTotal},
true
)
)
responsePagination = @$el.find(".response-pagination")
responsePagination.empty()
if responseTotal > 0
responsesRemaining = responseTotal - @responses.size()
showingResponsesText =
if responsesRemaining == 0
gettext("Showing all responses")
else
interpolate(
ngettext(
"Showing first response",
"Showing first %(numResponses)s responses",
@responses.size()
),
{numResponses: @responses.size()},
true
)
responsePagination.append($("<span>").addClass("response-display-count").html(
_.escape(showingResponsesText)
))
if responsesRemaining > 0
if responsesRemaining < SUBSEQUENT_RESPONSE_PAGE_SIZE
responseLimit = null
buttonText = gettext("Load all responses")
else
responseLimit = SUBSEQUENT_RESPONSE_PAGE_SIZE
buttonText = interpolate(
gettext("Load next %(numResponses)s responses"),
{numResponses: responseLimit},
true
)
loadMoreButton = $("<button>").addClass("load-response-button").html(
_.escape(buttonText)
)
loadMoreButton.click((event) => @loadResponses(responseLimit, loadMoreButton))
responsePagination.append(loadMoreButton)
renderResponse: (response) =>
response.set('thread', @model)
......
......@@ -1398,7 +1398,8 @@ body.discussion {
}
.discussion-post {
padding: $baseline*2 $baseline*2 $baseline/2 $baseline*2;
padding: $baseline*2 $baseline*2 $baseline $baseline*2;
box-shadow: 0 1px 3px $shadow;
> header .vote-btn {
position: relative;
......@@ -1805,7 +1806,7 @@ body.discussion {
.discussion-reply-new {
padding: 0px 30px $baseline;
padding: 0.5*$baseline 30px $baseline;
@include clearfix;
@include transition(opacity .2s linear 0s);
......@@ -2564,3 +2565,32 @@ display:none;
color: #333;
font-style: italic;
}
.response-count {
margin-top: $baseline;
padding: 0px 3*$baseline;
}
.response-pagination {
padding: 0px 1.5*$baseline;
.response-display-count {
display: block;
padding: 0.5*$baseline 1.5*$baseline;
}
.load-response-button {
display: block;
@include white-button;
font: normal 1em/1.6em $sans-serif;
position: relative;
padding: 0px 1.5*$baseline;
margin: $baseline/2 0px;
border: 1px solid #b2b2b2;
box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
font-size: 13px;
text-align: left;
@include animation(fadeIn .3s);
width: 100%;
}
}
......@@ -5,15 +5,15 @@
<script type="text/template" id="thread-template">
<article class="discussion-article" data-id="${'<%- id %>'}">
<div class="thread-content-wrapper"></div>
<div class="response-count"/>
<div class="add-response">
<button class="button add-response-btn">
<i class="icon icon-reply"></i>
<span class="add-response-btn-text">${_('Add A Response')}</span>
</button>
</div>
<ol class="responses">
<li class="loading"><div class="loading-animation"><span class="sr">${_('Loading content')}</span></div></li>
</ol>
<ol class="responses"/>
<div class="response-pagination"/>
<div class="post-status-closed bottom-post-status" style="display: none">
${_("This thread is closed.")}
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment