Addressed further review notes for Library Pagination

This commit is contained in:
Jonathan Piacenti
2014-12-12 19:19:56 +00:00
committed by E. Kolpakov
parent 7188c3a328
commit 074e4cfa22
18 changed files with 334 additions and 244 deletions

View File

@@ -239,7 +239,7 @@ define([
"js/spec/views/assets_spec",
"js/spec/views/baseview_spec",
"js/spec/views/container_spec",
"js/spec/views/library_container_spec",
"js/spec/views/paged_container_spec",
"js/spec/views/group_configuration_spec",
"js/spec/views/paging_spec",
"js/spec/views/unit_outline_spec",

View File

@@ -7,11 +7,11 @@ function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
'use strict';
return function (componentTemplates, XBlockInfoJson, action, options) {
var main_options = {
el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
action: action,
templates: new ComponentTemplates(componentTemplates, {parse: true})
};
el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
action: action,
templates: new ComponentTemplates(componentTemplates, {parse: true})
};
xmoduleLoader.done(function () {
var view = new ContainerPage(_.extend(main_options, options));

View File

@@ -1,20 +1,21 @@
define([
'jquery', 'underscore', 'js/models/xblock_info', 'js/views/pages/container',
'js/collections/component_template', 'xmodule', 'coffee/src/main',
'jquery', 'underscore', 'js/models/xblock_info', 'js/views/pages/paged_container',
'js/views/library_container', 'js/collections/component_template', 'xmodule', 'coffee/src/main',
'xblock/cms.runtime.v1'
],
function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
function($, _, XBlockInfo, PagedContainerPage, LibraryContainerView, ComponentTemplates, xmoduleLoader) {
'use strict';
return function (componentTemplates, XBlockInfoJson, options) {
var main_options = {
el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
templates: new ComponentTemplates(componentTemplates, {parse: true}),
action: 'view'
action: 'view',
viewClass: LibraryContainerView
};
xmoduleLoader.done(function () {
var view = new ContainerPage(_.extend(main_options, options));
var view = new PagedContainerPage(_.extend(main_options, options));
view.render();
});
};

View File

@@ -1,6 +1,6 @@
define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/models/xblock_info",
"js/views/library_container", "js/views/paging_header", "js/views/paging_footer"],
function ($, _, AjaxHelpers, URI, XBlockInfo, PagedContainer, PagingContainer, PagingFooter) {
"js/views/paged_container", "js/views/paging_header", "js/views/paging_footer"],
function ($, _, AjaxHelpers, URI, XBlockInfo, PagedContainer, PagingHeader, PagingFooter) {
var htmlResponseTpl = _.template('' +
'<div class="xblock-container-paging-parameters" data-start="<%= start %>" data-displayed="<%= displayed %>" data-total="<%= total %>"/>'

View File

@@ -1,7 +1,7 @@
define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_helpers",
"js/common_helpers/template_helpers", "js/spec_helpers/edit_helpers",
"js/views/pages/container", "js/models/xblock_info", "jquery.simulate"],
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, XBlockInfo) {
"js/views/pages/container", "js/views/pages/paged_container", "js/models/xblock_info"],
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, PagedContainerPage, XBlockInfo) {
function parameterized_suite(label, global_page_options, fixtures) {
describe(label + " ContainerPage", function () {
@@ -13,7 +13,8 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
mockBadContainerXBlockHtml = readFixtures('mock/mock-bad-javascript-container-xblock.underscore'),
mockBadXBlockContainerXBlockHtml = readFixtures('mock/mock-bad-xblock-container-xblock.underscore'),
mockUpdatedContainerXBlockHtml = readFixtures('mock/mock-updated-container-xblock.underscore'),
mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore');
mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore'),
PageClass = fixtures.page;
beforeEach(function () {
var newDisplayName = 'New Display Name';
@@ -62,7 +63,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
templates: EditHelpers.mockComponentTemplates,
el: $('#content')
};
return new ContainerPage(_.extend(options || {}, global_page_options, default_options));
return new PageClass(_.extend(options || {}, global_page_options, default_options));
};
renderContainerPage = function (test, html, options) {
@@ -273,7 +274,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
});
describe("xblock operations", function () {
var getGroupElement, paginated,
var getGroupElement, paginated, getDeleteOffset,
NUM_COMPONENTS_PER_GROUP = 3, GROUP_TO_TEST = "A",
allComponentsInGroup = _.map(
_.range(NUM_COMPONENTS_PER_GROUP),
@@ -283,9 +284,13 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
);
paginated = function () {
return containerPage.enable_paging;
return containerPage instanceof PagedContainerPage;
};
getDeleteOffset = function () {
// Paginated containers will make an additional AJAX request.
return paginated() ? 3 : 2;
};
getGroupElement = function () {
return containerPage.$("[data-locator='locator-group-" + GROUP_TO_TEST + "']");
@@ -316,8 +321,6 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
deleteComponent = function (componentIndex, requestOffset) {
clickDelete(componentIndex);
AjaxHelpers.respondWithJson(requests, {});
// second to last request contains given component's id (to delete the component)
AjaxHelpers.expectJsonRequest(requests, 'DELETE',
'/xblock/locator-component-' + GROUP_TO_TEST + (componentIndex + 1),
null, requests.length - requestOffset);
@@ -329,8 +332,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
deleteComponentWithSuccess = function (componentIndex) {
var deleteOffset;
deleteOffset = paginated() ? 3 : 2;
deleteOffset = getDeleteOffset();
deleteComponent(componentIndex, deleteOffset);
// verify the new list of components within the group
@@ -356,17 +358,12 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
});
it("can delete an xblock with broken JavaScript", function () {
var deleteOffset = getDeleteOffset();
renderContainerPage(this, mockBadContainerXBlockHtml);
containerPage.$('.delete-button').first().click();
EditHelpers.confirmPrompt(promptSpy);
AjaxHelpers.respondWithJson(requests, {});
var deleteOffset;
if (paginated()) {
deleteOffset = 3;
} else {
deleteOffset = 2;
}
// expect the second to last request to be a delete of the xblock
AjaxHelpers.expectJsonRequest(requests, 'DELETE', '/xblock/locator-broken-javascript',
null, requests.length - deleteOffset);
@@ -528,7 +525,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
});
describe('Template Picker', function () {
var showTemplatePicker, verifyCreateHtmlComponent, call_count;
var showTemplatePicker, verifyCreateHtmlComponent;
showTemplatePicker = function () {
containerPage.$('.new-component .new-component-type a.multiple-templates')[0].click();
@@ -536,7 +533,6 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
verifyCreateHtmlComponent = function (test, templateIndex, expectedRequest) {
var xblockCount;
// call_count = paginated() ? 18: 10;
renderContainerPage(test, mockContainerXBlockHtml);
showTemplatePicker();
xblockCount = containerPage.$('.studio-xblock-wrapper').length;
@@ -568,12 +564,17 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}
parameterized_suite("Non paged",
{ enable_paging: false },
{ initial: 'mock/mock-container-xblock.underscore', add_response: 'mock/mock-xblock.underscore' }
{ },
{
page: ContainerPage,
initial: 'mock/mock-container-xblock.underscore',
add_response: 'mock/mock-xblock.underscore'
}
);
parameterized_suite("Paged",
{ enable_paging: true, page_size: 42 },
{ page_size: 42 },
{
page: PagedContainerPage,
initial: 'mock/mock-container-paged-xblock.underscore',
add_response: 'mock/mock-xblock-paged.underscore'
});

View File

@@ -9,6 +9,8 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
// child xblocks within the page.
requestToken: "",
new_child_view: 'reorderable_container_child_preview',
xblockReady: function () {
XBlockView.prototype.xblockReady.call(this);
var reorderableClass, reorderableContainer,

View File

@@ -1,6 +1,5 @@
define(["jquery", "underscore", "js/views/paged_container", "js/utils/module", "gettext", "js/views/feedback_notification",
"js/views/paging_header", "js/views/paging_footer"],
function ($, _, PagedContainerView) {
define(["js/views/paged_container"],
function (PagedContainerView) {
// To be extended with Library-specific features later.
var LibraryContainerView = PagedContainerView;
return LibraryContainerView;

View File

@@ -5,9 +5,13 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
initialize: function(options){
var self = this;
ContainerView.prototype.initialize.call(this);
this.page_size = this.options.page_size || 10;
this.page_reload_callback = options.page_reload_callback || function () {};
// emulating Backbone.paginator interface
this.page_size = this.options.page_size;
// Reference to the page model
this.page = options.page;
// XBlocks are rendered via Django views and templates rather than underscore templates, and so don't
// have a Backbone model for us to manipulate in a backbone collection. Here, we emulate the interface
// of backbone.paginator so that we can use the Paging Header and Footer with this page. As a
// consequence, however, we have to manipulate its members manually.
this.collection = {
currentPage: 0,
totalPages: 0,
@@ -15,18 +19,23 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
sortDirection: "desc",
start: 0,
_size: 0,
bind: function() {}, // no-op
// Paging header and footer expect this to be a Backbone model they can listen to for changes, but
// they cannot. Provide the bind function for them, but have it do nothing.
bind: function() {},
// size() on backbone collections shows how many objects are in the collection, or in the case
// of paginator, on the current page.
size: function() { return self.collection._size; }
};
},
new_child_view: 'container_child_preview',
render: function(options) {
var eff_options = options || {};
eff_options.page_number = typeof eff_options.page_number !== "undefined"
? eff_options.page_number
options = options || {};
options.page_number = typeof options.page_number !== "undefined"
? options.page_number
: this.collection.currentPage;
return this.renderPage(eff_options);
return this.renderPage(options);
},
renderPage: function(options){
@@ -43,16 +52,15 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
success: function(fragment) {
self.handleXBlockFragment(fragment, options);
self.processPaging({ requested_page: options.page_number });
// This is expected to render the add xblock components menu.
self.page_reload_callback(self.$el)
self.page.renderAddXBlockComponents()
}
});
},
getRenderParameters: function(page_number) {
return {
enable_paging: true,
page_size: this.page_size,
enable_paging: true,
page_number: page_number
};
},
@@ -67,6 +75,8 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
},
processPaging: function(options){
// We have the Django template sneak us the pagination information,
// and we load it from a div here.
var $element = this.$el.find('.xblock-container-paging-parameters'),
total = $element.data('total'),
displayed = $element.data('displayed'),
@@ -82,6 +92,8 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
},
processPagingHeaderAndFooter: function(){
// Rendering the container view detaches the header and footer from the DOM.
// It's just as easy to recreate them as it is to try to shove them back into the tree.
if (this.pagingHeader)
this.pagingHeader.undelegateEvents();
if (this.pagingFooter)
@@ -100,12 +112,6 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
this.pagingFooter.render();
},
xblockReady: function () {
ContainerView.prototype.xblockReady.call(this);
this.requestToken = this.$('div.xblock').first().data('request-token');
},
refresh: function(block_added) {
if (block_added) {
this.collection.totalCount += 1;
@@ -150,7 +156,7 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
},
sortDisplayName: function() {
return "Date added"; // TODO add support for sorting
return gettext("Date added"); // TODO add support for sorting
}
});

View File

@@ -3,10 +3,10 @@
* This page allows the user to understand and manipulate the xblock and its children.
*/
define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views/utils/view_utils",
"js/views/container", "js/views/library_container", "js/views/xblock", "js/views/components/add_xblock", "js/views/modals/edit_xblock",
"js/views/container", "js/views/xblock", "js/views/components/add_xblock", "js/views/modals/edit_xblock",
"js/models/xblock_info", "js/views/xblock_string_field_editor", "js/views/pages/container_subviews",
"js/views/unit_outline", "js/views/utils/xblock_utils"],
function ($, _, gettext, BasePage, ViewUtils, ContainerView, PagedContainerView, XBlockView, AddXBlockComponent,
function ($, _, gettext, BasePage, ViewUtils, ContainerView, XBlockView, AddXBlockComponent,
EditXBlockModal, XBlockInfo, XBlockStringFieldEditor, ContainerSubviews, UnitOutlineView,
XBlockUtils) {
'use strict';
@@ -25,12 +25,16 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
view: 'container_preview',
defaultViewClass: ContainerView,
// Overridable by subclasses-- determines whether the XBlock component
// addition menu is added on initialization. You may set this to false
// if your subclass handles it.
components_on_init: true,
initialize: function(options) {
BasePage.prototype.initialize.call(this, options);
this.enable_paging = options.enable_paging || false;
if (this.enable_paging) {
this.page_size = options.page_size || 10;
}
this.viewClass = options.viewClass || this.defaultViewClass;
this.nameEditor = new XBlockStringFieldEditor({
el: this.$('.wrapper-xblock-field'),
model: this.model
@@ -75,26 +79,16 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
}
},
getXBlockView: function(){
var self = this,
parameters = {
el: this.$('.wrapper-xblock'),
model: this.model,
view: this.view
};
getViewParameters: function () {
return {
el: this.$('.wrapper-xblock'),
model: this.model,
view: this.view
}
},
if (this.enable_paging) {
parameters = _.extend(parameters, {
page_size: this.page_size,
page_reload_callback: function($element) {
self.renderAddXBlockComponents();
}
});
return new PagedContainerView(parameters);
}
else {
return new ContainerView(parameters);
}
getXBlockView: function(){
return new this.viewClass(this.getViewParameters());
},
render: function(options) {
@@ -120,7 +114,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
xblockView.notifyRuntime('page-shown', self);
// Render the add buttons. Paged containers should do this on their own.
if (!self.enable_paging) {
if (self.components_on_init) {
// Render the add buttons
self.renderAddXBlockComponents();
}
@@ -277,7 +271,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
rootLocator = this.xblockView.model.id;
if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) {
this.render({refresh: true, block_added: block_added});
} else if (parentElement.hasClass('reorderable-container') || this.enable_paging) {
} else if (parentElement.hasClass('reorderable-container')) {
this.refreshChildXBlock(xblockElement, block_added);
} else {
this.refreshXBlock(this.findXBlockElement(parentElement));
@@ -313,7 +307,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
});
temporaryView = new TemporaryXBlockView({
model: xblockInfo,
view: 'reorderable_container_child_preview',
view: self.xblockView.new_child_view,
el: xblockElement
});
return temporaryView.render({

View File

@@ -0,0 +1,36 @@
/**
* PagedXBlockContainerPage is a variant of XBlockContainerPage that supports Pagination.
*/
define(["jquery", "underscore", "gettext", "js/views/pages/container", "js/views/paged_container"],
function ($, _, gettext, XBlockContainerPage, PagedContainerView) {
'use strict';
var PagedXBlockContainerPage = XBlockContainerPage.extend({
defaultViewClass: PagedContainerView,
components_on_init: false,
initialize: function (options){
this.page_size = options.page_size || 10;
XBlockContainerPage.prototype.initialize.call(this, options);
},
getViewParameters: function () {
return _.extend(XBlockContainerPage.prototype.getViewParameters.call(this), {
page_size: this.page_size,
page: this
});
},
refreshXBlock: function(element, block_added) {
var xblockElement = this.findXBlockElement(element),
rootLocator = this.xblockView.model.id;
if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) {
this.render({refresh: true, block_added: block_added});
} else {
this.refreshChildXBlock(xblockElement, block_added);
}
}
});
return PagedXBlockContainerPage;
});

View File

@@ -44,6 +44,8 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) {
if (pageNumber <= 0) {
pageNumber = false;
}
// If we still have a page number by this point,
// and it's not the current page, load it.
if (pageNumber && pageNumber !== currentPage) {
view.setPage(pageNumber - 1);
}

View File

@@ -1,5 +1,5 @@
define(["jquery", "underscore"],
function ($, _) {
define([],
function () {
var PagedMixin = {
setPage: function (page) {
var self = this,