Commit ff1a08cb by E. Kolpakov

Paging for LibraryView added with JS tests.

parent 05817614
...@@ -237,12 +237,28 @@ def xblock_view_handler(request, usage_key_string, view_name): ...@@ -237,12 +237,28 @@ def xblock_view_handler(request, usage_key_string, view_name):
if view_name == 'reorderable_container_child_preview': if view_name == 'reorderable_container_child_preview':
reorderable_items.add(xblock.location) reorderable_items.add(xblock.location)
paging = None
try:
if request.REQUEST.get('enable_paging', 'false') == 'true':
paging = {
'page_number': int(request.REQUEST.get('page_number', 0)),
'page_size': int(request.REQUEST.get('page_size', 0)),
}
except ValueError:
log.exception(
"Couldn't parse paging parameters: enable_paging: %s, page_number: %s, page_size: %s",
request.REQUEST.get('enable_paging', 'false'),
request.REQUEST.get('page_number', 0),
request.REQUEST.get('page_size', 0)
)
# Set up the context to be passed to each XBlock's render method. # Set up the context to be passed to each XBlock's render method.
context = { context = {
'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks
'is_unit_page': is_unit(xblock), 'is_unit_page': is_unit(xblock),
'root_xblock': xblock if (view_name == 'container_preview') else None, 'root_xblock': xblock if (view_name == 'container_preview') else None,
'reorderable_items': reorderable_items 'reorderable_items': reorderable_items,
'paging': paging
} }
fragment = get_preview_fragment(request, xblock, context) fragment = get_preview_fragment(request, xblock, context)
......
...@@ -239,6 +239,7 @@ define([ ...@@ -239,6 +239,7 @@ define([
"js/spec/views/assets_spec", "js/spec/views/assets_spec",
"js/spec/views/baseview_spec", "js/spec/views/baseview_spec",
"js/spec/views/container_spec", "js/spec/views/container_spec",
"js/spec/views/library_container_spec",
"js/spec/views/group_configuration_spec", "js/spec/views/group_configuration_spec",
"js/spec/views/paging_spec", "js/spec/views/paging_spec",
"js/spec/views/unit_outline_spec", "js/spec/views/unit_outline_spec",
......
define([ define([
'jquery', 'js/models/xblock_info', 'js/views/pages/container', 'jquery', 'underscore', 'js/models/xblock_info', 'js/views/pages/container',
'js/collections/component_template', 'xmodule', 'coffee/src/main', 'js/collections/component_template', 'xmodule', 'coffee/src/main',
'xblock/cms.runtime.v1' 'xblock/cms.runtime.v1'
], ],
function($, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) { function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
'use strict'; 'use strict';
return function (componentTemplates, XBlockInfoJson, action, isUnitPage) { return function (componentTemplates, XBlockInfoJson, action, options) {
var templates = new ComponentTemplates(componentTemplates, {parse: true}), var main_options = {
mainXBlockInfo = new XBlockInfo(XBlockInfoJson, {parse: true});
xmoduleLoader.done(function () {
var view = new ContainerPage({
el: $('#content'), el: $('#content'),
model: mainXBlockInfo, model: new XBlockInfo(XBlockInfoJson, {parse: true}),
action: action, action: action,
templates: templates, templates: new ComponentTemplates(componentTemplates, {parse: true})
isUnitPage: isUnitPage };
});
xmoduleLoader.done(function () {
var view = new ContainerPage(_.extend(main_options, options));
view.render(); view.render();
}); });
}; };
......
define([ define([
'jquery', 'js/models/xblock_info', 'js/views/pages/container', 'jquery', 'underscore', 'js/models/xblock_info', 'js/views/pages/container',
'js/collections/component_template', 'xmodule', 'coffee/src/main', 'js/collections/component_template', 'xmodule', 'coffee/src/main',
'xblock/cms.runtime.v1' 'xblock/cms.runtime.v1'
], ],
function($, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) { function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
'use strict'; 'use strict';
return function (componentTemplates, XBlockInfoJson) { return function (componentTemplates, XBlockInfoJson, options) {
var templates = new ComponentTemplates(componentTemplates, {parse: true}), var main_options = {
mainXBlockInfo = new XBlockInfo(XBlockInfoJson, {parse: true}); el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
templates: new ComponentTemplates(componentTemplates, {parse: true}),
action: 'view'
};
xmoduleLoader.done(function () { xmoduleLoader.done(function () {
var view = new ContainerPage({ var view = new ContainerPage(_.extend(main_options, options));
el: $('#content'),
model: mainXBlockInfo,
action: "view",
templates: templates,
isUnitPage: false
});
view.render(); view.render();
}); });
}; };
......
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) {
var htmlResponseTpl = _.template('' +
'<div class="xblock-container-paging-parameters" data-start="<%= start %>" data-displayed="<%= displayed %>" data-total="<%= total %>"/>'
);
function getResponseHtml(options){
return '<div class="xblock" data-request-token="request_token">' +
'<div class="container-paging-header"></div>' +
htmlResponseTpl(options) +
'<div class="container-paging-footer"></div>' +
'</div>'
}
var PAGE_SIZE = 3;
var mockFirstPage = {
resources: [],
html: getResponseHtml({
start: 0,
displayed: PAGE_SIZE,
total: PAGE_SIZE + 1
})
};
var mockSecondPage = {
resources: [],
html: getResponseHtml({
start: PAGE_SIZE,
displayed: 1,
total: PAGE_SIZE + 1
})
};
var mockEmptyPage = {
resources: [],
html: getResponseHtml({
start: 0,
displayed: 0,
total: 0
})
};
var respondWithMockPage = function(requests) {
var requestIndex = requests.length - 1;
var request = requests[requestIndex];
var url = new URI(request.url);
var queryParameters = url.query(true); // Returns an object with each query parameter stored as a value
var page = queryParameters.page_number;
var response = page === "0" ? mockFirstPage : mockSecondPage;
AjaxHelpers.respondWithJson(requests, response, requestIndex);
};
var MockPagingView = PagedContainer.extend({
view: 'container_preview',
el: $("<div><div class='xblock' data-request-token='test_request_token'/></div>"),
model: new XBlockInfo({}, {parse: true})
});
describe("Paging Container", function() {
var pagingContainer;
beforeEach(function () {
var feedbackTpl = readFixtures('system-feedback.underscore');
setFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" }).text(feedbackTpl));
pagingContainer = new MockPagingView({ page_size: PAGE_SIZE });
});
describe("Container", function () {
describe("setPage", function () {
it('can set the current page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(0);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('should not change page after a server error', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.setPage(1);
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(0);
});
});
describe("nextPage", function () {
it('does not move forward after a server error', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.nextPage();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.nextPage();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('can not move forward from the final page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.nextPage();
expect(requests.length).toBe(1);
});
});
describe("previousPage", function () {
it('can move back a page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.previousPage();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('can not move back from the first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.previousPage();
expect(requests.length).toBe(1);
});
it('does not move back after a server error', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.previousPage();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(1);
});
});
});
describe("PagingHeader", function () {
beforeEach(function () {
var pagingFooterTpl = readFixtures('paging-header.underscore');
appendSetFixtures($("<script>", { id: "paging-header-tpl", type: "text/template" }).text(pagingFooterTpl));
});
describe("Next page button", function () {
beforeEach(function () {
pagingContainer.render();
});
it('does not move forward if a server error occurs', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingHeader.$('.next-page-link').click();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingHeader.$('.next-page-link').click();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('should be enabled when there is at least one more page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.next-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled on the final page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.next-page-link')).toHaveClass('is-disabled');
});
it('should be disabled on an empty page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingHeader.$('.next-page-link')).toHaveClass('is-disabled');
});
});
describe("Previous page button", function () {
beforeEach(function () {
pagingContainer.render();
});
it('does not move back if a server error occurs', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.pagingHeader.$('.previous-page-link').click();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('can go back a page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.pagingHeader.$('.previous-page-link').click();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('should be disabled on the first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.previous-page-link')).toHaveClass('is-disabled');
});
it('should be enabled on the second page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.previous-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled for an empty page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingHeader.$('.previous-page-link')).toHaveClass('is-disabled');
});
});
describe("Page metadata section", function() {
it('shows the correct metadata for the current page', function () {
var requests = AjaxHelpers.requests(this),
message;
pagingContainer.setPage(0);
respondWithMockPage(requests);
message = pagingContainer.pagingHeader.$('.meta').html().trim();
expect(message).toBe('<p>Showing <span class="count-current-shown">1-3</span>' +
' out of <span class="count-total">4 total</span>, ' +
'sorted by <span class="sort-order">Date added</span> descending</p>');
});
});
describe("Children count label", function () {
it('should show correct count on first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.count-current-shown')).toHaveHtml('1-3');
});
it('should show correct count on second page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.count-current-shown')).toHaveHtml('4-4');
});
it('should show correct count for an empty collection', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingHeader.$('.count-current-shown')).toHaveHtml('0-0');
});
});
describe("Children total label", function () {
it('should show correct total on the first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.count-total')).toHaveText('4 total');
});
it('should show correct total on the second page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingHeader.$('.count-total')).toHaveText('4 total');
});
it('should show zero total for an empty collection', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingHeader.$('.count-total')).toHaveText('0 total');
});
});
});
describe("PagingFooter", function () {
var pagingFooter;
beforeEach(function () {
var pagingFooterTpl = readFixtures('paging-footer.underscore');
appendSetFixtures($("<script>", { id: "paging-footer-tpl", type: "text/template" }).text(pagingFooterTpl));
});
describe("Next page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingContainer.render();
});
it('does not move forward if a server error occurs', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.next-page-link').click();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.next-page-link').click();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('should be enabled when there is at least one more page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.next-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled on the final page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.next-page-link')).toHaveClass('is-disabled');
});
it('should be disabled on an empty page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingFooter.$('.next-page-link')).toHaveClass('is-disabled');
});
});
describe("Previous page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingContainer.render();
});
it('does not move back if a server error occurs', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.previous-page-link').click();
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(1);
});
it('can go back a page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.previous-page-link').click();
respondWithMockPage(requests);
expect(pagingContainer.collection.currentPage).toBe(0);
});
it('should be disabled on the first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.previous-page-link')).toHaveClass('is-disabled');
});
it('should be enabled on the second page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.previous-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled for an empty page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingFooter.$('.previous-page-link')).toHaveClass('is-disabled');
});
});
describe("Current page label", function () {
it('should show 1 on the first page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.current-page')).toHaveText('1');
});
it('should show 2 on the second page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(1);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.current-page')).toHaveText('2');
});
it('should show 1 for an empty collection', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingFooter.$('.current-page')).toHaveText('1');
});
});
describe("Page total label", function () {
it('should show the correct value with more than one page', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.total-pages')).toHaveText('2');
});
it('should show page 1 when there are no assets', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingContainer.pagingFooter.$('.total-pages')).toHaveText('1');
});
});
describe("Page input field", function () {
var input;
it('should initially have a blank page input', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
expect(pagingContainer.pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should handle invalid page requests', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.page-number-input').val('abc');
pagingContainer.pagingFooter.$('.page-number-input').trigger('change');
expect(pagingContainer.collection.currentPage).toBe(0);
expect(pagingContainer.pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should switch pages via the input field', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.page-number-input').val('2');
pagingContainer.pagingFooter.$('.page-number-input').trigger('change');
AjaxHelpers.respondWithJson(requests, mockSecondPage);
expect(pagingContainer.collection.currentPage).toBe(1);
expect(pagingContainer.pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should handle AJAX failures when switching pages via the input field', function () {
var requests = AjaxHelpers.requests(this);
pagingContainer.setPage(0);
respondWithMockPage(requests);
pagingContainer.pagingFooter.$('.page-number-input').val('2');
pagingContainer.pagingFooter.$('.page-number-input').trigger('change');
requests[1].respond(500);
expect(pagingContainer.collection.currentPage).toBe(0);
expect(pagingContainer.pagingFooter.$('.page-number-input')).toHaveValue('');
});
});
});
});
});
...@@ -3,11 +3,13 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -3,11 +3,13 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
"js/views/pages/container", "js/models/xblock_info", "jquery.simulate"], "js/views/pages/container", "js/models/xblock_info", "jquery.simulate"],
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, XBlockInfo) { function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, XBlockInfo) {
describe("ContainerPage", function() { function parameterized_suite(label, global_page_options, fixtures) {
var lastRequest, renderContainerPage, expectComponents, respondWithHtml, describe(label + " ContainerPage", function () {
var lastRequest, getContainerPage, renderContainerPage, expectComponents, respondWithHtml,
model, containerPage, requests, initialDisplayName, model, containerPage, requests, initialDisplayName,
mockContainerPage = readFixtures('mock/mock-container-page.underscore'), mockContainerPage = readFixtures('mock/mock-container-page.underscore'),
mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore'), mockContainerXBlockHtml = readFixtures(fixtures.initial),
mockXBlockHtml = readFixtures(fixtures.add_response),
mockBadContainerXBlockHtml = readFixtures('mock/mock-bad-javascript-container-xblock.underscore'), mockBadContainerXBlockHtml = readFixtures('mock/mock-bad-javascript-container-xblock.underscore'),
mockBadXBlockContainerXBlockHtml = readFixtures('mock/mock-bad-xblock-container-xblock.underscore'), mockBadXBlockContainerXBlockHtml = readFixtures('mock/mock-bad-xblock-container-xblock.underscore'),
mockUpdatedContainerXBlockHtml = readFixtures('mock/mock-updated-container-xblock.underscore'), mockUpdatedContainerXBlockHtml = readFixtures('mock/mock-updated-container-xblock.underscore'),
...@@ -37,13 +39,15 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -37,13 +39,15 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
afterEach(function() { afterEach(function () {
EditHelpers.uninstallMockXBlock(); EditHelpers.uninstallMockXBlock();
}); });
lastRequest = function() { return requests[requests.length - 1]; }; lastRequest = function () {
return requests[requests.length - 1];
};
respondWithHtml = function(html) { respondWithHtml = function (html) {
var requestIndex = requests.length - 1; var requestIndex = requests.length - 1;
AjaxHelpers.respondWithJson( AjaxHelpers.respondWithJson(
requests, requests,
...@@ -52,13 +56,18 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -52,13 +56,18 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
); );
}; };
renderContainerPage = function(test, html, options) { getContainerPage = function (options) {
requests = AjaxHelpers.requests(test); var default_options = {
containerPage = new ContainerPage(_.extend(options || {}, {
model: model, model: model,
templates: EditHelpers.mockComponentTemplates, templates: EditHelpers.mockComponentTemplates,
el: $('#content') el: $('#content')
})); };
return new ContainerPage(_.extend(options || {}, global_page_options, default_options));
};
renderContainerPage = function (test, html, options) {
requests = AjaxHelpers.requests(test);
containerPage = getContainerPage(options);
containerPage.render(); containerPage.render();
respondWithHtml(html); respondWithHtml(html);
}; };
...@@ -67,39 +76,40 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -67,39 +76,40 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
// verify expected components (in expected order) by their locators // verify expected components (in expected order) by their locators
var components = $(container).find('.studio-xblock-wrapper'); var components = $(container).find('.studio-xblock-wrapper');
expect(components.length).toBe(locators.length); expect(components.length).toBe(locators.length);
_.each(locators, function(locator, locator_index) { _.each(locators, function (locator, locator_index) {
expect($(components[locator_index]).data('locator')).toBe(locator); expect($(components[locator_index]).data('locator')).toBe(locator);
}); });
}; };
describe("Initial display", function() { describe("Initial display", function () {
it('can render itself', function() { it('can render itself', function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
expect(containerPage.$('.xblock-header').length).toBe(9); expect(containerPage.$('.xblock-header').length).toBe(9);
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden'); expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
}); });
it('shows a loading indicator', function() { it('shows a loading indicator', function () {
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
containerPage = getContainerPage();
containerPage.render(); containerPage.render();
expect(containerPage.$('.ui-loading')).not.toHaveClass('is-hidden'); expect(containerPage.$('.ui-loading')).not.toHaveClass('is-hidden');
respondWithHtml(mockContainerXBlockHtml); respondWithHtml(mockContainerXBlockHtml);
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden'); expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
}); });
it('can show an xblock with broken JavaScript', function() { it('can show an xblock with broken JavaScript', function () {
renderContainerPage(this, mockBadContainerXBlockHtml); renderContainerPage(this, mockBadContainerXBlockHtml);
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden'); expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden'); expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
}); });
it('can show an xblock with an invalid XBlock', function() { it('can show an xblock with an invalid XBlock', function () {
renderContainerPage(this, mockBadXBlockContainerXBlockHtml); renderContainerPage(this, mockBadXBlockContainerXBlockHtml);
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden'); expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden'); expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
}); });
it('inline edits the display name when performing a new action', function() { it('inline edits the display name when performing a new action', function () {
renderContainerPage(this, mockContainerXBlockHtml, { renderContainerPage(this, mockContainerXBlockHtml, {
action: 'new' action: 'new'
}); });
...@@ -109,19 +119,19 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -109,19 +119,19 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
describe("Editing the container", function() { describe("Editing the container", function () {
var updatedDisplayName = 'Updated Test Container', var updatedDisplayName = 'Updated Test Container',
getDisplayNameWrapper; getDisplayNameWrapper;
afterEach(function() { afterEach(function () {
EditHelpers.cancelModalIfShowing(); EditHelpers.cancelModalIfShowing();
}); });
getDisplayNameWrapper = function() { getDisplayNameWrapper = function () {
return containerPage.$('.wrapper-xblock-field'); return containerPage.$('.wrapper-xblock-field');
}; };
it('can edit itself', function() { it('can edit itself', function () {
var editButtons, displayNameElement; var editButtons, displayNameElement;
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
displayNameElement = containerPage.$('.page-header-title'); displayNameElement = containerPage.$('.page-header-title');
...@@ -161,7 +171,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -161,7 +171,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(displayNameElement.text().trim()).toBe(updatedDisplayName); expect(displayNameElement.text().trim()).toBe(updatedDisplayName);
}); });
it('can inline edit the display name', function() { it('can inline edit the display name', function () {
var displayNameInput, displayNameWrapper; var displayNameInput, displayNameWrapper;
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
displayNameWrapper = getDisplayNameWrapper(); displayNameWrapper = getDisplayNameWrapper();
...@@ -176,12 +186,12 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -176,12 +186,12 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
describe("Editing an xblock", function() { describe("Editing an xblock", function () {
afterEach(function() { afterEach(function () {
EditHelpers.cancelModalIfShowing(); EditHelpers.cancelModalIfShowing();
}); });
it('can show an edit modal for a child xblock', function() { it('can show an edit modal for a child xblock', function () {
var editButtons; var editButtons;
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
editButtons = containerPage.$('.wrapper-xblock .edit-button'); editButtons = containerPage.$('.wrapper-xblock .edit-button');
...@@ -197,7 +207,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -197,7 +207,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(EditHelpers.isShowingModal()).toBeTruthy(); expect(EditHelpers.isShowingModal()).toBeTruthy();
}); });
it('can show an edit modal for a child xblock with broken JavaScript', function() { it('can show an edit modal for a child xblock with broken JavaScript', function () {
var editButtons; var editButtons;
renderContainerPage(this, mockBadContainerXBlockHtml); renderContainerPage(this, mockBadContainerXBlockHtml);
editButtons = containerPage.$('.wrapper-xblock .edit-button'); editButtons = containerPage.$('.wrapper-xblock .edit-button');
...@@ -210,7 +220,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -210,7 +220,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
describe("Editing an xmodule", function() { describe("Editing an xmodule", function () {
var mockXModuleEditor = readFixtures('mock/mock-xmodule-editor.underscore'), var mockXModuleEditor = readFixtures('mock/mock-xmodule-editor.underscore'),
newDisplayName = 'New Display Name'; newDisplayName = 'New Display Name';
...@@ -223,12 +233,12 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -223,12 +233,12 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
afterEach(function() { afterEach(function () {
EditHelpers.uninstallMockXModule(); EditHelpers.uninstallMockXModule();
EditHelpers.cancelModalIfShowing(); EditHelpers.cancelModalIfShowing();
}); });
it('can save changes to settings', function() { it('can save changes to settings', function () {
var editButtons, modal, mockUpdatedXBlockHtml; var editButtons, modal, mockUpdatedXBlockHtml;
mockUpdatedXBlockHtml = readFixtures('mock/mock-updated-xblock.underscore'); mockUpdatedXBlockHtml = readFixtures('mock/mock-updated-xblock.underscore');
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
...@@ -262,27 +272,29 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -262,27 +272,29 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
describe("xblock operations", function() { describe("xblock operations", function () {
var getGroupElement, var getGroupElement,
NUM_COMPONENTS_PER_GROUP = 3, GROUP_TO_TEST = "A", NUM_COMPONENTS_PER_GROUP = 3, GROUP_TO_TEST = "A",
allComponentsInGroup = _.map( allComponentsInGroup = _.map(
_.range(NUM_COMPONENTS_PER_GROUP), _.range(NUM_COMPONENTS_PER_GROUP),
function(index) { return 'locator-component-' + GROUP_TO_TEST + (index + 1); } function (index) {
return 'locator-component-' + GROUP_TO_TEST + (index + 1);
}
); );
getGroupElement = function() { getGroupElement = function () {
return containerPage.$("[data-locator='locator-group-" + GROUP_TO_TEST + "']"); return containerPage.$("[data-locator='locator-group-" + GROUP_TO_TEST + "']");
}; };
describe("Deleting an xblock", function() { describe("Deleting an xblock", function () {
var clickDelete, deleteComponent, deleteComponentWithSuccess, var clickDelete, deleteComponent, deleteComponentWithSuccess,
promptSpy; promptSpy;
beforeEach(function() { beforeEach(function () {
promptSpy = EditHelpers.createPromptSpy(); promptSpy = EditHelpers.createPromptSpy();
}); });
clickDelete = function(componentIndex, clickNo) { clickDelete = function (componentIndex, clickNo) {
// find all delete buttons for the given group // find all delete buttons for the given group
var deleteButtons = getGroupElement().find(".delete-button"); var deleteButtons = getGroupElement().find(".delete-button");
...@@ -295,7 +307,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -295,7 +307,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
EditHelpers.confirmPrompt(promptSpy, clickNo); EditHelpers.confirmPrompt(promptSpy, clickNo);
}; };
deleteComponent = function(componentIndex) { deleteComponent = function (componentIndex) {
clickDelete(componentIndex); clickDelete(componentIndex);
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
...@@ -308,7 +320,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -308,7 +320,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container'); AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
}; };
deleteComponentWithSuccess = function(componentIndex) { deleteComponentWithSuccess = function (componentIndex) {
deleteComponent(componentIndex); deleteComponent(componentIndex);
// verify the new list of components within the group // verify the new list of components within the group
...@@ -318,22 +330,22 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -318,22 +330,22 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
); );
}; };
it("can delete the first xblock", function() { it("can delete the first xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
deleteComponentWithSuccess(0); deleteComponentWithSuccess(0);
}); });
it("can delete a middle xblock", function() { it("can delete a middle xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
deleteComponentWithSuccess(1); deleteComponentWithSuccess(1);
}); });
it("can delete the last xblock", function() { it("can delete the last xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
deleteComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1); deleteComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
}); });
it("can delete an xblock with broken JavaScript", function() { it("can delete an xblock with broken JavaScript", function () {
renderContainerPage(this, mockBadContainerXBlockHtml); renderContainerPage(this, mockBadContainerXBlockHtml);
containerPage.$('.delete-button').first().click(); containerPage.$('.delete-button').first().click();
EditHelpers.confirmPrompt(promptSpy); EditHelpers.confirmPrompt(promptSpy);
...@@ -361,7 +373,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -361,7 +373,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(requests.length).toBe(numRequests); expect(requests.length).toBe(numRequests);
}); });
it('shows a notification during the delete operation', function() { it('shows a notification during the delete operation', function () {
var notificationSpy = EditHelpers.createNotificationSpy(); var notificationSpy = EditHelpers.createNotificationSpy();
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
clickDelete(0); clickDelete(0);
...@@ -381,11 +393,11 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -381,11 +393,11 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
}); });
}); });
describe("Duplicating an xblock", function() { describe("Duplicating an xblock", function () {
var clickDuplicate, duplicateComponentWithSuccess, var clickDuplicate, duplicateComponentWithSuccess,
refreshXBlockSpies; refreshXBlockSpies;
clickDuplicate = function(componentIndex) { clickDuplicate = function (componentIndex) {
// find all duplicate buttons for the given group // find all duplicate buttons for the given group
var duplicateButtons = getGroupElement().find(".duplicate-button"); var duplicateButtons = getGroupElement().find(".duplicate-button");
...@@ -395,7 +407,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -395,7 +407,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
duplicateButtons[componentIndex].click(); duplicateButtons[componentIndex].click();
}; };
duplicateComponentWithSuccess = function(componentIndex) { duplicateComponentWithSuccess = function (componentIndex) {
refreshXBlockSpies = spyOn(containerPage, "refreshXBlock"); refreshXBlockSpies = spyOn(containerPage, "refreshXBlock");
clickDuplicate(componentIndex); clickDuplicate(componentIndex);
...@@ -415,22 +427,22 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -415,22 +427,22 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(refreshXBlockSpies).toHaveBeenCalled(); expect(refreshXBlockSpies).toHaveBeenCalled();
}; };
it("can duplicate the first xblock", function() { it("can duplicate the first xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
duplicateComponentWithSuccess(0); duplicateComponentWithSuccess(0);
}); });
it("can duplicate a middle xblock", function() { it("can duplicate a middle xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
duplicateComponentWithSuccess(1); duplicateComponentWithSuccess(1);
}); });
it("can duplicate the last xblock", function() { it("can duplicate the last xblock", function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
duplicateComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1); duplicateComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
}); });
it("can duplicate an xblock with broken JavaScript", function() { it("can duplicate an xblock with broken JavaScript", function () {
renderContainerPage(this, mockBadContainerXBlockHtml); renderContainerPage(this, mockBadContainerXBlockHtml);
containerPage.$('.duplicate-button').first().click(); containerPage.$('.duplicate-button').first().click();
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', { AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', {
...@@ -498,15 +510,14 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -498,15 +510,14 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expectComponents(getGroupElement(), allComponentsInGroup); expectComponents(getGroupElement(), allComponentsInGroup);
}); });
describe('Template Picker', function() { describe('Template Picker', function () {
var showTemplatePicker, verifyCreateHtmlComponent, var showTemplatePicker, verifyCreateHtmlComponent;
mockXBlockHtml = readFixtures('mock/mock-xblock.underscore');
showTemplatePicker = function() { showTemplatePicker = function () {
containerPage.$('.new-component .new-component-type a.multiple-templates')[0].click(); containerPage.$('.new-component .new-component-type a.multiple-templates')[0].click();
}; };
verifyCreateHtmlComponent = function(test, templateIndex, expectedRequest) { verifyCreateHtmlComponent = function (test, templateIndex, expectedRequest) {
var xblockCount; var xblockCount;
renderContainerPage(test, mockContainerXBlockHtml); renderContainerPage(test, mockContainerXBlockHtml);
showTemplatePicker(); showTemplatePicker();
...@@ -518,17 +529,17 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -518,17 +529,17 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(containerPage.$('.studio-xblock-wrapper').length).toBe(xblockCount + 1); expect(containerPage.$('.studio-xblock-wrapper').length).toBe(xblockCount + 1);
}; };
it('can add an HTML component without a template', function() { it('can add an HTML component without a template', function () {
verifyCreateHtmlComponent(this, 0, { verifyCreateHtmlComponent(this, 0, {
"category": "html", "category": "html",
"parent_locator": "locator-group-A" "parent_locator": "locator-group-A"
}); });
}); });
it('can add an HTML component with a template', function() { it('can add an HTML component with a template', function () {
verifyCreateHtmlComponent(this, 1, { verifyCreateHtmlComponent(this, 1, {
"category": "html", "category": "html",
"boilerplate" : "announcement.yaml", "boilerplate": "announcement.yaml",
"parent_locator": "locator-group-A" "parent_locator": "locator-group-A"
}); });
}); });
...@@ -536,4 +547,16 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel ...@@ -536,4 +547,16 @@ 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' }
);
parameterized_suite("Paged",
{ enable_paging: true, page_size: 42 },
{
initial: 'mock/mock-container-paged-xblock.underscore',
add_response: 'mock/mock-container-paged-after-add-xblock.underscore'
});
}); });
...@@ -123,6 +123,10 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext", ...@@ -123,6 +123,10 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
}); });
}, },
acknowledgeXBlockDeletion: function(locator){
this.notifyRuntime('deleted-child', locator);
},
refresh: function() { refresh: function() {
var sortableInitializedClass = this.makeRequestSpecificSelector('.reorderable-container.ui-sortable'); var sortableInitializedClass = this.makeRequestSpecificSelector('.reorderable-container.ui-sortable');
this.$(sortableInitializedClass).sortable('refresh'); this.$(sortableInitializedClass).sortable('refresh');
......
define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext", "js/views/feedback_notification",
"js/views/paging_header", "js/views/paging_footer"],
function ($, _, XBlockView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter) {
var LibraryContainerView = XBlockView.extend({
// Store the request token of the first xblock on the page (which we know was rendered by Studio when
// the page was generated). Use that request token to filter out user-defined HTML in any
// child xblocks within the page.
requestToken: "",
initialize: function(options){
var self = this;
XBlockView.prototype.initialize.call(this);
this.page_size = this.options.page_size || 10;
if (options) {
this.page_reload_callback = options.page_reload_callback;
}
// emulating Backbone.paginator interface
this.collection = {
currentPage: 0,
totalPages: 0,
totalCount: 0,
sortDirection: "desc",
start: 0,
_size: 0,
bind: function() {}, // no-op
size: function() { return self.collection._size; }
};
},
render: function(options) {
var eff_options = options || {};
if (eff_options.block_added) {
this.collection.currentPage = this.getPageCount(this.collection.totalCount+1) - 1;
}
eff_options.page_number = typeof eff_options.page_number !== "undefined"
? eff_options.page_number
: this.collection.currentPage;
return this.renderPage(eff_options);
},
renderPage: function(options){
var self = this,
view = this.view,
xblockInfo = this.model,
xblockUrl = xblockInfo.url();
return $.ajax({
url: decodeURIComponent(xblockUrl) + "/" + view,
type: 'GET',
cache: false,
data: this.getRenderParameters(options.page_number),
headers: { Accept: 'application/json' },
success: function(fragment) {
self.handleXBlockFragment(fragment, options);
self.processPaging({ requested_page: options.page_number });
if (options.paging && self.page_reload_callback){
self.page_reload_callback(self.$el);
}
}
});
},
getRenderParameters: function(page_number) {
return {
enable_paging: true,
page_size: this.page_size,
page_number: page_number
};
},
getPageCount: function(total_count){
if (total_count==0) return 1;
return Math.ceil(total_count / this.page_size);
},
setPage: function(page_number) {
this.render({ page_number: page_number, paging: true });
},
nextPage: function() {
var collection = this.collection,
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1;
if (currentPage < lastPage) {
this.setPage(currentPage + 1);
}
},
previousPage: function() {
var collection = this.collection,
currentPage = collection.currentPage;
if (currentPage > 0) {
this.setPage(currentPage - 1);
}
},
processPaging: function(options){
var $element = this.$el.find('.xblock-container-paging-parameters'),
total = $element.data('total'),
displayed = $element.data('displayed'),
start = $element.data('start');
this.collection.currentPage = options.requested_page;
this.collection.totalCount = total;
this.collection.totalPages = this.getPageCount(total);
this.collection.start = start;
this.collection._size = displayed;
this.processPagingHeaderAndFooter();
},
processPagingHeaderAndFooter: function(){
if (this.pagingHeader)
this.pagingHeader.undelegateEvents();
if (this.pagingFooter)
this.pagingFooter.undelegateEvents();
this.pagingHeader = new PagingHeader({
view: this,
el: this.$el.find('.container-paging-header')
});
this.pagingFooter = new PagingFooter({
view: this,
el: this.$el.find('.container-paging-footer')
});
this.pagingHeader.render();
this.pagingFooter.render();
},
xblockReady: function () {
XBlockView.prototype.xblockReady.call(this);
this.requestToken = this.$('div.xblock').first().data('request-token');
},
refresh: function() { },
acknowledgeXBlockDeletion: function (locator){
this.notifyRuntime('deleted-child', locator);
this.collection._size -= 1;
this.collection.totalCount -= 1;
// pages are counted from 0 - thus currentPage == 1 if we're on second page
if (this.collection._size == 0 && this.collection.currentPage >= 1) {
this.setPage(this.collection.currentPage - 1);
this.collection.totalPages -= 1;
}
else {
this.pagingHeader.render();
this.pagingFooter.render();
}
},
makeRequestSpecificSelector: function(selector) {
return 'div.xblock[data-request-token="' + this.requestToken + '"] > ' + selector;
},
sortDisplayName: function() {
return "Date added"; // TODO add support for sorting
}
});
return LibraryContainerView;
}); // end define();
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
* This page allows the user to understand and manipulate the xblock and its children. * 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", define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views/utils/view_utils",
"js/views/container", "js/views/xblock", "js/views/components/add_xblock", "js/views/modals/edit_xblock", "js/views/container", "js/views/library_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/models/xblock_info", "js/views/xblock_string_field_editor", "js/views/pages/container_subviews",
"js/views/unit_outline", "js/views/utils/xblock_utils"], "js/views/unit_outline", "js/views/utils/xblock_utils"],
function ($, _, gettext, BasePage, ViewUtils, ContainerView, XBlockView, AddXBlockComponent, function ($, _, gettext, BasePage, ViewUtils, ContainerView, PagedContainerView, XBlockView, AddXBlockComponent,
EditXBlockModal, XBlockInfo, XBlockStringFieldEditor, ContainerSubviews, UnitOutlineView, EditXBlockModal, XBlockInfo, XBlockStringFieldEditor, ContainerSubviews, UnitOutlineView,
XBlockUtils) { XBlockUtils) {
'use strict'; 'use strict';
...@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
initialize: function(options) { initialize: function(options) {
BasePage.prototype.initialize.call(this, 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.nameEditor = new XBlockStringFieldEditor({ this.nameEditor = new XBlockStringFieldEditor({
el: this.$('.wrapper-xblock-field'), el: this.$('.wrapper-xblock-field'),
model: this.model model: this.model
...@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
if (this.options.action === 'new') { if (this.options.action === 'new') {
this.nameEditor.$('.xblock-field-value-edit').click(); this.nameEditor.$('.xblock-field-value-edit').click();
} }
this.xblockView = new ContainerView({ this.xblockView = this.getXBlockView();
el: this.$('.wrapper-xblock'),
model: this.model,
view: this.view
});
this.messageView = new ContainerSubviews.MessageView({ this.messageView = new ContainerSubviews.MessageView({
el: this.$('.container-message'), el: this.$('.container-message'),
model: this.model model: this.model
...@@ -75,6 +75,28 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -75,6 +75,28 @@ 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
};
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);
}
},
render: function(options) { render: function(options) {
var self = this, var self = this,
xblockView = this.xblockView, xblockView = this.xblockView,
...@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// Re-enable Backbone events for any updated DOM elements // Re-enable Backbone events for any updated DOM elements
self.delegateEvents(); self.delegateEvents();
} },
block_added: options && options.block_added
}); });
}, },
...@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
modal.edit(xblockElement, this.model, { modal.edit(xblockElement, this.model, {
refresh: function() { refresh: function() {
self.refreshXBlock(xblockElement); self.refreshXBlock(xblockElement, false);
} }
}); });
}, },
...@@ -226,7 +249,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -226,7 +249,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// Inform the runtime that the child has been deleted in case // Inform the runtime that the child has been deleted in case
// other views are listening to deletion events. // other views are listening to deletion events.
xblockView.notifyRuntime('deleted-child', parent.data('locator')); xblockView.acknowledgeXBlockDeletion(parent.data('locator'));
// Update publish and last modified information from the server. // Update publish and last modified information from the server.
this.model.fetch(); this.model.fetch();
...@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
onNewXBlock: function(xblockElement, scrollOffset, data) { onNewXBlock: function(xblockElement, scrollOffset, data) {
ViewUtils.setScrollOffset(xblockElement, scrollOffset); ViewUtils.setScrollOffset(xblockElement, scrollOffset);
xblockElement.data('locator', data.locator); xblockElement.data('locator', data.locator);
return this.refreshXBlock(xblockElement); return this.refreshXBlock(xblockElement, true);
}, },
/** /**
...@@ -243,17 +266,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views ...@@ -243,17 +266,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
* reorderable container then the element will be refreshed inline. If not, then the * reorderable container then the element will be refreshed inline. If not, then the
* parent container will be refreshed instead. * parent container will be refreshed instead.
* @param element An element representing the xblock to be refreshed. * @param element An element representing the xblock to be refreshed.
* @param block_added Flag to indicate that new block has been just added.
*/ */
refreshXBlock: function(element) { refreshXBlock: function(element, block_added) {
var xblockElement = this.findXBlockElement(element), var xblockElement = this.findXBlockElement(element),
parentElement = xblockElement.parent(), parentElement = xblockElement.parent(),
rootLocator = this.xblockView.model.id; rootLocator = this.xblockView.model.id;
if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) { if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) {
this.render({refresh: true}); this.render({refresh: true, block_added: block_added});
} else if (parentElement.hasClass('reorderable-container')) { } else if (parentElement.hasClass('reorderable-container')) {
this.refreshChildXBlock(xblockElement); this.refreshChildXBlock(xblockElement);
} else { } else {
this.refreshXBlock(this.findXBlockElement(parentElement)); this.refreshXBlock(this.findXBlockElement(parentElement), block_added);
} }
}, },
......
...@@ -38,6 +38,12 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) { ...@@ -38,6 +38,12 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) {
currentPage = collection.currentPage + 1, currentPage = collection.currentPage + 1,
pageInput = this.$("#page-number-input"), pageInput = this.$("#page-number-input"),
pageNumber = parseInt(pageInput.val(), 10); pageNumber = parseInt(pageInput.val(), 10);
if (pageNumber > collection.totalPages) {
pageNumber = false;
}
if (pageNumber <= 0) {
pageNumber = false;
}
if (pageNumber && pageNumber !== currentPage) { if (pageNumber && pageNumber !== currentPage) {
view.setPage(pageNumber - 1); view.setPage(pageNumber - 1);
} }
......
// studio - elements - pagination
// ==========================
%pagination {
@include clearfix;
display: inline-block;
width: flex-grid(3, 12);
&.pagination-compact {
@include text-align(right);
}
&.pagination-full {
display: block;
width: flex-grid(4, 12);
margin: $baseline auto;
}
.nav-item {
position: relative;
display: inline-block;
}
.nav-link {
@include transition(all $tmg-f2 ease-in-out 0s);
display: block;
padding: ($baseline/4) ($baseline*0.75);
&.previous {
margin-right: ($baseline/2);
}
&.next {
margin-left: ($baseline/2);
}
&:hover {
background-color: $blue;
border-radius: 3px;
color: $white;
}
&.is-disabled {
background-color: transparent;
color: $gray-l2;
pointer-events: none;
}
}
.nav-label {
@extend .sr;
}
.pagination-form,
.current-page,
.page-divider,
.total-pages {
display: inline-block;
}
.current-page,
.page-number-input,
.total-pages {
@extend %t-copy-base;
@extend %t-strong;
width: ($baseline*2.5);
margin: 0 ($baseline*0.75);
padding: ($baseline/4);
text-align: center;
color: $gray;
}
.current-page {
@extend %ui-depth1;
position: absolute;
@include left(-($baseline/4));
}
.page-divider {
@extend %t-title4;
@extend %t-regular;
vertical-align: middle;
color: $gray-l2;
}
.pagination-form {
@extend %ui-depth2;
position: relative;
.page-number-label,
.submit-pagination-form {
@extend .sr;
}
.page-number-input {
@include transition(all $tmg-f2 ease-in-out 0s);
border: 1px solid transparent;
border-bottom: 1px dotted $gray-l2;
border-radius: 0;
box-shadow: none;
background: none;
&:hover {
background-color: $white;
opacity: 0.6;
}
&:focus {
// borrowing the base input focus styles to match overall app
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
opacity: 1.0;
box-shadow: 0 0 3px $shadow-d1 inset;
background-color: $white;
border: 1px solid transparent;
border-radius: 3px;
}
}
}
}
\ No newline at end of file
...@@ -28,120 +28,7 @@ ...@@ -28,120 +28,7 @@
} }
.pagination { .pagination {
@include clearfix; @extend %pagination;
display: inline-block;
width: flex-grid(3, 12);
&.pagination-compact {
@include text-align(right);
}
&.pagination-full {
display: block;
width: flex-grid(4, 12);
margin: $baseline auto;
}
.nav-item {
position: relative;
display: inline-block;
}
.nav-link {
@include transition(all $tmg-f2 ease-in-out 0s);
display: block;
padding: ($baseline/4) ($baseline*0.75);
&.previous {
margin-right: ($baseline/2);
}
&.next {
margin-left: ($baseline/2);
}
&:hover {
background-color: $blue;
border-radius: 3px;
color: $white;
}
&.is-disabled {
background-color: transparent;
color: $gray-l2;
pointer-events: none;
}
}
.nav-label {
@extend .sr;
}
.pagination-form,
.current-page,
.page-divider,
.total-pages {
display: inline-block;
}
.current-page,
.page-number-input,
.total-pages {
@extend %t-copy-base;
@extend %t-strong;
width: ($baseline*2.5);
margin: 0 ($baseline*0.75);
padding: ($baseline/4);
text-align: center;
color: $gray;
}
.current-page {
@extend %ui-depth1;
position: absolute;
@include left(-($baseline/4));
}
.page-divider {
@extend %t-title4;
@extend %t-regular;
vertical-align: middle;
color: $gray-l2;
}
.pagination-form {
@extend %ui-depth2;
position: relative;
.page-number-label,
.submit-pagination-form {
@extend .sr;
}
.page-number-input {
@include transition(all $tmg-f2 ease-in-out 0s);
border: 1px solid transparent;
border-bottom: 1px dotted $gray-l2;
border-radius: 0;
box-shadow: none;
background: none;
&:hover {
background-color: $white;
opacity: 0.6;
}
&:focus {
// borrowing the base input focus styles to match overall app
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
opacity: 1.0;
box-shadow: 0 0 3px $shadow-d1 inset;
background-color: $white;
border: 1px solid transparent;
border-radius: 3px;
}
}
}
} }
.assets-table { .assets-table {
......
...@@ -103,6 +103,37 @@ ...@@ -103,6 +103,37 @@
} }
} }
.container-paging-header {
.meta-wrap {
margin: $baseline $baseline/2;
}
.meta {
@extend %t-copy-sub2;
display: inline-block;
vertical-align: top;
width: flex-grid(9, 12);
color: $gray-l1;
.count-current-shown,
.count-total,
.sort-order {
@extend %t-strong;
}
}
.pagination {
@extend %pagination;
}
}
.container-paging-footer {
.pagination {
@extend %pagination;
}
}
// ==================== // ====================
//UI: default internal xblock content styles //UI: default internal xblock content styles
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
// +Base - Elements // +Base - Elements
// ==================== // ====================
@import 'elements/typography'; @import 'elements/typography';
@import 'elements/pagination'; // pagination
@import 'elements/icons'; // references to icons used @import 'elements/icons'; // references to icons used
@import 'elements/controls'; // buttons, link styles, sliders, etc. @import 'elements/controls'; // buttons, link styles, sliders, etc.
@import 'elements/xblocks'; // studio rendering chrome for xblocks @import 'elements/xblocks'; // studio rendering chrome for xblocks
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
// +Base - Elements // +Base - Elements
// ==================== // ====================
@import 'elements/typography'; @import 'elements/typography';
@import 'elements/pagination'; // pagination
@import 'elements/icons'; // references to icons used @import 'elements/icons'; // references to icons used
@import 'elements/controls'; // buttons, link styles, sliders, etc. @import 'elements/controls'; // buttons, link styles, sliders, etc.
@import 'elements/xblocks'; // studio rendering chrome for xblocks @import 'elements/xblocks'; // studio rendering chrome for xblocks
......
...@@ -31,7 +31,10 @@ from django.utils.translation import ugettext as _ ...@@ -31,7 +31,10 @@ from django.utils.translation import ugettext as _
require(["js/factories/container"], function(ContainerFactory) { require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory( ContainerFactory(
${component_templates | n}, ${json.dumps(xblock_info) | n}, ${component_templates | n}, ${json.dumps(xblock_info) | n},
"${action}", ${json.dumps(is_unit_page)} "${action}",
{
isUnitPage: ${json.dumps(is_unit_page)}
}
); );
}); });
</%block> </%block>
......
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span class="xblock-display-name">Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-locator="locator-container" data-request-token="page-render-token"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<script type="text/template" id="paging-header-tpl">
<div class="meta-wrap">
<div class="meta">
<%= messageHtml %>
</div>
<nav class="pagination pagination-compact top">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</div>
</script>
<script type="text/template" id="paging-footer-tpl">
<nav class="pagination pagination-full bottom">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item page">
<div class="pagination-form">
<label class="page-number-label" for="page-number"><%= gettext("Page number") %></label>
<input id="page-number-input" class="page-number-input" name="page-number" type="text" size="4" />
</div>
<span class="current-page"><%= current_page + 1 %></span>
<span class="page-divider">/</span>
<span class="total-pages"><%= total_pages %></span>
</li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</script>
<div class="container-paging-header"></div>
<div class="studio-xblock-wrapper" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group A</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="header-actions">
<div class="xblock-header-primary">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A4">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group B</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
<div class="container-paging-footer"></div>
</div>
</article>
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span class="xblock-display-name">Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-locator="locator-container" data-request-token="page-render-token"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<script type="text/template" id="paging-header-tpl">
<div class="meta-wrap">
<div class="meta">
<%= messageHtml %>
</div>
<nav class="pagination pagination-compact top">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</div>
</script>
<script type="text/template" id="paging-footer-tpl">
<nav class="pagination pagination-full bottom">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item page">
<div class="pagination-form">
<label class="page-number-label" for="page-number"><%= gettext("Page number") %></label>
<input id="page-number-input" class="page-number-input" name="page-number" type="text" size="4" />
</div>
<span class="current-page"><%= current_page + 1 %></span>
<span class="page-divider">/</span>
<span class="total-pages"><%= total_pages %></span>
</li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</script>
<div class="container-paging-header"></div>
<div class="studio-xblock-wrapper" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group A</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="header-actions">
<div class="xblock-header-primary">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group B</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
<div class="container-paging-footer"></div>
</div>
</article>
...@@ -22,8 +22,12 @@ from django.utils.translation import ugettext as _ ...@@ -22,8 +22,12 @@ from django.utils.translation import ugettext as _
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/library"], function(LibraryFactory) { require(["js/factories/library"], function(LibraryFactory) {
LibraryFactory( LibraryFactory(
${component_templates | n}, ${component_templates | n}, ${json.dumps(xblock_info) | n},
${json.dumps(xblock_info) | n} {
isUnitPage: false,
enable_paging: true,
page_size: 10
}
); );
}); });
</%block> </%block>
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
""" """
import logging import logging
from .studio_editable import StudioEditableModule
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String, List from xblock.fields import Scope, String, List
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xmodule.studio_editable import StudioEditableModule
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -42,29 +42,55 @@ class LibraryRoot(XBlock): ...@@ -42,29 +42,55 @@ class LibraryRoot(XBlock):
def author_view(self, context): def author_view(self, context):
""" """
Renders the Studio preview view, which supports drag and drop. Renders the Studio preview view.
""" """
fragment = Fragment() fragment = Fragment()
self.render_children(context, fragment, can_reorder=False, can_add=True)
return fragment
def render_children(self, context, fragment, can_reorder=False, can_add=False): # pylint: disable=unused-argument
"""
Renders the children of the module with HTML appropriate for Studio. If can_reorder is True,
then the children will be rendered to support drag and drop.
"""
contents = [] contents = []
for child_key in self.children: # pylint: disable=E1101 paging = context.get('paging', None)
context['reorderable_items'].add(child_key)
children_count = len(self.children) # pylint: disable=no-member
item_start, item_end = 0, children_count
# TODO sort children
if paging:
page_number = paging.get('page_number', 0)
raw_page_size = paging.get('page_size', None)
page_size = raw_page_size if raw_page_size is not None else children_count
item_start, item_end = page_size * page_number, page_size * (page_number + 1)
children_to_show = self.children[item_start:item_end] # pylint: disable=no-member
for child_key in children_to_show: # pylint: disable=E1101
child = self.runtime.get_block(child_key) child = self.runtime.get_block(child_key)
rendered_child = self.runtime.render_child(child, StudioEditableModule.get_preview_view_name(child), context) child_view_name = StudioEditableModule.get_preview_view_name(child)
rendered_child = self.runtime.render_child(child, child_view_name, context)
fragment.add_frag_resources(rendered_child) fragment.add_frag_resources(rendered_child)
contents.append({ contents.append({
'id': unicode(child_key), 'id': child.location.to_deprecated_string(),
'content': rendered_child.content, 'content': rendered_child.content
}) })
fragment.add_content(self.runtime.render_template("studio_render_children_view.html", { fragment.add_content(
self.runtime.render_template("studio_render_paged_children_view.html", {
'items': contents, 'items': contents,
'xblock_context': context, 'xblock_context': context,
'can_add': True, 'can_add': can_add,
'can_reorder': True, 'can_reorder': False,
})) 'first_displayed': item_start,
return fragment 'total_children': children_count,
'displayed_children': len(children_to_show)
})
)
@property @property
def display_org_with_default(self): def display_org_with_default(self):
......
...@@ -155,7 +155,6 @@ class VideoStudentViewHandlers(object): ...@@ -155,7 +155,6 @@ class VideoStudentViewHandlers(object):
if transcript_name: if transcript_name:
# Get the asset path for course # Get the asset path for course
asset_path = None
course = self.descriptor.runtime.modulestore.get_course(self.course_id) course = self.descriptor.runtime.modulestore.get_course(self.course_id)
if course.static_asset_path: if course.static_asset_path:
asset_path = course.static_asset_path asset_path = course.static_asset_path
......
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='static_content.html'/>
% for template_name in ["paging-header", "paging-footer"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
<div class="xblock-container-paging-parameters" data-start="${first_displayed}" data-displayed="${displayed_children}" data-total="${total_children}"></div>
<div class="container-paging-header"></div>
% for item in items:
${item['content']}
% endfor
% if can_add:
<div class="add-xblock-component new-component-item adding"></div>
% endif
<div class="container-paging-footer"></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