TNL-2384 Refactored Studio's PagingView to use RequireJS Text and moved it to common so that it can also be used by LMS.
214 lines
10 KiB
JavaScript
214 lines
10 KiB
JavaScript
define(["jquery", "underscore", "js/views/utils/view_utils", "js/views/container", "js/utils/module", "gettext",
|
|
"js/views/feedback_notification", "common/js/components/views/paging_header",
|
|
"common/js/components/views/paging_footer", "common/js/components/views/paging_mixin"],
|
|
function ($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter, PagingMixin) {
|
|
var PagedContainerView = ContainerView.extend(PagingMixin).extend({
|
|
initialize: function(options){
|
|
var self = this;
|
|
ContainerView.prototype.initialize.call(this);
|
|
this.page_size = this.options.page_size;
|
|
// Reference to the page model
|
|
this.page = options.page;
|
|
// XBlocks are rendered via Django views and templates rather than underscore templates, and so don't
|
|
// have a Backbone model for us to manipulate in a backbone collection. Here, we emulate the interface
|
|
// of backbone.paginator so that we can use the Paging Header and Footer with this page. As a
|
|
// consequence, however, we have to manipulate its members manually.
|
|
this.collection = {
|
|
currentPage: 0,
|
|
totalPages: 0,
|
|
totalCount: 0,
|
|
sortDirection: "desc",
|
|
start: 0,
|
|
_size: 0,
|
|
// Paging header and footer expect this to be a Backbone model they can listen to for changes, but
|
|
// they cannot. Provide the bind function for them, but have it do nothing.
|
|
bind: function() {},
|
|
// size() on backbone collections shows how many objects are in the collection, or in the case
|
|
// of paginator, on the current page.
|
|
size: function() { return self.collection._size; },
|
|
// Toggles the functionality for showing and hiding child previews.
|
|
showChildrenPreviews: true
|
|
};
|
|
},
|
|
|
|
new_child_view: 'container_child_preview',
|
|
|
|
render: function(options) {
|
|
options = options || {};
|
|
options.page_number = typeof options.page_number !== "undefined"
|
|
? options.page_number
|
|
: this.collection.currentPage;
|
|
return this.renderPage(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, 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, force_render) {
|
|
// Options should at least contain page_number.
|
|
return {
|
|
page_size: this.page_size,
|
|
enable_paging: true,
|
|
page_number: page_number,
|
|
force_render: force_render
|
|
};
|
|
},
|
|
|
|
getPageCount: function(total_count){
|
|
if (total_count===0) return 1;
|
|
return Math.ceil(total_count / this.page_size);
|
|
},
|
|
|
|
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){
|
|
// We have the Django template sneak us the pagination information,
|
|
// and we load it from a div here.
|
|
var $element = this.$el.find('.xblock-container-paging-parameters'),
|
|
total = $element.data('total'),
|
|
displayed = $element.data('displayed'),
|
|
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();
|
|
},
|
|
|
|
processPagingHeaderAndFooter: function(){
|
|
// Rendering the container view detaches the header and footer from the DOM.
|
|
// It's just as easy to recreate them as it is to try to shove them back into the tree.
|
|
if (this.pagingHeader)
|
|
this.pagingHeader.undelegateEvents();
|
|
if (this.pagingFooter)
|
|
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();
|
|
},
|
|
|
|
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.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();
|
|
}
|
|
},
|
|
|
|
acknowledgeXBlockDeletion: function (locator){
|
|
this.notifyRuntime('deleted-child', locator);
|
|
this.collection._size -= 1;
|
|
this.collection.totalCount -= 1;
|
|
var current_page = this.collection.currentPage;
|
|
var total_pages = this.getPageCount(this.collection.totalCount);
|
|
this.collection.totalPages = total_pages;
|
|
// Starts counting from 0
|
|
if ((current_page + 1) > total_pages) {
|
|
// The number of total pages has changed. Move down.
|
|
// Also, be mindful of the off-by-one.
|
|
this.setPage(total_pages - 1)
|
|
} else if ((current_page + 1) != total_pages) {
|
|
// Refresh page to get any blocks shifted from the next page.
|
|
this.setPage(current_page)
|
|
} else {
|
|
// We're on the last page, just need to update the numbers in the
|
|
// pagination interface.
|
|
this.pagingHeader.render();
|
|
this.pagingFooter.render();
|
|
}
|
|
},
|
|
|
|
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();
|
|
}
|
|
});
|
|
|
|
return PagedContainerView;
|
|
}); // end define();
|