Merge pull request #6939 from open-craft/content_libraries/previews
Add show/hide previews button to Content Libraries
This commit is contained in:
@@ -16,6 +16,7 @@ from xmodule.modulestore.django import modulestore
|
||||
from xblock.core import XBlock
|
||||
from xblock.django.request import webob_to_django_response, django_to_webob_request
|
||||
from xblock.exceptions import NoSuchHandlerError
|
||||
from xblock.fields import Scope
|
||||
from xblock.plugin import PluginMissingError
|
||||
from xblock.runtime import Mixologist
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
# pylint: disable=too-many-format-args
|
||||
return HttpResponse(
|
||||
content="Couldn't parse paging parameters: enable_paging: "
|
||||
"%s, page_number: %s, page_size: %s".format(
|
||||
"{0}, page_number: {1}, page_size: {2}".format(
|
||||
request.REQUEST.get('enable_paging', 'false'),
|
||||
request.REQUEST.get('page_number', 0),
|
||||
request.REQUEST.get('page_size', 0)
|
||||
@@ -273,6 +273,8 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
content_type="text/plain",
|
||||
)
|
||||
|
||||
force_render = request.REQUEST.get('force_render', None)
|
||||
|
||||
# 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
|
||||
@@ -281,6 +283,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
'root_xblock': xblock if (view_name == 'container_preview') else None,
|
||||
'reorderable_items': reorderable_items,
|
||||
'paging': paging,
|
||||
'force_render': force_render,
|
||||
}
|
||||
|
||||
fragment = get_preview_fragment(request, xblock, context)
|
||||
|
||||
@@ -231,4 +231,5 @@ def manage_library_users(request, library_key_string):
|
||||
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
|
||||
'library_key': unicode(library_key),
|
||||
'lib_users_url': reverse_library_url('manage_library_users', library_key_string),
|
||||
'show_children_previews': library.show_children_previews
|
||||
})
|
||||
|
||||
@@ -113,6 +113,12 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
def render_child_placeholder(self, block, view_name, context):
|
||||
"""
|
||||
Renders a placeholder XBlock.
|
||||
"""
|
||||
return self.wrap_xblock(block, view_name, Fragment(), context)
|
||||
|
||||
|
||||
class StudioPermissionsService(object):
|
||||
"""
|
||||
@@ -240,6 +246,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
template_context = {
|
||||
'xblock_context': context,
|
||||
'xblock': xblock,
|
||||
'show_preview': context.get('show_preview', True),
|
||||
'content': frag.content,
|
||||
'is_root': is_root,
|
||||
'is_reorderable': is_reorderable,
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/models/xblock_info",
|
||||
"js/views/paged_container", "js/views/paging_header", "js/views/paging_footer"],
|
||||
function ($, _, AjaxHelpers, URI, XBlockInfo, PagedContainer, PagingHeader, PagingFooter) {
|
||||
"js/views/paged_container", "js/views/paging_header", "js/views/paging_footer", "js/views/xblock"],
|
||||
function ($, _, AjaxHelpers, URI, XBlockInfo, PagedContainer, PagingHeader, PagingFooter, XBlockView) {
|
||||
|
||||
var htmlResponseTpl = _.template('' +
|
||||
'<div class="xblock-container-paging-parameters" data-start="<%= start %>" data-displayed="<%= displayed %>" data-total="<%= total %>"/>'
|
||||
'<div class="xblock-container-paging-parameters" ' +
|
||||
'data-start="<%= start %>" ' +
|
||||
'data-displayed="<%= displayed %>" ' +
|
||||
'data-total="<%= total %>" ' +
|
||||
'data-previews="<%= previews %>"></div>'
|
||||
);
|
||||
|
||||
function getResponseHtml(options){
|
||||
function getResponseHtml(override_options){
|
||||
var default_options = {
|
||||
start: 0,
|
||||
displayed: PAGE_SIZE,
|
||||
total: PAGE_SIZE + 1,
|
||||
previews: true
|
||||
};
|
||||
var options = _.extend(default_options, override_options);
|
||||
return '<div class="xblock" data-request-token="request_token">' +
|
||||
'<div class="container-paging-header"></div>' +
|
||||
htmlResponseTpl(options) +
|
||||
@@ -14,43 +25,43 @@ define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/mo
|
||||
'</div>'
|
||||
}
|
||||
|
||||
var makePage = function(html_parameters) {
|
||||
return {
|
||||
resources: [],
|
||||
html: getResponseHtml(html_parameters)
|
||||
};
|
||||
};
|
||||
|
||||
var PAGE_SIZE = 3;
|
||||
|
||||
var mockFirstPage = {
|
||||
resources: [],
|
||||
html: getResponseHtml({
|
||||
var mockFirstPage = makePage({
|
||||
start: 0,
|
||||
displayed: PAGE_SIZE,
|
||||
total: PAGE_SIZE + 1
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
var mockSecondPage = {
|
||||
resources: [],
|
||||
html: getResponseHtml({
|
||||
start: PAGE_SIZE,
|
||||
displayed: 1,
|
||||
total: PAGE_SIZE + 1
|
||||
})
|
||||
};
|
||||
var mockSecondPage = makePage({
|
||||
start: PAGE_SIZE,
|
||||
displayed: 1,
|
||||
total: PAGE_SIZE + 1
|
||||
});
|
||||
|
||||
var mockEmptyPage = {
|
||||
resources: [],
|
||||
html: getResponseHtml({
|
||||
start: 0,
|
||||
displayed: 0,
|
||||
total: 0
|
||||
})
|
||||
};
|
||||
var mockEmptyPage = makePage({
|
||||
start: 0,
|
||||
displayed: 0,
|
||||
total: 0
|
||||
});
|
||||
|
||||
var respondWithMockPage = function(requests) {
|
||||
var respondWithMockPage = function(requests, mockPage) {
|
||||
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);
|
||||
if (typeof mockPage == 'undefined') {
|
||||
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;
|
||||
mockPage = page === "0" ? mockFirstPage : mockSecondPage;
|
||||
}
|
||||
AjaxHelpers.respondWithJson(requests, mockPage, requestIndex);
|
||||
};
|
||||
|
||||
var MockPagingView = PagedContainer.extend({
|
||||
@@ -65,10 +76,26 @@ define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/mo
|
||||
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 });
|
||||
pagingContainer = new MockPagingView({page_size: PAGE_SIZE});
|
||||
});
|
||||
|
||||
describe("Container", function () {
|
||||
describe("rendering", function(){
|
||||
|
||||
it('should set show_previews', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
expect(pagingContainer.collection.showChildrenPreviews).toBe(true); //precondition check
|
||||
|
||||
pagingContainer.setPage(0);
|
||||
respondWithMockPage(requests, makePage({previews: false}));
|
||||
expect(pagingContainer.collection.showChildrenPreviews).toBe(false);
|
||||
|
||||
pagingContainer.setPage(0);
|
||||
respondWithMockPage(requests, makePage({previews: true}));
|
||||
expect(pagingContainer.collection.showChildrenPreviews).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPage", function () {
|
||||
it('can set the current page', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
@@ -304,8 +331,6 @@ define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/mo
|
||||
});
|
||||
|
||||
describe("PagingFooter", function () {
|
||||
var pagingFooter;
|
||||
|
||||
beforeEach(function () {
|
||||
var pagingFooterTpl = readFixtures('paging-footer.underscore');
|
||||
appendSetFixtures($("<script>", { id: "paging-footer-tpl", type: "text/template" }).text(pagingFooterTpl));
|
||||
@@ -485,5 +510,57 @@ define([ "jquery", "underscore", "js/common_helpers/ajax_helpers", "URI", "js/mo
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Previews", function(){
|
||||
describe("Toggle Previews", function(){
|
||||
var testSendsAjax,
|
||||
defaultUrl = "/preview/xblock/handler/trigger_previews";
|
||||
|
||||
testSendsAjax = function (show_previews) {
|
||||
it("should send " + (!show_previews) + " when showChildrenPreviews was " + show_previews, function(){
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
pagingContainer.collection.showChildrenPreviews = show_previews;
|
||||
pagingContainer.togglePreviews();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', defaultUrl, { showChildrenPreviews: !show_previews});
|
||||
AjaxHelpers.respondWithJson(requests, { showChildrenPreviews: !show_previews });
|
||||
});
|
||||
};
|
||||
testSendsAjax(true);
|
||||
testSendsAjax(false);
|
||||
|
||||
it("should trigger render on success", function(){
|
||||
spyOn(pagingContainer, 'render');
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
|
||||
pagingContainer.togglePreviews();
|
||||
AjaxHelpers.respondWithJson(requests, { showChildrenPreviews: true });
|
||||
|
||||
expect(pagingContainer.render).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not trigger render on failure", function(){
|
||||
spyOn(pagingContainer, 'render');
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
|
||||
pagingContainer.togglePreviews();
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
expect(pagingContainer.render).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should send force_render when new block causes page change", function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
pagingContainer.setPage(0);
|
||||
respondWithMockPage(requests);
|
||||
spyOn(pagingContainer, 'render');
|
||||
var mockXBlockInfo = new XBlockInfo({id: 'mock-location'});
|
||||
var mockXBlockView = new XBlockView({model: mockXBlockInfo});
|
||||
mockXBlockView.model.id = 'mock-location';
|
||||
pagingContainer.refresh(mockXBlockView, true);
|
||||
expect(pagingContainer.render).toHaveBeenCalled();
|
||||
expect(pagingContainer.render.mostRecentCall.args[0].force_render).toEqual('mock-location');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore'),
|
||||
mockXBlockVisibilityEditorHtml = readFixtures('mock/mock-xblock-visibility-editor.underscore'),
|
||||
PageClass = fixtures.page,
|
||||
pagedSpecificTests = fixtures.paged_specific_tests,
|
||||
hasVisibilityEditor = fixtures.has_visibility_editor;
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -305,13 +306,9 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
}
|
||||
);
|
||||
|
||||
paginated = function () {
|
||||
return containerPage instanceof PagedContainerPage;
|
||||
};
|
||||
|
||||
getDeleteOffset = function () {
|
||||
// Paginated containers will make an additional AJAX request.
|
||||
return paginated() ? 3 : 2;
|
||||
return pagedSpecificTests ? 3 : 2;
|
||||
};
|
||||
|
||||
getGroupElement = function () {
|
||||
@@ -509,6 +506,48 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
});
|
||||
});
|
||||
|
||||
describe("Previews", function () {
|
||||
|
||||
var getButtonIcon, getButtonText;
|
||||
|
||||
getButtonIcon = function (containerPage) {
|
||||
return containerPage.$('.action-toggle-preview i');
|
||||
};
|
||||
|
||||
getButtonText = function (containerPage) {
|
||||
return containerPage.$('.action-toggle-preview .preview-text').text().trim();
|
||||
};
|
||||
|
||||
if (pagedSpecificTests) {
|
||||
it('has no text on the preview button to start with', function () {
|
||||
containerPage = getContainerPage();
|
||||
expect(getButtonIcon(containerPage)).toHaveClass('fa-refresh');
|
||||
expect(getButtonIcon(containerPage).parent()).toHaveClass('is-hidden');
|
||||
expect(getButtonText(containerPage)).toBe("");
|
||||
});
|
||||
|
||||
function updatePreviewButtonTest(show_previews, expected_text) {
|
||||
it('can set preview button to "' + expected_text + '"', function () {
|
||||
containerPage = getContainerPage();
|
||||
containerPage.updatePreviewButton(show_previews);
|
||||
expect(getButtonText(containerPage)).toBe(expected_text);
|
||||
});
|
||||
}
|
||||
|
||||
updatePreviewButtonTest(true, 'Hide Previews');
|
||||
updatePreviewButtonTest(false, 'Show Previews');
|
||||
|
||||
it('triggers underlying view togglePreviews when preview button clicked', function () {
|
||||
containerPage = getContainerPage();
|
||||
containerPage.render();
|
||||
spyOn(containerPage.xblockView, 'togglePreviews');
|
||||
|
||||
containerPage.$('.toggle-preview-button').click();
|
||||
expect(containerPage.xblockView.togglePreviews).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('createNewComponent ', function () {
|
||||
var clickNewComponent;
|
||||
|
||||
@@ -591,6 +630,7 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -601,7 +641,8 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
page: ContainerPage,
|
||||
initial: 'mock/mock-container-xblock.underscore',
|
||||
add_response: 'mock/mock-xblock.underscore',
|
||||
has_visibility_editor: true
|
||||
has_visibility_editor: true,
|
||||
paged_specific_tests: false
|
||||
}
|
||||
);
|
||||
|
||||
@@ -612,7 +653,8 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
|
||||
page: PagedContainerPage,
|
||||
initial: 'mock/mock-container-paged-xblock.underscore',
|
||||
add_response: 'mock/mock-xblock-paged.underscore',
|
||||
has_visibility_editor: false
|
||||
has_visibility_editor: false,
|
||||
paged_specific_tests: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettext",
|
||||
define(["jquery", "underscore", "js/views/utils/view_utils", "js/views/container", "js/utils/module", "gettext",
|
||||
"js/views/feedback_notification", "js/views/paging_header", "js/views/paging_footer", "js/views/paging_mixin"],
|
||||
function ($, _, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter, PagingMixin) {
|
||||
function ($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter, PagingMixin) {
|
||||
var PagedContainerView = ContainerView.extend(PagingMixin).extend({
|
||||
initialize: function(options){
|
||||
var self = this;
|
||||
@@ -24,7 +24,9 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
bind: function() {},
|
||||
// size() on backbone collections shows how many objects are in the collection, or in the case
|
||||
// of paginator, on the current page.
|
||||
size: function() { return self.collection._size; }
|
||||
size: function() { return self.collection._size; },
|
||||
// Toggles the functionality for showing and hiding child previews.
|
||||
showChildrenPreviews: true
|
||||
};
|
||||
},
|
||||
|
||||
@@ -47,21 +49,29 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
url: decodeURIComponent(xblockUrl) + "/" + view,
|
||||
type: 'GET',
|
||||
cache: false,
|
||||
data: this.getRenderParameters(options.page_number),
|
||||
data: this.getRenderParameters(options.page_number, options.force_render),
|
||||
headers: { Accept: 'application/json' },
|
||||
success: function(fragment) {
|
||||
self.handleXBlockFragment(fragment, options);
|
||||
self.processPaging({ requested_page: options.page_number });
|
||||
self.page.updatePreviewButton(self.collection.showChildrenPreviews);
|
||||
self.page.renderAddXBlockComponents();
|
||||
if (options.force_render) {
|
||||
var target = $('.studio-xblock-wrapper[data-locator="' + options.force_render + '"]');
|
||||
// Scroll us to the element with a little buffer at the top for context.
|
||||
ViewUtils.setScrollOffset(target, ($(window).height() * .10));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getRenderParameters: function(page_number) {
|
||||
getRenderParameters: function(page_number, force_render) {
|
||||
// Options should at least contain page_number.
|
||||
return {
|
||||
page_size: this.page_size,
|
||||
enable_paging: true,
|
||||
page_number: page_number
|
||||
page_number: page_number,
|
||||
force_render: force_render
|
||||
};
|
||||
},
|
||||
|
||||
@@ -70,8 +80,10 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
return Math.ceil(total_count / this.page_size);
|
||||
},
|
||||
|
||||
setPage: function(page_number) {
|
||||
this.render({ page_number: page_number});
|
||||
setPage: function(page_number, additional_options) {
|
||||
additional_options = additional_options || {};
|
||||
var options = _.extend({page_number: page_number}, additional_options);
|
||||
this.render(options);
|
||||
},
|
||||
|
||||
processPaging: function(options){
|
||||
@@ -80,13 +92,15 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
var $element = this.$el.find('.xblock-container-paging-parameters'),
|
||||
total = $element.data('total'),
|
||||
displayed = $element.data('displayed'),
|
||||
start = $element.data('start');
|
||||
start = $element.data('start'),
|
||||
previews = $element.data('previews');
|
||||
|
||||
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.collection.showChildrenPreviews = previews;
|
||||
|
||||
this.processPagingHeaderAndFooter();
|
||||
},
|
||||
@@ -112,23 +126,44 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
this.pagingFooter.render();
|
||||
},
|
||||
|
||||
refresh: function(block_added) {
|
||||
if (block_added) {
|
||||
this.collection.totalCount += 1;
|
||||
this.collection._size +=1;
|
||||
if (this.collection.totalCount == 1) {
|
||||
this.render();
|
||||
refresh: function(xblockView, block_added, is_duplicate) {
|
||||
if (!block_added) {
|
||||
return
|
||||
}
|
||||
if (is_duplicate) {
|
||||
// Duplicated blocks can be inserted onto the current page.
|
||||
var xblock = xblockView.xblock.element.parents(".studio-xblock-wrapper").first();
|
||||
var all_xblocks = xblock.parent().children(".studio-xblock-wrapper");
|
||||
var index = all_xblocks.index(xblock);
|
||||
if ((index + 1 <= this.page_size) && (all_xblocks.length > this.page_size)) {
|
||||
// Pop the last XBlock off the bottom.
|
||||
all_xblocks[all_xblocks.length - 1].remove();
|
||||
return
|
||||
}
|
||||
this.collection.totalPages = this.getPageCount(this.collection.totalCount);
|
||||
var new_page = this.collection.totalPages - 1;
|
||||
// If we're on a new page due to overflow, or this is the first item, set the page.
|
||||
if (((this.collection.currentPage) != new_page) || this.collection.totalCount == 1) {
|
||||
this.setPage(new_page);
|
||||
} else {
|
||||
this.pagingHeader.render();
|
||||
this.pagingFooter.render();
|
||||
}
|
||||
this.collection.totalCount += 1;
|
||||
this.collection._size +=1;
|
||||
if (this.collection.totalCount == 1) {
|
||||
this.render();
|
||||
return
|
||||
}
|
||||
this.collection.totalPages = this.getPageCount(this.collection.totalCount);
|
||||
var target_page = this.collection.totalPages - 1;
|
||||
// If we're on a new page due to overflow, or this is the first item, set the page.
|
||||
if (((this.collection.currentPage) != target_page) || this.collection.totalCount == 1) {
|
||||
var force_render = xblockView.model.id;
|
||||
if (is_duplicate) {
|
||||
// The duplicate should be on the next page if we've gotten here.
|
||||
target_page = this.collection.currentPage + 1;
|
||||
}
|
||||
this.setPage(
|
||||
target_page,
|
||||
{force_render: force_render}
|
||||
);
|
||||
|
||||
} else {
|
||||
this.pagingHeader.render();
|
||||
this.pagingFooter.render();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -157,6 +192,19 @@ define(["jquery", "underscore", "js/views/container", "js/utils/module", "gettex
|
||||
|
||||
sortDisplayName: function() {
|
||||
return gettext("Date added"); // TODO add support for sorting
|
||||
},
|
||||
|
||||
togglePreviews: function(){
|
||||
var self = this,
|
||||
xblockUrl = this.model.url();
|
||||
return $.ajax({
|
||||
// No runtime, so can't get this via the handler() call.
|
||||
url: '/preview' + decodeURIComponent(xblockUrl) + "/handler/trigger_previews",
|
||||
type: 'POST',
|
||||
data: JSON.stringify({ showChildrenPreviews: !this.collection.showChildrenPreviews}),
|
||||
dataType: 'json'
|
||||
})
|
||||
.then(self.render).promise();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
return this.xblockView.model.urlRoot;
|
||||
},
|
||||
|
||||
onXBlockRefresh: function(xblockView, block_added) {
|
||||
this.xblockView.refresh(block_added);
|
||||
onXBlockRefresh: function(xblockView, block_added, is_duplicate) {
|
||||
this.xblockView.refresh(xblockView, block_added, is_duplicate);
|
||||
// Update publish and last modified information from the server.
|
||||
this.model.fetch();
|
||||
},
|
||||
@@ -214,7 +214,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
parent_locator: parentLocator
|
||||
});
|
||||
return $.postJSON(this.getURLRoot() + '/', requestData,
|
||||
_.bind(this.onNewXBlock, this, placeholderElement, scrollOffset))
|
||||
_.bind(this.onNewXBlock, this, placeholderElement, scrollOffset, false))
|
||||
.fail(function() {
|
||||
// Remove the placeholder if the update failed
|
||||
placeholderElement.remove();
|
||||
@@ -237,7 +237,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
parent_locator: parentElement.data('locator')
|
||||
};
|
||||
return $.postJSON(self.getURLRoot() + '/', requestData,
|
||||
_.bind(self.onNewXBlock, self, placeholderElement, scrollOffset))
|
||||
_.bind(self.onNewXBlock, self, placeholderElement, scrollOffset, true))
|
||||
.fail(function() {
|
||||
// Remove the placeholder if the update failed
|
||||
placeholderElement.remove();
|
||||
@@ -269,10 +269,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
this.model.fetch();
|
||||
},
|
||||
|
||||
onNewXBlock: function(xblockElement, scrollOffset, data) {
|
||||
onNewXBlock: function(xblockElement, scrollOffset, is_duplicate, data) {
|
||||
ViewUtils.setScrollOffset(xblockElement, scrollOffset);
|
||||
xblockElement.data('locator', data.locator);
|
||||
return this.refreshXBlock(xblockElement, true);
|
||||
return this.refreshXBlock(xblockElement, true, is_duplicate);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -282,14 +282,14 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
* @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, block_added) {
|
||||
refreshXBlock: function(element, block_added, is_duplicate) {
|
||||
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, block_added: block_added});
|
||||
} else if (parentElement.hasClass('reorderable-container')) {
|
||||
this.refreshChildXBlock(xblockElement, block_added);
|
||||
this.refreshChildXBlock(xblockElement, block_added, is_duplicate);
|
||||
} else {
|
||||
this.refreshXBlock(this.findXBlockElement(parentElement));
|
||||
}
|
||||
@@ -303,7 +303,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
* refreshing.
|
||||
* @returns {jQuery promise} A promise representing the complete operation.
|
||||
*/
|
||||
refreshChildXBlock: function(xblockElement, block_added) {
|
||||
refreshChildXBlock: function(xblockElement, block_added, is_duplicate) {
|
||||
var self = this,
|
||||
xblockInfo,
|
||||
TemporaryXBlockView,
|
||||
@@ -329,7 +329,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
});
|
||||
return temporaryView.render({
|
||||
success: function() {
|
||||
self.onXBlockRefresh(temporaryView, block_added);
|
||||
self.onXBlockRefresh(temporaryView, block_added, is_duplicate);
|
||||
temporaryView.unbind(); // Remove the temporary view
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,11 +6,14 @@ define(["jquery", "underscore", "gettext", "js/views/pages/container", "js/views
|
||||
'use strict';
|
||||
var PagedXBlockContainerPage = XBlockContainerPage.extend({
|
||||
|
||||
events: {"click .toggle-preview-button": "toggleChildrenPreviews"},
|
||||
defaultViewClass: PagedContainerView,
|
||||
components_on_init: false,
|
||||
|
||||
initialize: function (options){
|
||||
this.events = _.extend({}, XBlockContainerPage.prototype.events, this.events);
|
||||
this.page_size = options.page_size || 10;
|
||||
this.showChildrenPreviews = options.showChildrenPreviews || true;
|
||||
XBlockContainerPage.prototype.initialize.call(this, options);
|
||||
},
|
||||
|
||||
@@ -21,16 +24,28 @@ define(["jquery", "underscore", "gettext", "js/views/pages/container", "js/views
|
||||
});
|
||||
},
|
||||
|
||||
refreshXBlock: function(element, block_added) {
|
||||
refreshXBlock: function(element, block_added, is_duplicate) {
|
||||
var xblockElement = this.findXBlockElement(element),
|
||||
rootLocator = this.xblockView.model.id;
|
||||
if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) {
|
||||
this.render({refresh: true, block_added: block_added});
|
||||
} else {
|
||||
this.refreshChildXBlock(xblockElement, block_added);
|
||||
this.refreshChildXBlock(xblockElement, block_added, is_duplicate);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toggleChildrenPreviews: function(xblockElement) {
|
||||
xblockElement.preventDefault();
|
||||
this.xblockView.togglePreviews();
|
||||
},
|
||||
|
||||
updatePreviewButton: function(show_previews){
|
||||
var text = (show_previews) ? gettext('Hide Previews') : gettext('Show Previews'),
|
||||
button = $('.nav-actions .button-toggle-preview');
|
||||
|
||||
this.$(".preview-text", button).text(text);
|
||||
this.$('.toggle-preview-button').removeClass("is-hidden");
|
||||
}
|
||||
});
|
||||
return PagedXBlockContainerPage;
|
||||
});
|
||||
|
||||
@@ -356,6 +356,10 @@
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid $gray-l4;
|
||||
background-color: $gray-l6;
|
||||
&.is-collapsed {
|
||||
border-bottom: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.xblock-render {
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
<span class="action-button-text">Add Component</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="action-item action-toggle-preview nav-item">
|
||||
<a href="#" class="button button-toggle-preview action-button toggle-preview-button is-hidden">
|
||||
<i class="icon fa fa-refresh"></i>
|
||||
<span class="action-button-text preview-text"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
261
cms/templates/js/mock/mock-paged-container-xblock.underscore
Normal file
261
cms/templates/js/mock/mock-paged-container-xblock.underscore
Normal file
@@ -0,0 +1,261 @@
|
||||
<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 fa fa-angle-left"></i> <span class="nav-label">Previous</span></a></li>
|
||||
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label">Next</span> <i class="icon fa fa-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 fa fa-angle-left"></i> <span class="nav-label">Previous</span></a></li>
|
||||
<li class="nav-item page">
|
||||
<div class="pagination-form">
|
||||
<label class="page-number-label" for="page-number">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">Next</span> <i class="icon fa fa-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 fa fa-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>
|
||||
<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>
|
||||
<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 fa fa-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>
|
||||
<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>
|
||||
<div class="add-xblock-component new-component-item adding"></div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<div class="container-paging-footer"></div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -27,7 +27,8 @@ from django.utils.translation import ugettext as _
|
||||
{
|
||||
isUnitPage: false,
|
||||
page_size: 10,
|
||||
canEdit: ${"true" if can_edit else "false"}
|
||||
canEdit: ${"true" if can_edit else "false"},
|
||||
showChildrenPreviews: ${'true' if show_children_previews else 'false'}
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -53,7 +54,13 @@ from django.utils.translation import ugettext as _
|
||||
<a href="#" class="button new-button new-component-button">
|
||||
<i class="icon fa fa-plus icon-inline"></i> <span class="action-button-text">${_("Add Component")}</span>
|
||||
</a>
|
||||
</li>
|
||||
</li>
|
||||
<li class="action-item action-toggle-preview nav-item">
|
||||
<a href="#" class="button button-toggle-preview action-button toggle-preview-button is-hidden">
|
||||
<i class="icon fa fa-refresh"></i>
|
||||
<span class="action-button-text preview-text"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -47,7 +47,11 @@ messages = json.dumps(xblock.validate().to_json())
|
||||
% endif
|
||||
|
||||
<header class="xblock-header xblock-header-${xblock.category}">
|
||||
<div class="xblock-header-primary">
|
||||
<div class="xblock-header-primary
|
||||
% if not show_preview:
|
||||
is-collapsed
|
||||
% endif
|
||||
">
|
||||
<div class="header-details">
|
||||
% if show_inline:
|
||||
<a href="#" data-tooltip="${_('Expand or Collapse')}" class="action expand-collapse collapse">
|
||||
@@ -128,14 +132,17 @@ messages = json.dumps(xblock.validate().to_json())
|
||||
<div class="wrapper-xblock-message xblock-validation-messages" data-locator="${xblock.location | h}"/>
|
||||
% endif
|
||||
|
||||
% if is_root or not xblock_url:
|
||||
<article class="xblock-render">
|
||||
${content}
|
||||
</article>
|
||||
% else:
|
||||
<div class="xblock-message-area">
|
||||
${content}
|
||||
% endif
|
||||
% if show_preview:
|
||||
% if is_root or not xblock_url:
|
||||
<article class="xblock-render">
|
||||
${content}
|
||||
</article>
|
||||
% else:
|
||||
<div class="xblock-message-area">
|
||||
${content}
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if not is_root:
|
||||
</section>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
"""
|
||||
import logging
|
||||
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope, String, List
|
||||
from xblock.fragment import Fragment
|
||||
from xmodule.studio_editable import StudioEditableModule
|
||||
|
||||
from xblock.fields import Scope, String, List, Boolean
|
||||
from xblock.fragment import Fragment
|
||||
from xblock.core import XBlock
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Make '_' a no-op so we can scrape strings
|
||||
@@ -32,6 +33,12 @@ class LibraryRoot(XBlock):
|
||||
scope=Scope.settings,
|
||||
xml_node=True,
|
||||
)
|
||||
show_children_previews = Boolean(
|
||||
display_name="Hide children preview",
|
||||
help="Choose if preview of library contents is shown",
|
||||
scope=Scope.user_state,
|
||||
default=True
|
||||
)
|
||||
has_children = True
|
||||
has_author_view = True
|
||||
|
||||
@@ -69,10 +76,22 @@ class LibraryRoot(XBlock):
|
||||
|
||||
children_to_show = self.children[item_start:item_end] # pylint: disable=no-member
|
||||
|
||||
force_render = context.get('force_render', None)
|
||||
|
||||
for child_key in children_to_show: # pylint: disable=E1101
|
||||
# Children must have a separate context from the library itself. Make a copy.
|
||||
child_context = context.copy()
|
||||
child_context['show_preview'] = self.show_children_previews
|
||||
child = self.runtime.get_block(child_key)
|
||||
child_view_name = StudioEditableModule.get_preview_view_name(child)
|
||||
rendered_child = self.runtime.render_child(child, child_view_name, context)
|
||||
|
||||
if unicode(child.location) == force_render:
|
||||
child_context['show_preview'] = True
|
||||
|
||||
if child_context['show_preview']:
|
||||
rendered_child = self.runtime.render_child(child, child_view_name, child_context)
|
||||
else:
|
||||
rendered_child = self.runtime.render_child_placeholder(child, child_view_name, child_context)
|
||||
fragment.add_frag_resources(rendered_child)
|
||||
|
||||
contents.append({
|
||||
@@ -87,7 +106,8 @@ class LibraryRoot(XBlock):
|
||||
'can_add': can_add,
|
||||
'first_displayed': item_start,
|
||||
'total_children': children_count,
|
||||
'displayed_children': len(children_to_show)
|
||||
'displayed_children': len(children_to_show),
|
||||
'previews': self.show_children_previews
|
||||
})
|
||||
)
|
||||
|
||||
@@ -106,3 +126,9 @@ class LibraryRoot(XBlock):
|
||||
Always returns the raw 'library' field from the key.
|
||||
"""
|
||||
return self.scope_ids.usage_id.course_key.library
|
||||
|
||||
@XBlock.json_handler
|
||||
def trigger_previews(self, request_body, suffix): # pylint: disable=unused-argument
|
||||
""" Enable or disable previews in studio for library children. """
|
||||
self.show_children_previews = request_body.get('showChildrenPreviews', self.show_children_previews)
|
||||
return {'showChildrenPreviews': self.show_children_previews}
|
||||
|
||||
@@ -500,6 +500,12 @@ class XBlockWrapper(PageObject):
|
||||
"""
|
||||
self.q(css=self._bounded_selector('span.message-text a')).first.click()
|
||||
|
||||
def is_placeholder(self):
|
||||
"""
|
||||
Checks to see if the XBlock is rendered as a placeholder without a preview.
|
||||
"""
|
||||
return not self.q(css=self._bounded_selector('.wrapper-xblock article')).present
|
||||
|
||||
@property
|
||||
def group_configuration_link_name(self):
|
||||
"""
|
||||
|
||||
@@ -69,6 +69,25 @@ class LibraryEditPage(LibraryPage, PaginatedMixin):
|
||||
"""
|
||||
return self._get_xblocks()
|
||||
|
||||
def are_previews_showing(self):
|
||||
"""
|
||||
Determines whether or not previews are showing for XBlocks
|
||||
"""
|
||||
return all([not xblock.is_placeholder() for xblock in self.xblocks])
|
||||
|
||||
def toggle_previews(self):
|
||||
"""
|
||||
Clicks the preview toggling button and waits for the previews to appear or disappear.
|
||||
"""
|
||||
toggle = not self.are_previews_showing()
|
||||
self.q(css='.toggle-preview-button').click()
|
||||
EmptyPromise(
|
||||
lambda: self.are_previews_showing() == toggle,
|
||||
'Preview is visible: %s' % toggle,
|
||||
timeout=30
|
||||
).fulfill()
|
||||
self.wait_until_ready()
|
||||
|
||||
def click_duplicate_button(self, xblock_id):
|
||||
"""
|
||||
Click on the duplicate button for the given XBlock
|
||||
|
||||
@@ -51,7 +51,6 @@ class LibraryEditPageTest(StudioLibraryTest):
|
||||
Then one XBlock is displayed
|
||||
And displayed XBlock are second one
|
||||
"""
|
||||
self.browser.save_screenshot('library_page')
|
||||
self.assertEqual(len(self.lib_page.xblocks), 0)
|
||||
|
||||
# Create a new block:
|
||||
@@ -343,6 +342,161 @@ class LibraryNavigationTest(StudioLibraryTest):
|
||||
self.assertEqual(self.lib_page.xblocks[-1].name, '11')
|
||||
self.assertEqual(self.lib_page.get_page_number(), '1')
|
||||
|
||||
def test_previews(self):
|
||||
"""
|
||||
Scenario: Ensure the user is able to hide previews of XBlocks.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
Then previews are visible
|
||||
And when I click the toggle previews button
|
||||
Then the previews will not be visible
|
||||
And when I click the toggle previews button
|
||||
Then the previews are visible
|
||||
"""
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
self.assertFalse(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
|
||||
def test_previews_navigation(self):
|
||||
"""
|
||||
Scenario: Ensure preview settings persist across navigation.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
Then previews are visible
|
||||
And when I click the toggle previews button
|
||||
And click the next page button
|
||||
Then the previews will not be visible
|
||||
And the first XBlock will be the 11th one
|
||||
And the last XBlock will be the 20th one
|
||||
And when I click the toggle previews button
|
||||
And I click the previous page button
|
||||
Then the previews will be visible
|
||||
And the first XBlock will be the first one
|
||||
And the last XBlock will be the 11th one
|
||||
"""
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
# Which set of arrows shouldn't matter for this test.
|
||||
self.lib_page.move_forward('top')
|
||||
self.assertFalse(self.lib_page.are_previews_showing())
|
||||
self.assertEqual(self.lib_page.xblocks[0].name, '11')
|
||||
self.assertEqual(self.lib_page.xblocks[-1].name, '20')
|
||||
self.lib_page.toggle_previews()
|
||||
self.lib_page.move_back('top')
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.assertEqual(self.lib_page.xblocks[0].name, '1')
|
||||
self.assertEqual(self.lib_page.xblocks[-1].name, '10')
|
||||
|
||||
def test_preview_state_persistance(self):
|
||||
"""
|
||||
Scenario: Ensure preview state persists between page loads.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
Then previews are visible
|
||||
And when I click the toggle previews button
|
||||
And I revisit the page
|
||||
Then the previews will not be visible
|
||||
"""
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
self.lib_page.visit()
|
||||
self.lib_page.wait_until_ready()
|
||||
self.assertFalse(self.lib_page.are_previews_showing())
|
||||
|
||||
def test_preview_add_xblock(self):
|
||||
"""
|
||||
Scenario: Ensure previews are shown when adding new blocks, regardless of preview setting.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
Then previews are visible
|
||||
And when I click the toggle previews button
|
||||
Then the previews will not be visible
|
||||
And when I add an XBlock
|
||||
Then I will be on the 5th page
|
||||
And the XBlock will have loaded a preview
|
||||
And when I revisit the library
|
||||
And I go to the 5th page
|
||||
Then the top XBlock will be the one I added
|
||||
And it will not have a preview
|
||||
And when I add an XBlock
|
||||
Then the XBlock I added will have a preview
|
||||
And the top XBlock will not have one.
|
||||
"""
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
self.assertFalse(self.lib_page.are_previews_showing())
|
||||
add_component(self.lib_page, "problem", "Checkboxes")
|
||||
self.assertEqual(self.lib_page.get_page_number(), '5')
|
||||
first_added = self.lib_page.xblocks[0]
|
||||
self.assertIn("Checkboxes", first_added.name)
|
||||
self.assertFalse(self.lib_page.xblocks[0].is_placeholder())
|
||||
self.lib_page.visit()
|
||||
self.lib_page.wait_until_ready()
|
||||
self.lib_page.go_to_page(5)
|
||||
self.assertTrue(self.lib_page.xblocks[0].is_placeholder())
|
||||
add_component(self.lib_page, "problem", "Multiple Choice")
|
||||
# DOM has detatched the element since last assignment
|
||||
first_added = self.lib_page.xblocks[0]
|
||||
second_added = self.lib_page.xblocks[1]
|
||||
self.assertIn("Multiple Choice", second_added.name)
|
||||
self.assertFalse(second_added.is_placeholder())
|
||||
self.assertTrue(first_added.is_placeholder())
|
||||
|
||||
def test_edit_with_preview(self):
|
||||
"""
|
||||
Scenario: Editing an XBlock should show me a preview even if previews are hidden.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
Then previews are visible
|
||||
And when I click the toggle previews button
|
||||
Then the previews will not be visible
|
||||
And when I edit the first XBlock
|
||||
Then the first XBlock will show a preview
|
||||
And the other XBlocks will still be placeholders
|
||||
"""
|
||||
self.assertTrue(self.lib_page.are_previews_showing())
|
||||
self.lib_page.toggle_previews()
|
||||
self.assertFalse(self.lib_page.are_previews_showing())
|
||||
target = self.lib_page.xblocks[0]
|
||||
target.edit()
|
||||
target.save_settings()
|
||||
self.assertFalse(target.is_placeholder())
|
||||
self.assertTrue(all([xblock.is_placeholder() for xblock in self.lib_page.xblocks[1:]]))
|
||||
|
||||
def test_duplicate_xblock_pagination(self):
|
||||
"""
|
||||
Scenario: Duplicating an XBlock should not shift the page if the XBlock is not at the end.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
When I duplicate the third XBlock
|
||||
Then the page should not change
|
||||
And the duplicate XBlock should be there
|
||||
And it should show a preview
|
||||
And there should not be more than 10 XBlocks visible.
|
||||
"""
|
||||
third_block_id = self.lib_page.xblocks[2].locator
|
||||
self.lib_page.click_duplicate_button(third_block_id)
|
||||
self.lib_page.wait_until_ready()
|
||||
target = self.lib_page.xblocks[3]
|
||||
self.assertIn('Duplicate', target.name)
|
||||
self.assertFalse(target.is_placeholder())
|
||||
self.assertEqual(len(self.lib_page.xblocks), 10)
|
||||
|
||||
def test_duplicate_xblock_pagination_end(self):
|
||||
"""
|
||||
Scenario: Duplicating an XBlock if it's the last one should bring me to the next page with a preview.
|
||||
Given that I have a library in Studio with 40 XBlocks
|
||||
And when I hide previews
|
||||
And I duplicate the last XBlock
|
||||
The page should change to page 2
|
||||
And the duplicate XBlock should be the first XBlock
|
||||
And it should not be a placeholder
|
||||
"""
|
||||
self.lib_page.toggle_previews()
|
||||
last_block_id = self.lib_page.xblocks[-1].locator
|
||||
self.lib_page.click_duplicate_button(last_block_id)
|
||||
self.lib_page.wait_until_ready()
|
||||
self.assertEqual(self.lib_page.get_page_number(), '2')
|
||||
target_block = self.lib_page.xblocks[0]
|
||||
self.assertIn('Duplicate', target_block.name)
|
||||
self.assertFalse(target_block.is_placeholder())
|
||||
|
||||
|
||||
class LibraryUsersPageTest(StudioLibraryTest):
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
<div class="xblock-container-paging-parameters" data-start="${first_displayed}" data-displayed="${displayed_children}" data-total="${total_children}"></div>
|
||||
<div class="xblock-container-paging-parameters"
|
||||
data-start="${first_displayed}"
|
||||
data-displayed="${displayed_children}"
|
||||
data-total="${total_children}"
|
||||
data-previews="${'true' if previews else 'false'}"
|
||||
></div>
|
||||
|
||||
<div class="container-paging-header"></div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user