Paging for LibraryView added with JS tests.
This commit is contained in:
@@ -237,12 +237,28 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
if view_name == 'reorderable_container_child_preview':
|
||||
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.
|
||||
context = {
|
||||
'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks
|
||||
'is_unit_page': is_unit(xblock),
|
||||
'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)
|
||||
|
||||
@@ -239,6 +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/group_configuration_spec",
|
||||
"js/spec/views/paging_spec",
|
||||
"js/spec/views/unit_outline_spec",
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
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',
|
||||
'xblock/cms.runtime.v1'
|
||||
],
|
||||
function($, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
|
||||
function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
|
||||
'use strict';
|
||||
return function (componentTemplates, XBlockInfoJson, action, isUnitPage) {
|
||||
var templates = new ComponentTemplates(componentTemplates, {parse: true}),
|
||||
mainXBlockInfo = new XBlockInfo(XBlockInfoJson, {parse: true});
|
||||
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})
|
||||
};
|
||||
|
||||
xmoduleLoader.done(function () {
|
||||
var view = new ContainerPage({
|
||||
el: $('#content'),
|
||||
model: mainXBlockInfo,
|
||||
action: action,
|
||||
templates: templates,
|
||||
isUnitPage: isUnitPage
|
||||
});
|
||||
var view = new ContainerPage(_.extend(main_options, options));
|
||||
view.render();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
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',
|
||||
'xblock/cms.runtime.v1'
|
||||
],
|
||||
function($, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
|
||||
function($, _, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
|
||||
'use strict';
|
||||
return function (componentTemplates, XBlockInfoJson) {
|
||||
var templates = new ComponentTemplates(componentTemplates, {parse: true}),
|
||||
mainXBlockInfo = new XBlockInfo(XBlockInfoJson, {parse: true});
|
||||
return function (componentTemplates, XBlockInfoJson, options) {
|
||||
var main_options = {
|
||||
el: $('#content'),
|
||||
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
|
||||
templates: new ComponentTemplates(componentTemplates, {parse: true}),
|
||||
action: 'view'
|
||||
};
|
||||
|
||||
xmoduleLoader.done(function () {
|
||||
var view = new ContainerPage({
|
||||
el: $('#content'),
|
||||
model: mainXBlockInfo,
|
||||
action: "view",
|
||||
templates: templates,
|
||||
isUnitPage: false
|
||||
});
|
||||
var view = new ContainerPage(_.extend(main_options, options));
|
||||
view.render();
|
||||
});
|
||||
};
|
||||
|
||||
489
cms/static/js/spec/views/library_container_spec.js
Normal file
489
cms/static/js/spec/views/library_container_spec.js
Normal file
@@ -0,0 +1,489 @@
|
||||
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,537 +3,560 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
"js/views/pages/container", "js/models/xblock_info", "jquery.simulate"],
|
||||
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, XBlockInfo) {
|
||||
|
||||
describe("ContainerPage", function() {
|
||||
var lastRequest, renderContainerPage, expectComponents, respondWithHtml,
|
||||
model, containerPage, requests, initialDisplayName,
|
||||
mockContainerPage = readFixtures('mock/mock-container-page.underscore'),
|
||||
mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore'),
|
||||
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');
|
||||
|
||||
beforeEach(function () {
|
||||
var newDisplayName = 'New Display Name';
|
||||
|
||||
EditHelpers.installEditTemplates();
|
||||
TemplateHelpers.installTemplate('xblock-string-field-editor');
|
||||
TemplateHelpers.installTemplate('container-message');
|
||||
appendSetFixtures(mockContainerPage);
|
||||
|
||||
EditHelpers.installMockXBlock({
|
||||
data: "<p>Some HTML</p>",
|
||||
metadata: {
|
||||
display_name: newDisplayName
|
||||
}
|
||||
});
|
||||
|
||||
initialDisplayName = 'Test Container';
|
||||
|
||||
model = new XBlockInfo({
|
||||
id: 'locator-container',
|
||||
display_name: initialDisplayName,
|
||||
category: 'vertical'
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
EditHelpers.uninstallMockXBlock();
|
||||
});
|
||||
|
||||
lastRequest = function() { return requests[requests.length - 1]; };
|
||||
|
||||
respondWithHtml = function(html) {
|
||||
var requestIndex = requests.length - 1;
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{ html: html, "resources": [] },
|
||||
requestIndex
|
||||
);
|
||||
};
|
||||
|
||||
renderContainerPage = function(test, html, options) {
|
||||
requests = AjaxHelpers.requests(test);
|
||||
containerPage = new ContainerPage(_.extend(options || {}, {
|
||||
model: model,
|
||||
templates: EditHelpers.mockComponentTemplates,
|
||||
el: $('#content')
|
||||
}));
|
||||
containerPage.render();
|
||||
respondWithHtml(html);
|
||||
};
|
||||
|
||||
expectComponents = function (container, locators) {
|
||||
// verify expected components (in expected order) by their locators
|
||||
var components = $(container).find('.studio-xblock-wrapper');
|
||||
expect(components.length).toBe(locators.length);
|
||||
_.each(locators, function(locator, locator_index) {
|
||||
expect($(components[locator_index]).data('locator')).toBe(locator);
|
||||
});
|
||||
};
|
||||
|
||||
describe("Initial display", function() {
|
||||
it('can render itself', function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
expect(containerPage.$('.xblock-header').length).toBe(9);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('shows a loading indicator', function() {
|
||||
requests = AjaxHelpers.requests(this);
|
||||
containerPage.render();
|
||||
expect(containerPage.$('.ui-loading')).not.toHaveClass('is-hidden');
|
||||
respondWithHtml(mockContainerXBlockHtml);
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('can show an xblock with broken JavaScript', function() {
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('can show an xblock with an invalid XBlock', function() {
|
||||
renderContainerPage(this, mockBadXBlockContainerXBlockHtml);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('inline edits the display name when performing a new action', function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
action: 'new'
|
||||
});
|
||||
expect(containerPage.$('.xblock-header').length).toBe(9);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.xblock-field-input')).not.toHaveClass('is-hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Editing the container", function() {
|
||||
var updatedDisplayName = 'Updated Test Container',
|
||||
getDisplayNameWrapper;
|
||||
|
||||
afterEach(function() {
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
getDisplayNameWrapper = function() {
|
||||
return containerPage.$('.wrapper-xblock-field');
|
||||
};
|
||||
|
||||
it('can edit itself', function() {
|
||||
var editButtons, displayNameElement;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
displayNameElement = containerPage.$('.page-header-title');
|
||||
|
||||
// Click the root edit button
|
||||
editButtons = containerPage.$('.nav-actions .edit-button');
|
||||
editButtons.first().click();
|
||||
|
||||
// Expect a request to be made to show the studio view for the container
|
||||
expect(str.startsWith(lastRequest().url, '/xblock/locator-container/studio_view')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockContainerXBlockHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
|
||||
// Expect the correct title to be shown
|
||||
expect(EditHelpers.getModalTitle()).toBe('Editing: Test Container');
|
||||
|
||||
// Press the save button and respond with a success message to the save
|
||||
EditHelpers.pressModalButton('.action-save');
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
expect(EditHelpers.isShowingModal()).toBeFalsy();
|
||||
|
||||
// Expect the last request be to refresh the container page
|
||||
expect(str.startsWith(lastRequest().url,
|
||||
'/xblock/locator-container/container_preview')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockUpdatedContainerXBlockHtml,
|
||||
resources: []
|
||||
});
|
||||
|
||||
// Respond to the subsequent xblock info fetch request.
|
||||
AjaxHelpers.respondWithJson(requests, {"display_name": updatedDisplayName});
|
||||
|
||||
// Expect the title to have been updated
|
||||
expect(displayNameElement.text().trim()).toBe(updatedDisplayName);
|
||||
});
|
||||
|
||||
it('can inline edit the display name', function() {
|
||||
var displayNameInput, displayNameWrapper;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
displayNameWrapper = getDisplayNameWrapper();
|
||||
displayNameInput = EditHelpers.inlineEdit(displayNameWrapper, updatedDisplayName);
|
||||
displayNameInput.change();
|
||||
// This is the response for the change operation.
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
// This is the response for the subsequent fetch operation.
|
||||
AjaxHelpers.respondWithJson(requests, {"display_name": updatedDisplayName});
|
||||
EditHelpers.verifyInlineEditChange(displayNameWrapper, updatedDisplayName);
|
||||
expect(containerPage.model.get('display_name')).toBe(updatedDisplayName);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Editing an xblock", function() {
|
||||
afterEach(function() {
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
it('can show an edit modal for a child xblock', function() {
|
||||
var editButtons;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
// The container should have rendered six mock xblocks
|
||||
expect(editButtons.length).toBe(6);
|
||||
editButtons[0].click();
|
||||
// Make sure that the correct xblock is requested to be edited
|
||||
expect(str.startsWith(lastRequest().url, '/xblock/locator-component-A1/studio_view')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXBlockEditorHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can show an edit modal for a child xblock with broken JavaScript', function() {
|
||||
var editButtons;
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
editButtons[0].click();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXBlockEditorHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Editing an xmodule", function() {
|
||||
var mockXModuleEditor = readFixtures('mock/mock-xmodule-editor.underscore'),
|
||||
newDisplayName = 'New Display Name';
|
||||
function parameterized_suite(label, global_page_options, fixtures) {
|
||||
describe(label + " ContainerPage", function () {
|
||||
var lastRequest, getContainerPage, renderContainerPage, expectComponents, respondWithHtml,
|
||||
model, containerPage, requests, initialDisplayName,
|
||||
mockContainerPage = readFixtures('mock/mock-container-page.underscore'),
|
||||
mockContainerXBlockHtml = readFixtures(fixtures.initial),
|
||||
mockXBlockHtml = readFixtures(fixtures.add_response),
|
||||
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');
|
||||
|
||||
beforeEach(function () {
|
||||
EditHelpers.installMockXModule({
|
||||
var newDisplayName = 'New Display Name';
|
||||
|
||||
EditHelpers.installEditTemplates();
|
||||
TemplateHelpers.installTemplate('xblock-string-field-editor');
|
||||
TemplateHelpers.installTemplate('container-message');
|
||||
appendSetFixtures(mockContainerPage);
|
||||
|
||||
EditHelpers.installMockXBlock({
|
||||
data: "<p>Some HTML</p>",
|
||||
metadata: {
|
||||
display_name: newDisplayName
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
EditHelpers.uninstallMockXModule();
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
initialDisplayName = 'Test Container';
|
||||
|
||||
it('can save changes to settings', function() {
|
||||
var editButtons, modal, mockUpdatedXBlockHtml;
|
||||
mockUpdatedXBlockHtml = readFixtures('mock/mock-updated-xblock.underscore');
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
// The container should have rendered six mock xblocks
|
||||
expect(editButtons.length).toBe(6);
|
||||
editButtons[0].click();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXModuleEditor,
|
||||
resources: []
|
||||
model = new XBlockInfo({
|
||||
id: 'locator-container',
|
||||
display_name: initialDisplayName,
|
||||
category: 'vertical'
|
||||
});
|
||||
|
||||
modal = $('.edit-xblock-modal');
|
||||
expect(modal.length).toBe(1);
|
||||
// Click on the settings tab
|
||||
modal.find('.settings-button').click();
|
||||
// Change the display name's text
|
||||
modal.find('.setting-input').text("Mock Update");
|
||||
// Press the save button
|
||||
modal.find('.action-save').click();
|
||||
// Respond to the save
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
id: model.id
|
||||
});
|
||||
|
||||
// Respond to the request to refresh
|
||||
respondWithHtml(mockUpdatedXBlockHtml);
|
||||
|
||||
// Verify that the xblock was updated
|
||||
expect(containerPage.$('.mock-updated-content').text()).toBe('Mock Update');
|
||||
});
|
||||
});
|
||||
|
||||
describe("xblock operations", function() {
|
||||
var getGroupElement,
|
||||
NUM_COMPONENTS_PER_GROUP = 3, GROUP_TO_TEST = "A",
|
||||
allComponentsInGroup = _.map(
|
||||
_.range(NUM_COMPONENTS_PER_GROUP),
|
||||
function(index) { return 'locator-component-' + GROUP_TO_TEST + (index + 1); }
|
||||
);
|
||||
afterEach(function () {
|
||||
EditHelpers.uninstallMockXBlock();
|
||||
});
|
||||
|
||||
getGroupElement = function() {
|
||||
return containerPage.$("[data-locator='locator-group-" + GROUP_TO_TEST + "']");
|
||||
lastRequest = function () {
|
||||
return requests[requests.length - 1];
|
||||
};
|
||||
|
||||
describe("Deleting an xblock", function() {
|
||||
var clickDelete, deleteComponent, deleteComponentWithSuccess,
|
||||
promptSpy;
|
||||
respondWithHtml = function (html) {
|
||||
var requestIndex = requests.length - 1;
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{ html: html, "resources": [] },
|
||||
requestIndex
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
promptSpy = EditHelpers.createPromptSpy();
|
||||
});
|
||||
|
||||
clickDelete = function(componentIndex, clickNo) {
|
||||
|
||||
// find all delete buttons for the given group
|
||||
var deleteButtons = getGroupElement().find(".delete-button");
|
||||
expect(deleteButtons.length).toBe(NUM_COMPONENTS_PER_GROUP);
|
||||
|
||||
// click the requested delete button
|
||||
deleteButtons[componentIndex].click();
|
||||
|
||||
// click the 'yes' or 'no' button in the prompt
|
||||
EditHelpers.confirmPrompt(promptSpy, clickNo);
|
||||
getContainerPage = function (options) {
|
||||
var default_options = {
|
||||
model: model,
|
||||
templates: EditHelpers.mockComponentTemplates,
|
||||
el: $('#content')
|
||||
};
|
||||
return new ContainerPage(_.extend(options || {}, global_page_options, default_options));
|
||||
};
|
||||
|
||||
deleteComponent = function(componentIndex) {
|
||||
clickDelete(componentIndex);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
renderContainerPage = function (test, html, options) {
|
||||
requests = AjaxHelpers.requests(test);
|
||||
containerPage = getContainerPage(options);
|
||||
containerPage.render();
|
||||
respondWithHtml(html);
|
||||
};
|
||||
|
||||
// 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 - 2);
|
||||
expectComponents = function (container, locators) {
|
||||
// verify expected components (in expected order) by their locators
|
||||
var components = $(container).find('.studio-xblock-wrapper');
|
||||
expect(components.length).toBe(locators.length);
|
||||
_.each(locators, function (locator, locator_index) {
|
||||
expect($(components[locator_index]).data('locator')).toBe(locator);
|
||||
});
|
||||
};
|
||||
|
||||
// final request to refresh the xblock info
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
};
|
||||
|
||||
deleteComponentWithSuccess = function(componentIndex) {
|
||||
deleteComponent(componentIndex);
|
||||
|
||||
// verify the new list of components within the group
|
||||
expectComponents(
|
||||
getGroupElement(),
|
||||
_.without(allComponentsInGroup, allComponentsInGroup[componentIndex])
|
||||
);
|
||||
};
|
||||
|
||||
it("can delete the first xblock", function() {
|
||||
describe("Initial display", function () {
|
||||
it('can render itself', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(0);
|
||||
expect(containerPage.$('.xblock-header').length).toBe(9);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it("can delete a middle xblock", function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(1);
|
||||
it('shows a loading indicator', function () {
|
||||
requests = AjaxHelpers.requests(this);
|
||||
containerPage = getContainerPage();
|
||||
containerPage.render();
|
||||
expect(containerPage.$('.ui-loading')).not.toHaveClass('is-hidden');
|
||||
respondWithHtml(mockContainerXBlockHtml);
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it("can delete the last xblock", function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
|
||||
});
|
||||
|
||||
it("can delete an xblock with broken JavaScript", function() {
|
||||
it('can show an xblock with broken JavaScript', function () {
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
containerPage.$('.delete-button').first().click();
|
||||
EditHelpers.confirmPrompt(promptSpy);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
// expect the second to last request to be a delete of the xblock
|
||||
AjaxHelpers.expectJsonRequest(requests, 'DELETE', '/xblock/locator-broken-javascript',
|
||||
null, requests.length - 2);
|
||||
// expect the last request to be a fetch of the xblock info for the parent container
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('does not delete when clicking No in prompt', function () {
|
||||
var numRequests;
|
||||
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
numRequests = requests.length;
|
||||
|
||||
// click delete on the first component but press no
|
||||
clickDelete(0, true);
|
||||
|
||||
// all components should still exist
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
|
||||
// no requests should have been sent to the server
|
||||
expect(requests.length).toBe(numRequests);
|
||||
it('can show an xblock with an invalid XBlock', function () {
|
||||
renderContainerPage(this, mockBadXBlockContainerXBlockHtml);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.ui-loading')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('shows a notification during the delete operation', function() {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDelete(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('does not delete an xblock upon failure', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDelete(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
it('inline edits the display name when performing a new action', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
action: 'new'
|
||||
});
|
||||
expect(containerPage.$('.xblock-header').length).toBe(9);
|
||||
expect(containerPage.$('.wrapper-xblock .level-nesting')).not.toHaveClass('is-hidden');
|
||||
expect(containerPage.$('.xblock-field-input')).not.toHaveClass('is-hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Duplicating an xblock", function() {
|
||||
var clickDuplicate, duplicateComponentWithSuccess,
|
||||
refreshXBlockSpies;
|
||||
describe("Editing the container", function () {
|
||||
var updatedDisplayName = 'Updated Test Container',
|
||||
getDisplayNameWrapper;
|
||||
|
||||
clickDuplicate = function(componentIndex) {
|
||||
afterEach(function () {
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
// find all duplicate buttons for the given group
|
||||
var duplicateButtons = getGroupElement().find(".duplicate-button");
|
||||
expect(duplicateButtons.length).toBe(NUM_COMPONENTS_PER_GROUP);
|
||||
|
||||
// click the requested duplicate button
|
||||
duplicateButtons[componentIndex].click();
|
||||
getDisplayNameWrapper = function () {
|
||||
return containerPage.$('.wrapper-xblock-field');
|
||||
};
|
||||
|
||||
duplicateComponentWithSuccess = function(componentIndex) {
|
||||
refreshXBlockSpies = spyOn(containerPage, "refreshXBlock");
|
||||
it('can edit itself', function () {
|
||||
var editButtons, displayNameElement;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
displayNameElement = containerPage.$('.page-header-title');
|
||||
|
||||
clickDuplicate(componentIndex);
|
||||
// Click the root edit button
|
||||
editButtons = containerPage.$('.nav-actions .edit-button');
|
||||
editButtons.first().click();
|
||||
|
||||
// verify content of request
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', {
|
||||
'duplicate_source_locator': 'locator-component-' + GROUP_TO_TEST + (componentIndex + 1),
|
||||
'parent_locator': 'locator-group-' + GROUP_TO_TEST
|
||||
});
|
||||
|
||||
// send the response
|
||||
// Expect a request to be made to show the studio view for the container
|
||||
expect(str.startsWith(lastRequest().url, '/xblock/locator-container/studio_view')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
'locator': 'locator-duplicated-component'
|
||||
html: mockContainerXBlockHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
|
||||
// Expect the correct title to be shown
|
||||
expect(EditHelpers.getModalTitle()).toBe('Editing: Test Container');
|
||||
|
||||
// Press the save button and respond with a success message to the save
|
||||
EditHelpers.pressModalButton('.action-save');
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
expect(EditHelpers.isShowingModal()).toBeFalsy();
|
||||
|
||||
// Expect the last request be to refresh the container page
|
||||
expect(str.startsWith(lastRequest().url,
|
||||
'/xblock/locator-container/container_preview')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockUpdatedContainerXBlockHtml,
|
||||
resources: []
|
||||
});
|
||||
|
||||
// expect parent container to be refreshed
|
||||
expect(refreshXBlockSpies).toHaveBeenCalled();
|
||||
};
|
||||
// Respond to the subsequent xblock info fetch request.
|
||||
AjaxHelpers.respondWithJson(requests, {"display_name": updatedDisplayName});
|
||||
|
||||
it("can duplicate the first xblock", function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(0);
|
||||
// Expect the title to have been updated
|
||||
expect(displayNameElement.text().trim()).toBe(updatedDisplayName);
|
||||
});
|
||||
|
||||
it("can duplicate a middle xblock", function() {
|
||||
it('can inline edit the display name', function () {
|
||||
var displayNameInput, displayNameWrapper;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(1);
|
||||
});
|
||||
|
||||
it("can duplicate the last xblock", function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
|
||||
});
|
||||
|
||||
it("can duplicate an xblock with broken JavaScript", function() {
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
containerPage.$('.duplicate-button').first().click();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', {
|
||||
'duplicate_source_locator': 'locator-broken-javascript',
|
||||
'parent_locator': 'locator-container'
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a notification when duplicating', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDuplicate(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
AjaxHelpers.respondWithJson(requests, {"locator": "new_item"});
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('does not duplicate an xblock upon failure', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
refreshXBlockSpies = spyOn(containerPage, "refreshXBlock");
|
||||
clickDuplicate(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
expect(refreshXBlockSpies).not.toHaveBeenCalled();
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
displayNameWrapper = getDisplayNameWrapper();
|
||||
displayNameInput = EditHelpers.inlineEdit(displayNameWrapper, updatedDisplayName);
|
||||
displayNameInput.change();
|
||||
// This is the response for the change operation.
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
// This is the response for the subsequent fetch operation.
|
||||
AjaxHelpers.respondWithJson(requests, {"display_name": updatedDisplayName});
|
||||
EditHelpers.verifyInlineEditChange(displayNameWrapper, updatedDisplayName);
|
||||
expect(containerPage.model.get('display_name')).toBe(updatedDisplayName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNewComponent ', function () {
|
||||
var clickNewComponent;
|
||||
describe("Editing an xblock", function () {
|
||||
afterEach(function () {
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
clickNewComponent = function (index) {
|
||||
containerPage.$(".new-component .new-component-type a.single-template")[index].click();
|
||||
};
|
||||
|
||||
it('sends the correct JSON to the server', function () {
|
||||
it('can show an edit modal for a child xblock', function () {
|
||||
var editButtons;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
EditHelpers.verifyXBlockRequest(requests, {
|
||||
"category": "discussion",
|
||||
"type": "discussion",
|
||||
"parent_locator": "locator-group-A"
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
// The container should have rendered six mock xblocks
|
||||
expect(editButtons.length).toBe(6);
|
||||
editButtons[0].click();
|
||||
// Make sure that the correct xblock is requested to be edited
|
||||
expect(str.startsWith(lastRequest().url, '/xblock/locator-component-A1/studio_view')).toBeTruthy();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXBlockEditorHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can show an edit modal for a child xblock with broken JavaScript', function () {
|
||||
var editButtons;
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
editButtons[0].click();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXBlockEditorHtml,
|
||||
resources: []
|
||||
});
|
||||
expect(EditHelpers.isShowingModal()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Editing an xmodule", function () {
|
||||
var mockXModuleEditor = readFixtures('mock/mock-xmodule-editor.underscore'),
|
||||
newDisplayName = 'New Display Name';
|
||||
|
||||
beforeEach(function () {
|
||||
EditHelpers.installMockXModule({
|
||||
data: "<p>Some HTML</p>",
|
||||
metadata: {
|
||||
display_name: newDisplayName
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a notification while creating', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Adding/);
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
afterEach(function () {
|
||||
EditHelpers.uninstallMockXModule();
|
||||
EditHelpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
it('does not insert component upon failure', function () {
|
||||
var requestCount;
|
||||
it('can save changes to settings', function () {
|
||||
var editButtons, modal, mockUpdatedXBlockHtml;
|
||||
mockUpdatedXBlockHtml = readFixtures('mock/mock-updated-xblock.underscore');
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
requestCount = requests.length;
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
// No new requests should be made to refresh the view
|
||||
expect(requests.length).toBe(requestCount);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
editButtons = containerPage.$('.wrapper-xblock .edit-button');
|
||||
// The container should have rendered six mock xblocks
|
||||
expect(editButtons.length).toBe(6);
|
||||
editButtons[0].click();
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
html: mockXModuleEditor,
|
||||
resources: []
|
||||
});
|
||||
|
||||
modal = $('.edit-xblock-modal');
|
||||
expect(modal.length).toBe(1);
|
||||
// Click on the settings tab
|
||||
modal.find('.settings-button').click();
|
||||
// Change the display name's text
|
||||
modal.find('.setting-input').text("Mock Update");
|
||||
// Press the save button
|
||||
modal.find('.action-save').click();
|
||||
// Respond to the save
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
id: model.id
|
||||
});
|
||||
|
||||
// Respond to the request to refresh
|
||||
respondWithHtml(mockUpdatedXBlockHtml);
|
||||
|
||||
// Verify that the xblock was updated
|
||||
expect(containerPage.$('.mock-updated-content').text()).toBe('Mock Update');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Template Picker', function() {
|
||||
var showTemplatePicker, verifyCreateHtmlComponent,
|
||||
mockXBlockHtml = readFixtures('mock/mock-xblock.underscore');
|
||||
describe("xblock operations", function () {
|
||||
var getGroupElement,
|
||||
NUM_COMPONENTS_PER_GROUP = 3, GROUP_TO_TEST = "A",
|
||||
allComponentsInGroup = _.map(
|
||||
_.range(NUM_COMPONENTS_PER_GROUP),
|
||||
function (index) {
|
||||
return 'locator-component-' + GROUP_TO_TEST + (index + 1);
|
||||
}
|
||||
);
|
||||
|
||||
showTemplatePicker = function() {
|
||||
containerPage.$('.new-component .new-component-type a.multiple-templates')[0].click();
|
||||
getGroupElement = function () {
|
||||
return containerPage.$("[data-locator='locator-group-" + GROUP_TO_TEST + "']");
|
||||
};
|
||||
|
||||
describe("Deleting an xblock", function () {
|
||||
var clickDelete, deleteComponent, deleteComponentWithSuccess,
|
||||
promptSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
promptSpy = EditHelpers.createPromptSpy();
|
||||
});
|
||||
|
||||
clickDelete = function (componentIndex, clickNo) {
|
||||
|
||||
// find all delete buttons for the given group
|
||||
var deleteButtons = getGroupElement().find(".delete-button");
|
||||
expect(deleteButtons.length).toBe(NUM_COMPONENTS_PER_GROUP);
|
||||
|
||||
// click the requested delete button
|
||||
deleteButtons[componentIndex].click();
|
||||
|
||||
// click the 'yes' or 'no' button in the prompt
|
||||
EditHelpers.confirmPrompt(promptSpy, clickNo);
|
||||
};
|
||||
|
||||
verifyCreateHtmlComponent = function(test, templateIndex, expectedRequest) {
|
||||
var xblockCount;
|
||||
renderContainerPage(test, mockContainerXBlockHtml);
|
||||
showTemplatePicker();
|
||||
xblockCount = containerPage.$('.studio-xblock-wrapper').length;
|
||||
containerPage.$('.new-component-html a')[templateIndex].click();
|
||||
EditHelpers.verifyXBlockRequest(requests, expectedRequest);
|
||||
deleteComponent = function (componentIndex) {
|
||||
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 - 2);
|
||||
|
||||
// final request to refresh the xblock info
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
};
|
||||
|
||||
deleteComponentWithSuccess = function (componentIndex) {
|
||||
deleteComponent(componentIndex);
|
||||
|
||||
// verify the new list of components within the group
|
||||
expectComponents(
|
||||
getGroupElement(),
|
||||
_.without(allComponentsInGroup, allComponentsInGroup[componentIndex])
|
||||
);
|
||||
};
|
||||
|
||||
it("can delete the first xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(0);
|
||||
});
|
||||
|
||||
it("can delete a middle xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(1);
|
||||
});
|
||||
|
||||
it("can delete the last xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
deleteComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
|
||||
});
|
||||
|
||||
it("can delete an xblock with broken JavaScript", function () {
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
containerPage.$('.delete-button').first().click();
|
||||
EditHelpers.confirmPrompt(promptSpy);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
// expect the second to last request to be a delete of the xblock
|
||||
AjaxHelpers.expectJsonRequest(requests, 'DELETE', '/xblock/locator-broken-javascript',
|
||||
null, requests.length - 2);
|
||||
// expect the last request to be a fetch of the xblock info for the parent container
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
});
|
||||
|
||||
it('does not delete when clicking No in prompt', function () {
|
||||
var numRequests;
|
||||
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
numRequests = requests.length;
|
||||
|
||||
// click delete on the first component but press no
|
||||
clickDelete(0, true);
|
||||
|
||||
// all components should still exist
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
|
||||
// no requests should have been sent to the server
|
||||
expect(requests.length).toBe(numRequests);
|
||||
});
|
||||
|
||||
it('shows a notification during the delete operation', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDelete(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('does not delete an xblock upon failure', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDelete(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Duplicating an xblock", function () {
|
||||
var clickDuplicate, duplicateComponentWithSuccess,
|
||||
refreshXBlockSpies;
|
||||
|
||||
clickDuplicate = function (componentIndex) {
|
||||
|
||||
// find all duplicate buttons for the given group
|
||||
var duplicateButtons = getGroupElement().find(".duplicate-button");
|
||||
expect(duplicateButtons.length).toBe(NUM_COMPONENTS_PER_GROUP);
|
||||
|
||||
// click the requested duplicate button
|
||||
duplicateButtons[componentIndex].click();
|
||||
};
|
||||
|
||||
duplicateComponentWithSuccess = function (componentIndex) {
|
||||
refreshXBlockSpies = spyOn(containerPage, "refreshXBlock");
|
||||
|
||||
clickDuplicate(componentIndex);
|
||||
|
||||
// verify content of request
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', {
|
||||
'duplicate_source_locator': 'locator-component-' + GROUP_TO_TEST + (componentIndex + 1),
|
||||
'parent_locator': 'locator-group-' + GROUP_TO_TEST
|
||||
});
|
||||
|
||||
// send the response
|
||||
AjaxHelpers.respondWithJson(requests, {
|
||||
'locator': 'locator-duplicated-component'
|
||||
});
|
||||
|
||||
// expect parent container to be refreshed
|
||||
expect(refreshXBlockSpies).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
it("can duplicate the first xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(0);
|
||||
});
|
||||
|
||||
it("can duplicate a middle xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(1);
|
||||
});
|
||||
|
||||
it("can duplicate the last xblock", function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
duplicateComponentWithSuccess(NUM_COMPONENTS_PER_GROUP - 1);
|
||||
});
|
||||
|
||||
it("can duplicate an xblock with broken JavaScript", function () {
|
||||
renderContainerPage(this, mockBadContainerXBlockHtml);
|
||||
containerPage.$('.duplicate-button').first().click();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/', {
|
||||
'duplicate_source_locator': 'locator-broken-javascript',
|
||||
'parent_locator': 'locator-container'
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a notification when duplicating', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickDuplicate(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
AjaxHelpers.respondWithJson(requests, {"locator": "new_item"});
|
||||
respondWithHtml(mockXBlockHtml);
|
||||
expect(containerPage.$('.studio-xblock-wrapper').length).toBe(xblockCount + 1);
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('does not duplicate an xblock upon failure', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
refreshXBlockSpies = spyOn(containerPage, "refreshXBlock");
|
||||
clickDuplicate(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
expect(refreshXBlockSpies).not.toHaveBeenCalled();
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Duplicating/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNewComponent ', function () {
|
||||
var clickNewComponent;
|
||||
|
||||
clickNewComponent = function (index) {
|
||||
containerPage.$(".new-component .new-component-type a.single-template")[index].click();
|
||||
};
|
||||
|
||||
it('can add an HTML component without a template', function() {
|
||||
verifyCreateHtmlComponent(this, 0, {
|
||||
"category": "html",
|
||||
it('sends the correct JSON to the server', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
EditHelpers.verifyXBlockRequest(requests, {
|
||||
"category": "discussion",
|
||||
"type": "discussion",
|
||||
"parent_locator": "locator-group-A"
|
||||
});
|
||||
});
|
||||
|
||||
it('can add an HTML component with a template', function() {
|
||||
verifyCreateHtmlComponent(this, 1, {
|
||||
"category": "html",
|
||||
"boilerplate" : "announcement.yaml",
|
||||
"parent_locator": "locator-group-A"
|
||||
it('shows a notification while creating', function () {
|
||||
var notificationSpy = EditHelpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
EditHelpers.verifyNotificationShowing(notificationSpy, /Adding/);
|
||||
AjaxHelpers.respondWithJson(requests, { });
|
||||
EditHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('does not insert component upon failure', function () {
|
||||
var requestCount;
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
clickNewComponent(0);
|
||||
requestCount = requests.length;
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
// No new requests should be made to refresh the view
|
||||
expect(requests.length).toBe(requestCount);
|
||||
expectComponents(getGroupElement(), allComponentsInGroup);
|
||||
});
|
||||
|
||||
describe('Template Picker', function () {
|
||||
var showTemplatePicker, verifyCreateHtmlComponent;
|
||||
|
||||
showTemplatePicker = function () {
|
||||
containerPage.$('.new-component .new-component-type a.multiple-templates')[0].click();
|
||||
};
|
||||
|
||||
verifyCreateHtmlComponent = function (test, templateIndex, expectedRequest) {
|
||||
var xblockCount;
|
||||
renderContainerPage(test, mockContainerXBlockHtml);
|
||||
showTemplatePicker();
|
||||
xblockCount = containerPage.$('.studio-xblock-wrapper').length;
|
||||
containerPage.$('.new-component-html a')[templateIndex].click();
|
||||
EditHelpers.verifyXBlockRequest(requests, expectedRequest);
|
||||
AjaxHelpers.respondWithJson(requests, {"locator": "new_item"});
|
||||
respondWithHtml(mockXBlockHtml);
|
||||
expect(containerPage.$('.studio-xblock-wrapper').length).toBe(xblockCount + 1);
|
||||
};
|
||||
|
||||
it('can add an HTML component without a template', function () {
|
||||
verifyCreateHtmlComponent(this, 0, {
|
||||
"category": "html",
|
||||
"parent_locator": "locator-group-A"
|
||||
});
|
||||
});
|
||||
|
||||
it('can add an HTML component with a template', function () {
|
||||
verifyCreateHtmlComponent(this, 1, {
|
||||
"category": "html",
|
||||
"boilerplate": "announcement.yaml",
|
||||
"parent_locator": "locator-group-A"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
});
|
||||
},
|
||||
|
||||
acknowledgeXBlockDeletion: function(locator){
|
||||
this.notifyRuntime('deleted-child', locator);
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
var sortableInitializedClass = this.makeRequestSpecificSelector('.reorderable-container.ui-sortable');
|
||||
this.$(sortableInitializedClass).sortable('refresh');
|
||||
|
||||
164
cms/static/js/views/library_container.js
Normal file
164
cms/static/js/views/library_container.js
Normal file
@@ -0,0 +1,164 @@
|
||||
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 @@
|
||||
* 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/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/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,
|
||||
XBlockUtils) {
|
||||
'use strict';
|
||||
@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
|
||||
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.nameEditor = new XBlockStringFieldEditor({
|
||||
el: this.$('.wrapper-xblock-field'),
|
||||
model: this.model
|
||||
@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
if (this.options.action === 'new') {
|
||||
this.nameEditor.$('.xblock-field-value-edit').click();
|
||||
}
|
||||
this.xblockView = new ContainerView({
|
||||
el: this.$('.wrapper-xblock'),
|
||||
model: this.model,
|
||||
view: this.view
|
||||
});
|
||||
this.xblockView = this.getXBlockView();
|
||||
this.messageView = new ContainerSubviews.MessageView({
|
||||
el: this.$('.container-message'),
|
||||
model: this.model
|
||||
@@ -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) {
|
||||
var self = this,
|
||||
xblockView = this.xblockView,
|
||||
@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
|
||||
// Re-enable Backbone events for any updated DOM elements
|
||||
self.delegateEvents();
|
||||
}
|
||||
},
|
||||
block_added: options && options.block_added
|
||||
});
|
||||
},
|
||||
|
||||
@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
|
||||
modal.edit(xblockElement, this.model, {
|
||||
refresh: function() {
|
||||
self.refreshXBlock(xblockElement);
|
||||
self.refreshXBlock(xblockElement, false);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -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
|
||||
// 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.
|
||||
this.model.fetch();
|
||||
@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
onNewXBlock: function(xblockElement, scrollOffset, data) {
|
||||
ViewUtils.setScrollOffset(xblockElement, scrollOffset);
|
||||
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
|
||||
* reorderable container then the element will be refreshed inline. If not, then the
|
||||
* parent container will be refreshed instead.
|
||||
* @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),
|
||||
parentElement = xblockElement.parent(),
|
||||
rootLocator = this.xblockView.model.id;
|
||||
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')) {
|
||||
this.refreshChildXBlock(xblockElement);
|
||||
} else {
|
||||
this.refreshXBlock(this.findXBlockElement(parentElement));
|
||||
this.refreshXBlock(this.findXBlockElement(parentElement), block_added);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) {
|
||||
currentPage = collection.currentPage + 1,
|
||||
pageInput = this.$("#page-number-input"),
|
||||
pageNumber = parseInt(pageInput.val(), 10);
|
||||
if (pageNumber > collection.totalPages) {
|
||||
pageNumber = false;
|
||||
}
|
||||
if (pageNumber <= 0) {
|
||||
pageNumber = false;
|
||||
}
|
||||
if (pageNumber && pageNumber !== currentPage) {
|
||||
view.setPage(pageNumber - 1);
|
||||
}
|
||||
|
||||
119
cms/static/sass/elements/_pagination.scss
Normal file
119
cms/static/sass/elements/_pagination.scss
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,120 +28,7 @@
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@extend %pagination;
|
||||
}
|
||||
|
||||
.assets-table {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
// +Base - Elements
|
||||
// ====================
|
||||
@import 'elements/typography';
|
||||
@import 'elements/pagination'; // pagination
|
||||
@import 'elements/icons'; // references to icons used
|
||||
@import 'elements/controls'; // buttons, link styles, sliders, etc.
|
||||
@import 'elements/xblocks'; // studio rendering chrome for xblocks
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
// +Base - Elements
|
||||
// ====================
|
||||
@import 'elements/typography';
|
||||
@import 'elements/pagination'; // pagination
|
||||
@import 'elements/icons'; // references to icons used
|
||||
@import 'elements/controls'; // buttons, link styles, sliders, etc.
|
||||
@import 'elements/xblocks'; // studio rendering chrome for xblocks
|
||||
|
||||
@@ -31,7 +31,10 @@ from django.utils.translation import ugettext as _
|
||||
require(["js/factories/container"], function(ContainerFactory) {
|
||||
ContainerFactory(
|
||||
${component_templates | n}, ${json.dumps(xblock_info) | n},
|
||||
"${action}", ${json.dumps(is_unit_page)}
|
||||
"${action}",
|
||||
{
|
||||
isUnitPage: ${json.dumps(is_unit_page)}
|
||||
}
|
||||
);
|
||||
});
|
||||
</%block>
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
|
||||
<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>
|
||||
257
cms/templates/js/mock/mock-container-paged-xblock.underscore
Normal file
257
cms/templates/js/mock/mock-container-paged-xblock.underscore
Normal file
@@ -0,0 +1,257 @@
|
||||
<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 _
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/library"], function(LibraryFactory) {
|
||||
LibraryFactory(
|
||||
${component_templates | n},
|
||||
${json.dumps(xblock_info) | n}
|
||||
${component_templates | n}, ${json.dumps(xblock_info) | n},
|
||||
{
|
||||
isUnitPage: false,
|
||||
enable_paging: true,
|
||||
page_size: 10
|
||||
}
|
||||
);
|
||||
});
|
||||
</%block>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"""
|
||||
import logging
|
||||
|
||||
from .studio_editable import StudioEditableModule
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope, String, List
|
||||
from xblock.fragment import Fragment
|
||||
from xmodule.studio_editable import StudioEditableModule
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,29 +42,55 @@ class LibraryRoot(XBlock):
|
||||
|
||||
def author_view(self, context):
|
||||
"""
|
||||
Renders the Studio preview view, which supports drag and drop.
|
||||
Renders the Studio preview view.
|
||||
"""
|
||||
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 = []
|
||||
|
||||
for child_key in self.children: # pylint: disable=E1101
|
||||
context['reorderable_items'].add(child_key)
|
||||
paging = context.get('paging', None)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
contents.append({
|
||||
'id': unicode(child_key),
|
||||
'content': rendered_child.content,
|
||||
'id': child.location.to_deprecated_string(),
|
||||
'content': rendered_child.content
|
||||
})
|
||||
|
||||
fragment.add_content(self.runtime.render_template("studio_render_children_view.html", {
|
||||
'items': contents,
|
||||
'xblock_context': context,
|
||||
'can_add': True,
|
||||
'can_reorder': True,
|
||||
}))
|
||||
return fragment
|
||||
fragment.add_content(
|
||||
self.runtime.render_template("studio_render_paged_children_view.html", {
|
||||
'items': contents,
|
||||
'xblock_context': context,
|
||||
'can_add': can_add,
|
||||
'can_reorder': False,
|
||||
'first_displayed': item_start,
|
||||
'total_children': children_count,
|
||||
'displayed_children': len(children_to_show)
|
||||
})
|
||||
)
|
||||
|
||||
@property
|
||||
def display_org_with_default(self):
|
||||
|
||||
@@ -155,7 +155,6 @@ class VideoStudentViewHandlers(object):
|
||||
|
||||
if transcript_name:
|
||||
# Get the asset path for course
|
||||
asset_path = None
|
||||
course = self.descriptor.runtime.modulestore.get_course(self.course_id)
|
||||
if course.static_asset_path:
|
||||
asset_path = course.static_asset_path
|
||||
|
||||
23
lms/templates/studio_render_paged_children_view.html
Normal file
23
lms/templates/studio_render_paged_children_view.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<%! 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>
|
||||
Reference in New Issue
Block a user