diff --git a/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee b/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee
new file mode 100644
index 0000000000..bd251de39f
--- /dev/null
+++ b/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee
@@ -0,0 +1,88 @@
+describe "DiscussionThreadView", ->
+ beforeEach ->
+ setFixtures(
+ """
+
+
+ """
+ )
+
+ 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")
diff --git a/common/static/coffee/src/discussion/views/discussion_thread_view.coffee b/common/static/coffee/src/discussion/views/discussion_thread_view.coffee
index e79dafacdd..95cda6b255 100644
--- a/common/static/coffee/src/discussion/views/discussion_thread_view.coffee
+++ b/common/static/coffee/src/discussion/views/discussion_thread_view.coffee
@@ -1,6 +1,9 @@
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($("").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 = $("