paged_container.js 12.2 KB
Newer Older
1 2
define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/views/container", "js/utils/module", "gettext",
        "common/js/components/views/feedback_notification", "js/views/paging_header", "common/js/components/views/paging_footer"],
3 4
    function ($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter) {
        var PagedContainerView = ContainerView.extend({
5 6

            initialize: function(options) {
7 8
                var self = this;
                ContainerView.prototype.initialize.call(this);
9 10 11 12 13 14 15
                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.
16 17 18 19 20 21 22
                this.collection = {
                    currentPage: 0,
                    totalPages: 0,
                    totalCount: 0,
                    sortDirection: "desc",
                    start: 0,
                    _size: 0,
23 24 25 26 27
                    // 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.
28 29
                    size: function() { return self.collection._size; },
                    // Toggles the functionality for showing and hiding child previews.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
                    showChildrenPreviews: true,

                    // PagingFooter expects to be able to control paging through the collection instead of the view,
                    // so we just make these functions act as pass-throughs
                    setPage: function (page) {
                        self.setPage(page - 1);
                    },

                    nextPage: function () {
                        self.nextPage();
                    },

                    previousPage: function() {
                        self.previousPage();
                    },

                    getPage: function () {
                        return self.collection.currentPage + 1;
                    },

                    hasPreviousPage: function () {
                        return self.collection.currentPage > 0;
                    },

                    hasNextPage: function () {
                        return self.collection.currentPage < self.collection.totalPages - 1;
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
                    },

                    getTotalPages: function () {
                        return this.totalPages;
                    },

                    getPageNumber: function () {
                        return this.getPage();
                    },

                    getTotalRecords: function () {
                        return this.totalCount;
                    },

                    getPageSize: function () {
                        return self.page_size;
72
                    }
73 74 75
                };
            },

76 77
            new_child_view: 'container_child_preview',

78
            render: function(options) {
79 80 81
                options = options || {};
                options.page_number = typeof options.page_number !== "undefined"
                    ? options.page_number
82
                    : this.collection.currentPage;
83
                return this.renderPage(options);
84 85
            },

86
            renderPage: function(options) {
87 88 89 90
                var self = this,
                    view = this.view,
                    xblockInfo = this.model,
                    xblockUrl = xblockInfo.url();
91

92 93 94 95
                return $.ajax({
                    url: decodeURIComponent(xblockUrl) + "/" + view,
                    type: 'GET',
                    cache: false,
96
                    data: this.getRenderParameters(options.page_number, options.force_render),
97 98 99 100
                    headers: { Accept: 'application/json' },
                    success: function(fragment) {
                        self.handleXBlockFragment(fragment, options);
                        self.processPaging({ requested_page: options.page_number });
101
                        self.page.updatePreviewButton(self.collection.showChildrenPreviews);
102
                        self.page.renderAddXBlockComponents();
103 104 105 106 107
                        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));
                        }
108 109 110 111
                    }
                });
            },

112 113
            getRenderParameters: function(page_number, force_render) {
                // Options should at least contain page_number.
114 115
                return {
                    page_size: this.page_size,
116
                    enable_paging: true,
117 118
                    page_number: page_number,
                    force_render: force_render
119 120 121
                };
            },

122 123 124 125
            getPageCount: function(total_count) {
                if (total_count === 0) {
                    return 1;
                }
126 127 128
                return Math.ceil(total_count / this.page_size);
            },

129 130 131 132
            setPage: function(page_number, additional_options) {
                additional_options = additional_options || {};
                var options = _.extend({page_number: page_number}, additional_options);
                this.render(options);
133 134
            },

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
            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);
                }
            },

152
            processPaging: function(options){
153 154
                // We have the Django template sneak us the pagination information,
                // and we load it from a div here.
155 156 157
                var $element = this.$el.find('.xblock-container-paging-parameters'),
                    total = $element.data('total'),
                    displayed = $element.data('displayed'),
158 159
                    start = $element.data('start'),
                    previews = $element.data('previews');
160 161 162 163 164 165

                this.collection.currentPage = options.requested_page;
                this.collection.totalCount = total;
                this.collection.totalPages = this.getPageCount(total);
                this.collection.start = start;
                this.collection._size = displayed;
166
                this.collection.showChildrenPreviews = previews;
167 168 169 170 171

                this.processPagingHeaderAndFooter();
            },

            processPagingHeaderAndFooter: function(){
172 173
                // 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.
174 175 176 177 178 179 180 181 182 183
                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({
184
                    collection: this.collection,
185 186 187 188 189 190 191
                    el: this.$el.find('.container-paging-footer')
                });

                this.pagingHeader.render();
                this.pagingFooter.render();
            },

192 193 194 195 196 197 198 199 200 201 202 203
            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();
204 205
                        return
                    }
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
                }
                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;
221
                    }
222 223 224 225 226 227 228 229
                    this.setPage(
                        target_page,
                        {force_render: force_render}
                    );

                } else {
                    this.pagingHeader.render();
                    this.pagingFooter.render();
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
                }
            },

            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() {
257
                return gettext("Date added");  // TODO add support for sorting
258 259 260 261 262 263 264 265 266 267 268 269 270
            },

            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();
271 272 273 274 275
            }
        });

        return PagedContainerView;
    }); // end define();