Commit b31ba9d0 by Ehtesham

upgrade Pagingcollection to edx-ui-toolkit's PagingCollection

parent f6d9c9a3
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
"backbone": "common/js/vendor/backbone", "backbone": "common/js/vendor/backbone",
"backbone-relational" : "js/vendor/backbone-relational.min", "backbone-relational" : "js/vendor/backbone-relational.min",
"backbone.associations": "js/vendor/backbone-associations-min", "backbone.associations": "js/vendor/backbone-associations-min",
"backbone.paginator": "js/vendor/backbone.paginator.min", "backbone.paginator": "common/js/vendor/backbone.paginator",
"tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min", "tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
"jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min", "jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
"xmodule": "/xmodule/xmodule", "xmodule": "/xmodule/xmodule",
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
}, },
"backbone.paginator": { "backbone.paginator": {
deps: ["backbone"], deps: ["backbone"],
exports: "Backbone.Paginator" exports: "Backbone.PageableCollection"
}, },
"youtube": { "youtube": {
exports: "YT" exports: "YT"
......
...@@ -32,7 +32,7 @@ requirejs.config({ ...@@ -32,7 +32,7 @@ requirejs.config({
"underscore.string": "common/js/vendor/underscore.string", "underscore.string": "common/js/vendor/underscore.string",
"backbone": "common/js/vendor/backbone", "backbone": "common/js/vendor/backbone",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min", "backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min", "backbone.paginator": "common/js/vendor/backbone.paginator",
"backbone-relational": "xmodule_js/common_static/js/vendor/backbone-relational.min", "backbone-relational": "xmodule_js/common_static/js/vendor/backbone-relational.min",
"tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min", "tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce", "jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce",
...@@ -141,7 +141,7 @@ requirejs.config({ ...@@ -141,7 +141,7 @@ requirejs.config({
}, },
"backbone.paginator": { "backbone.paginator": {
deps: ["backbone"], deps: ["backbone"],
exports: "Backbone.Paginator" exports: "Backbone.PageableCollection"
}, },
"backbone-relational": { "backbone-relational": {
deps: ["backbone"], deps: ["backbone"],
......
...@@ -29,7 +29,7 @@ requirejs.config({ ...@@ -29,7 +29,7 @@ requirejs.config({
"underscore.string": "common/js/vendor/underscore.string", "underscore.string": "common/js/vendor/underscore.string",
"backbone": "common/js/vendor/backbone", "backbone": "common/js/vendor/backbone",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min", "backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min", "backbone.paginator": "common/js/vendor/backbone.paginator",
"tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min", "tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce", "jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce",
"xmodule": "xmodule_js/src/xmodule", "xmodule": "xmodule_js/src/xmodule",
...@@ -124,7 +124,7 @@ requirejs.config({ ...@@ -124,7 +124,7 @@ requirejs.config({
}, },
"backbone.paginator": { "backbone.paginator": {
deps: ["backbone"], deps: ["backbone"],
exports: "Backbone.Paginator" exports: "Backbone.PageableCollection"
}, },
"youtube": { "youtube": {
exports: "YT" exports: "YT"
......
...@@ -230,7 +230,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"], ...@@ -230,7 +230,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"],
describe "Basic", -> describe "Basic", ->
# Separate setup method to work-around mis-parenting of beforeEach methods # Separate setup method to work-around mis-parenting of beforeEach methods
setup = (requests) -> setup = (requests) ->
@view.pagingView.setPage(0) @view.pagingView.setPage(1)
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse) AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
$.fn.fileupload = -> $.fn.fileupload = ->
...@@ -274,7 +274,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"], ...@@ -274,7 +274,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"],
{view: @view, requests: requests} = @createAssetsView(this) {view: @view, requests: requests} = @createAssetsView(this)
appendSetFixtures('<div class="ui-loading"/>') appendSetFixtures('<div class="ui-loading"/>')
expect($('.ui-loading').is(':visible')).toBe(true) expect($('.ui-loading').is(':visible')).toBe(true)
@view.pagingView.setPage(0) @view.pagingView.setPage(1)
AjaxHelpers.respondWithError(requests) AjaxHelpers.respondWithError(requests)
expect($('.ui-loading').is(':visible')).toBe(false) expect($('.ui-loading').is(':visible')).toBe(false)
...@@ -323,7 +323,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"], ...@@ -323,7 +323,7 @@ define ["jquery", "common/js/spec_helpers/ajax_helpers", "squire"],
describe "Sorting", -> describe "Sorting", ->
# Separate setup method to work-around mis-parenting of beforeEach methods # Separate setup method to work-around mis-parenting of beforeEach methods
setup = (requests) -> setup = (requests) ->
@view.pagingView.setPage(0) @view.pagingView.setPage(1)
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse) AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
it "should have the correct default sort order", -> it "should have the correct default sort order", ->
......
define(["backbone.paginator", "js/models/asset"], function(BackbonePaginator, AssetModel) { define([
var AssetCollection = BackbonePaginator.requestPager.extend({ "underscore",
"edx-ui-toolkit/js/pagination/paging-collection",
"js/models/asset"
], function(_, PagingCollection, AssetModel) {
'use strict';
var AssetCollection = PagingCollection.extend({
assetType: '', assetType: '',
model : AssetModel, model : AssetModel,
paginator_core: {
type: 'GET',
accepts: 'application/json',
dataType: 'json',
url: function() { return this.url; }
},
paginator_ui: {
firstPage: 0,
currentPage: 0,
perPage: 50
},
server_api: {
'page': function() { return this.currentPage; },
'page_size': function() { return this.perPage; },
'sort': function() { return this.sortField; },
'direction': function() { return this.sortDirection; },
'asset_type': function() { return this.assetType; },
'format': 'json'
},
parse: function(response) { state: {
var totalCount = response.totalCount, firstPage: 0,
start = response.start, pageSize: 50,
currentPage = response.page, sortKey: 'sort',
pageSize = response.pageSize, order: null,
totalPages = Math.ceil(totalCount / pageSize); currentPage: null,
this.totalCount = totalCount; totalRecords: null,
this.totalPages = Math.max(totalPages, 1); // Treat an empty collection as having 1 page... totalCount: null
this.currentPage = currentPage;
this.start = start;
return response.assets;
},
setPage: function (page) {
var oldPage = this.currentPage,
self = this;
this.goTo(page - 1, {
reset: true,
success: function () {
self.trigger('page_changed');
},
error: function () {
self.currentPage = oldPage;
}
});
},
nextPage: function () {
if (this.currentPage < this.totalPages - 1) {
this.setPage(this.getPage() + 1);
}
}, },
previousPage: function () { queryParams: {
if (this.currentPage > 0) { currentPage: 'page',
this.setPage(this.getPage() - 1); pageSize: 'page_size',
} sortKey: 'sort',
order: 'direction',
directions: {
asc: 'asc',
desc: 'desc'
}, },
asset_type: function() { return this.assetType; }
getPage: function () {
return this.currentPage + 1;
}, },
hasPreviousPage: function () { parse: function(response, options) {
return this.currentPage > 0; response.results = response.assets;
delete response.assets;
return PagingCollection.prototype.parse.call(this, response, options);
}, },
hasNextPage: function () { /* jshint unused:false */
return this.currentPage < this.totalPages - 1; parseState: function (response, queryParams, state, options) {
return {
totalRecords: response[0].totalCount,
totalPages: Math.ceil(response[0].totalCount / response[0].pageSize)
};
} }
}); });
return AssetCollection; return AssetCollection;
}); });
...@@ -18,7 +18,13 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -18,7 +18,13 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
spyOn($.fn, "fileupload").and.returnValue(""); spyOn($.fn, "fileupload").and.returnValue("");
var collection = new AssetCollection(); var TestAssetsCollection = AssetCollection.extend({
state: {
firstPage: 0,
pageSize: 2
}
});
var collection = new TestAssetsCollection();
collection.url = "assets-url"; collection.url = "assets-url";
assetsView = new AssetsView({ assetsView = new AssetsView({
collection: collection, collection: collection,
...@@ -43,7 +49,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -43,7 +49,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
start: 0, start: 0,
end: 0, end: 0,
page: 0, page: 0,
pageSize: 5, pageSize: 2,
totalCount: 0 totalCount: 0
}; };
...@@ -134,11 +140,10 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -134,11 +140,10 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
var setup; var setup;
setup = function(responseData) { setup = function(responseData) {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
if (!responseData){ if (!responseData){
AjaxHelpers.respondWithJson(requests, mockEmptyAssetsResponse); AjaxHelpers.respondWithJson(requests, mockEmptyAssetsResponse);
} } else {
else{
AjaxHelpers.respondWithJson(requests, responseData); AjaxHelpers.respondWithJson(requests, responseData);
} }
return requests; return requests;
...@@ -184,7 +189,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -184,7 +189,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
spyOn(assetsView, "addAsset").and.callFake(function () { spyOn(assetsView, "addAsset").and.callFake(function () {
assetsView.collection.add(mockAssetUploadResponse.asset); assetsView.collection.add(mockAssetUploadResponse.asset);
assetsView.pagingView.renderPageItems(); assetsView.pagingView.renderPageItems();
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
}); });
$('a:contains("Upload your first asset")').click(); $('a:contains("Upload your first asset")').click();
...@@ -269,7 +274,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -269,7 +274,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
$.each(assetsView.pagingView.filterableColumns, function(columnID, columnData){ $.each(assetsView.pagingView.filterableColumns, function(columnID, columnData){
var $typeColumn = $('#' + columnID); var $typeColumn = $('#' + columnID);
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
respondWithMockAssets(requests); respondWithMockAssets(requests);
var assetsNumber = assetsView.collection.length; var assetsNumber = assetsView.collection.length;
assetsView._toggleFilterColumn('Images', 'Images'); assetsView._toggleFilterColumn('Images', 'Images');
...@@ -302,7 +307,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -302,7 +307,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
assetsView.pagingView.registerSortableColumn('name-col', 'Name Column', 'nameField', 'asc'); assetsView.pagingView.registerSortableColumn('name-col', 'Name Column', 'nameField', 'asc');
assetsView.pagingView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type'); assetsView.pagingView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
assetsView.pagingView.setInitialSortColumn('name-col'); assetsView.pagingView.setInitialSortColumn('name-col');
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
respondWithMockAssets(requests); respondWithMockAssets(requests);
var sortInfo = assetsView.pagingView.sortableColumnInfo('name-col'); var sortInfo = assetsView.pagingView.sortableColumnInfo('name-col');
expect(sortInfo.defaultSortDirection).toBe('asc'); expect(sortInfo.defaultSortDirection).toBe('asc');
...@@ -318,7 +323,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -318,7 +323,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
expect(assetsView).toBeDefined(); expect(assetsView).toBeDefined();
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
$.each(assetsView.pagingView.filterableColumns, function(columnID, columnData) { $.each(assetsView.pagingView.filterableColumns, function(columnID, columnData) {
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
respondWithMockAssets(requests); respondWithMockAssets(requests);
var $typeColumn = $('#' + columnID); var $typeColumn = $('#' + columnID);
expect($typeColumn).toBeVisible(); expect($typeColumn).toBeVisible();
...@@ -410,7 +415,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -410,7 +415,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
it('can move forward a page using the next page button', function () { it('can move forward a page using the next page button', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
AjaxHelpers.respondWithJson(requests, firstPageAssets); AjaxHelpers.respondWithJson(requests, firstPageAssets);
expect(assetsView.pagingView.pagingFooter).toBeDefined(); expect(assetsView.pagingView.pagingFooter).toBeDefined();
expect(assetsView.pagingView.pagingFooter.$('button.next-page-link')) expect(assetsView.pagingView.pagingFooter.$('button.next-page-link'))
...@@ -423,7 +428,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -423,7 +428,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
it('can move back a page using the previous page button', function () { it('can move back a page using the previous page button', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
assetsView.pagingView.setPage(1); assetsView.pagingView.setPage(2);
AjaxHelpers.respondWithJson(requests, secondPageAssets); AjaxHelpers.respondWithJson(requests, secondPageAssets);
expect(assetsView.pagingView.pagingFooter).toBeDefined(); expect(assetsView.pagingView.pagingFooter).toBeDefined();
expect(assetsView.pagingView.pagingFooter.$('button.previous-page-link')) expect(assetsView.pagingView.pagingFooter.$('button.previous-page-link'))
...@@ -436,12 +441,12 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset ...@@ -436,12 +441,12 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/asset
it('can set the current page using the page number input', function () { it('can set the current page using the page number input', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
assetsView.pagingView.setPage(0); assetsView.pagingView.setPage(1);
AjaxHelpers.respondWithJson(requests, firstPageAssets); AjaxHelpers.respondWithJson(requests, firstPageAssets);
assetsView.pagingView.pagingFooter.$('#page-number-input').val('2'); assetsView.pagingView.pagingFooter.$('#page-number-input').val('2');
assetsView.pagingView.pagingFooter.$('#page-number-input').trigger('change'); assetsView.pagingView.pagingFooter.$('#page-number-input').trigger('change');
AjaxHelpers.respondWithJson(requests, secondPageAssets); AjaxHelpers.respondWithJson(requests, secondPageAssets);
expect(assetsView.collection.currentPage).toBe(1); expect(assetsView.collection.getPageNumber()).toBe(2);
expect(assetsView.pagingView.pagingFooter.$('button.previous-page-link')) expect(assetsView.pagingView.pagingFooter.$('button.previous-page-link'))
.not.toHaveClass('is-disabled'); .not.toHaveClass('is-disabled');
}); });
......
...@@ -81,7 +81,7 @@ define(["jquery", "underscore", "common/js/spec_helpers/ajax_helpers", "URI", "j ...@@ -81,7 +81,7 @@ define(["jquery", "underscore", "common/js/spec_helpers/ajax_helpers", "URI", "j
}); });
describe("Container", function () { describe("Container", function () {
describe("rendering", function(){ describe("rendering", function() {
it('should set show_previews', function() { it('should set show_previews', function() {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
......
define([ define([
"jquery", "jquery",
"common/js/spec_helpers/ajax_helpers",
"URI", "URI",
"common/js/spec_helpers/ajax_helpers",
"edx-ui-toolkit/js/pagination/paging-collection",
"js/views/paging", "js/views/paging",
"js/views/paging_header", "js/views/paging_header"
"common/js/components/collections/paging_collection" ], function ($, URI, AjaxHelpers, PagingCollection, PagingView, PagingHeader) {
], function ($, AjaxHelpers, URI, PagingView, PagingHeader, PagingCollection) {
var createPageableItem = function(index) { var createPageableItem = function(index) {
var id = 'item_' + index; var id = 'item_' + index;
...@@ -24,7 +24,6 @@ define([ ...@@ -24,7 +24,6 @@ define([
], ],
num_pages: 2, num_pages: 2,
page_size: 3, page_size: 3,
current_page: 0,
count: 4, count: 4,
page: 0, page: 0,
start: 0 start: 0
...@@ -35,16 +34,14 @@ define([ ...@@ -35,16 +34,14 @@ define([
], ],
num_pages: 2, num_pages: 2,
page_size: 3, page_size: 3,
current_page: 1,
count: 4,
page: 1, page: 1,
count: 4,
start: 3 start: 3
}; };
var mockEmptyPage = { var mockEmptyPage = {
results: [], results: [],
num_pages: 1, num_pages: 1,
page_size: 3, page_size: 3,
current_page: 0,
count: 0, count: 0,
page: 0, page: 0,
start: 0 start: 0
...@@ -69,11 +66,18 @@ define([ ...@@ -69,11 +66,18 @@ define([
}); });
describe("Paging", function() { describe("Paging", function() {
var pagingView; var pagingView,
TestPagingCollection = PagingCollection.extend({
state: {
firstPage: 0,
currentPage: null,
pageSize: 3
}
});
beforeEach(function () { beforeEach(function () {
var collection = new PagingCollection(); var collection = new TestPagingCollection();
collection.isZeroIndexed = true; collection.url = '/dummy/';
pagingView = new MockPagingView({collection: collection}); pagingView = new MockPagingView({collection: collection});
}); });
...@@ -83,10 +87,10 @@ define([ ...@@ -83,10 +87,10 @@ define([
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
pagingView.setPage(1); pagingView.setPage(1);
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(0); expect(pagingView.collection.getPageNumber()).toBe(1);
pagingView.setPage(2); pagingView.setPage(2);
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(1); expect(pagingView.collection.getPageNumber()).toBe(2);
}); });
it('should not change page after a server error', function () { it('should not change page after a server error', function () {
...@@ -95,7 +99,9 @@ define([ ...@@ -95,7 +99,9 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingView.setPage(2); pagingView.setPage(2);
requests[1].respond(500); requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
/* PagingCollection sets the currentPage to the old page in case of failure */
expect(pagingView.collection.getPageNumber()).toBe(1);
}); });
}); });
...@@ -106,7 +112,7 @@ define([ ...@@ -106,7 +112,7 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingView.nextPage(); pagingView.nextPage();
requests[1].respond(500); requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0); expect(pagingView.collection.getPageNumber()).toBe(1);
}); });
it('can move to the next page', function () { it('can move to the next page', function () {
...@@ -115,7 +121,9 @@ define([ ...@@ -115,7 +121,9 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingView.nextPage(); pagingView.nextPage();
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(1);
/* PagingCollection now returns the normalized page number; adds one if zero indexed */
expect(pagingView.collection.getPageNumber()).toBe(2);
}); });
it('can not move forward from the final page', function () { it('can not move forward from the final page', function () {
...@@ -128,14 +136,13 @@ define([ ...@@ -128,14 +136,13 @@ define([
}); });
describe("previousPage", function () { describe("previousPage", function () {
it('can move back a page', function () { it('can move back a page', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
pagingView.setPage(2); pagingView.setPage(2);
respondWithMockItems(requests); respondWithMockItems(requests);
pagingView.previousPage(); pagingView.previousPage();
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(0); expect(pagingView.collection.getPageNumber()).toBe(1);
}); });
it('can not move back from the first page', function () { it('can not move back from the first page', function () {
...@@ -152,12 +159,11 @@ define([ ...@@ -152,12 +159,11 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingView.previousPage(); pagingView.previousPage();
requests[1].respond(500); requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(1); expect(pagingView.collection.getPageNumber()).toBe(2);
}); });
}); });
describe("toggleSortOrder", function () { describe("toggleSortOrder", function () {
it('can toggle direction of the current sort', function () { it('can toggle direction of the current sort', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
expect(pagingView.collection.sortDirection).toBe('desc'); expect(pagingView.collection.sortDirection).toBe('desc');
...@@ -220,7 +226,8 @@ define([ ...@@ -220,7 +226,8 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingHeader.$('.next-page-link').click(); pagingHeader.$('.next-page-link').click();
requests[1].respond(500); requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0); expect(pagingView.collection.state.currentPage).toBe(0);
expect(pagingView.collection.getPageNumber()).toBe(1);
}); });
it('can move to the next page', function () { it('can move to the next page', function () {
...@@ -229,7 +236,7 @@ define([ ...@@ -229,7 +236,7 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingHeader.$('.next-page-link').click(); pagingHeader.$('.next-page-link').click();
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(1); expect(pagingView.collection.getPageNumber()).toBe(2);
}); });
it('should be enabled when there is at least one more page', function () { it('should be enabled when there is at least one more page', function () {
...@@ -248,7 +255,7 @@ define([ ...@@ -248,7 +255,7 @@ define([
it('should be disabled on an empty page', function () { it('should be disabled on an empty page', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
pagingView.setPage(0); pagingView.setPage(1);
AjaxHelpers.respondWithJson(requests, mockEmptyPage); AjaxHelpers.respondWithJson(requests, mockEmptyPage);
expect(pagingHeader.$('.next-page-link')).toHaveClass('is-disabled'); expect(pagingHeader.$('.next-page-link')).toHaveClass('is-disabled');
}); });
...@@ -267,7 +274,7 @@ define([ ...@@ -267,7 +274,7 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingHeader.$('.previous-page-link').click(); pagingHeader.$('.previous-page-link').click();
requests[1].respond(500); requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(1); expect(pagingView.collection.getPageNumber()).toBe(2);
}); });
it('can go back a page', function () { it('can go back a page', function () {
...@@ -276,7 +283,7 @@ define([ ...@@ -276,7 +283,7 @@ define([
respondWithMockItems(requests); respondWithMockItems(requests);
pagingHeader.$('.previous-page-link').click(); pagingHeader.$('.previous-page-link').click();
respondWithMockItems(requests); respondWithMockItems(requests);
expect(pagingView.collection.currentPage).toBe(0); expect(pagingView.collection.getPageNumber()).toBe(1);
}); });
it('should be disabled on the first page', function () { it('should be disabled on the first page', function () {
......
define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset", "js/views/paging", define([
"js/views/asset", "js/views/paging_header", "common/js/components/views/paging_footer", "jquery",
"js/utils/modal", "common/js/components/utils/view_utils", "common/js/components/views/feedback_notification", "underscore",
"gettext",
"edx-ui-toolkit/js/utils/html-utils",
"js/views/baseview",
"js/models/asset",
"js/views/paging",
"js/views/asset",
"js/views/paging_header",
"common/js/components/views/paging_footer",
"js/utils/modal",
"common/js/components/utils/view_utils",
"common/js/components/views/feedback_notification",
"text!templates/asset-library.underscore", "text!templates/asset-library.underscore",
"jquery.fileupload-process", "jquery.fileupload-validate"], "jquery.fileupload-process",
function($, _, gettext, BaseView, AssetModel, PagingView, AssetView, PagingHeader, PagingFooter, "jquery.fileupload-validate"
],
function($, _, gettext, HtmlUtils, BaseView, AssetModel, PagingView, AssetView, PagingHeader, PagingFooter,
ModalUtils, ViewUtils, NotificationView, asset_library_template) { ModalUtils, ViewUtils, NotificationView, asset_library_template) {
var CONVERSION_FACTOR_MBS_TO_BYTES = 1000 * 1000; var CONVERSION_FACTOR_MBS_TO_BYTES = 1000 * 1000;
...@@ -67,7 +80,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -67,7 +80,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
ViewUtils.hideLoadingIndicator(); ViewUtils.hideLoadingIndicator();
// Create the table // Create the table
this.$el.html(_.template(asset_library_template)({typeData: this.typeData})); HtmlUtils.setHtml(
this.$el,
HtmlUtils.template(asset_library_template)({typeData: this.typeData})
);
tableBody = this.$('#asset-table-body'); tableBody = this.$('#asset-table-body');
this.tableBody = tableBody; this.tableBody = tableBody;
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')}); this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
...@@ -97,7 +113,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -97,7 +113,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
pagingView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type'); pagingView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
pagingView.setInitialSortColumn('js-asset-date-col'); pagingView.setInitialSortColumn('js-asset-date-col');
pagingView.setInitialFilterColumn('js-asset-type-col'); pagingView.setInitialFilterColumn('js-asset-type-col');
pagingView.setPage(0); pagingView.setPage(1);
return pagingView; return pagingView;
}, },
...@@ -106,7 +122,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -106,7 +122,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
return this; return this;
}, },
afterRender: function(){ afterRender: function() {
// Bind events with html elements // Bind events with html elements
$('li a.upload-button').on('click', _.bind(this.showUploadModal, this)); $('li a.upload-button').on('click', _.bind(this.showUploadModal, this));
$('.upload-modal .close-button').on('click', _.bind(this.hideModal, this)); $('.upload-modal .close-button').on('click', _.bind(this.hideModal, this));
...@@ -127,7 +143,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -127,7 +143,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
// so that the new asset is shown at the top of the page. // so that the new asset is shown at the top of the page.
this.pagingView.setInitialSortColumn('js-asset-date-col'); this.pagingView.setInitialSortColumn('js-asset-date-col');
this.pagingView.setInitialFilterColumn('js-asset-type-col'); this.pagingView.setInitialFilterColumn('js-asset-type-col');
this.pagingView.setPage(0); this.pagingView.setPage(1);
analytics.track('Uploaded a File', { analytics.track('Uploaded a File', {
'course': course_location_analytics, 'course': course_location_analytics,
...@@ -217,9 +233,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -217,9 +233,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
startUpload: function (event) { startUpload: function (event) {
var file = event.target.value; var file = event.target.value;
if (!this.largeFileErrorMsg) { if (!this.largeFileErrorMsg) {
$('.upload-modal h1').text(gettext('Uploading')); $('.upload-modal h1').text(gettext('Uploading'));
$('.upload-modal .file-name').html(file.substring(file.lastIndexOf("\\") + 1)); $('.upload-modal .file-name').text(file.substring(file.lastIndexOf('\\') + 1));
$('.upload-modal .choose-file-button').hide(); $('.upload-modal .choose-file-button').hide();
$('.upload-modal .progress-bar').removeClass('loaded').show(); $('.upload-modal .progress-bar').removeClass('loaded').show();
} }
...@@ -228,13 +245,16 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -228,13 +245,16 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
resetUploadModal: function () { resetUploadModal: function () {
// Reset modal so it no longer displays information about previously // Reset modal so it no longer displays information about previously
// completed uploads. // completed uploads.
var percentVal = '0%'; var percentVal = '0%',
$('.upload-modal .progress-fill').width(percentVal); $progressFill = $('.upload-modal .progress-fill'),
$('.upload-modal .progress-fill').html(percentVal); $fileName = $('.upload-modal .file-name');
$progressFill.width(percentVal);
$progressFill.text(percentVal);
$('.upload-modal .progress-bar').hide(); $('.upload-modal .progress-bar').hide();
$('.upload-modal .file-name').show(); $fileName.show();
$('.upload-modal .file-name').html(''); $fileName.text('');
$('.upload-modal .choose-file-button').text(gettext('Choose File')); $('.upload-modal .choose-file-button').text(gettext('Choose File'));
$('.upload-modal .embeddable-xml-input').val(''); $('.upload-modal .embeddable-xml-input').val('');
$('.upload-modal .embeddable').hide(); $('.upload-modal .embeddable').hide();
...@@ -243,9 +263,11 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -243,9 +263,11 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
}, },
showUploadFeedback: function (event, percentComplete) { showUploadFeedback: function (event, percentComplete) {
var percentVal = percentComplete + '%'; var percentVal = percentComplete + '%',
$('.upload-modal .progress-fill').width(percentVal); $progressFill = $('.upload-modal .progress-fill');
$('.upload-modal .progress-fill').html(percentVal);
$progressFill.width(percentVal);
$progressFill.text(percentVal);
}, },
openFilterColumn: function($this) { openFilterColumn: function($this) {
...@@ -285,12 +307,12 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -285,12 +307,12 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
var filterColumn = this.$el.find('.filterable-column'); var filterColumn = this.$el.find('.filterable-column');
var resetFilter = filterColumn.find('.reset-filter'); var resetFilter = filterColumn.find('.reset-filter');
var title = filterColumn.find('.title'); var title = filterColumn.find('.title');
if(assettype === this.allLabel) {
if (assettype === this.allLabel) {
collection.assetType = ''; collection.assetType = '';
resetFilter.hide(); resetFilter.hide();
title.removeClass('column-selected-link'); title.removeClass('column-selected-link');
} } else {
else {
collection.assetType = assettype; collection.assetType = assettype;
resetFilter.show(); resetFilter.show();
title.addClass('column-selected-link'); title.addClass('column-selected-link');
...@@ -302,35 +324,38 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -302,35 +324,38 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
'.column-filter-link[data-assetfilter="' + assettype + '"]')); '.column-filter-link[data-assetfilter="' + assettype + '"]'));
}, },
closeFilterPopup: function(element){ closeFilterPopup: function(element) {
var $menu = element.parents('.nav-dd > .nav-item'); var $menu = element.parents('.nav-dd > .nav-item');
this.toggleFilterColumnState($menu, element); this.toggleFilterColumnState($menu, element);
}, },
displayFinishedUpload: function (resp) { displayFinishedUpload: function (resp) {
var asset = resp.asset; var asset = resp.asset,
$progressFill = $('.upload-modal .progress-fill');
$('.upload-modal h1').text(gettext('Upload New File')); $('.upload-modal h1').text(gettext('Upload New File'));
$('.upload-modal .embeddable-xml-input').val(asset.portable_url).show(); $('.upload-modal .embeddable-xml-input').val(asset.portable_url).show();
$('.upload-modal .embeddable').show(); $('.upload-modal .embeddable').show();
$('.upload-modal .file-name').hide(); $('.upload-modal .file-name').hide();
$('.upload-modal .progress-fill').html(resp.msg); $progressFill.text(resp.msg);
$('.upload-modal .choose-file-button').text(gettext('Load Another File')).show(); $('.upload-modal .choose-file-button').text(gettext('Load Another File')).show();
$('.upload-modal .progress-fill').width('100%'); $progressFill.width('100%');
this.addAsset(new AssetModel(asset)); this.addAsset(new AssetModel(asset));
}, },
displayFailedUpload: function (resp) { displayFailedUpload: function (resp) {
var $progressFill = $('.upload-modal .progress-fill');
$('.upload-modal h1').text(gettext('Upload New File')); $('.upload-modal h1').text(gettext('Upload New File'));
$('.upload-modal .embeddable-xml-input').hide(); $('.upload-modal .embeddable-xml-input').hide();
$('.upload-modal .embeddable').hide(); $('.upload-modal .embeddable').hide();
$('.upload-modal .file-name').hide(); $('.upload-modal .file-name').hide();
$('.upload-modal .progress-fill').html(resp.msg); $progressFill.text(resp.msg);
$('.upload-modal .choose-file-button').text(gettext('Load Another File')).show(); $('.upload-modal .choose-file-button').text(gettext('Load Another File')).show();
$('.upload-modal .progress-fill').width('0%'); $progressFill.width('0%');
} }
}); });
return AssetsView; return AssetsView;
}); // end define(); });
...@@ -2,7 +2,8 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie ...@@ -2,7 +2,8 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie
"common/js/components/views/feedback_notification", "js/views/paging_header", "common/js/components/views/paging_footer"], "common/js/components/views/feedback_notification", "js/views/paging_header", "common/js/components/views/paging_footer"],
function ($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter) { function ($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter) {
var PagedContainerView = ContainerView.extend({ var PagedContainerView = ContainerView.extend({
initialize: function(options){
initialize: function(options) {
var self = this; var self = this;
ContainerView.prototype.initialize.call(this); ContainerView.prototype.initialize.call(this);
this.page_size = this.options.page_size; this.page_size = this.options.page_size;
...@@ -52,6 +53,22 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie ...@@ -52,6 +53,22 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie
hasNextPage: function () { hasNextPage: function () {
return self.collection.currentPage < self.collection.totalPages - 1; return self.collection.currentPage < self.collection.totalPages - 1;
},
getTotalPages: function () {
return this.totalPages;
},
getPageNumber: function () {
return this.getPage();
},
getTotalRecords: function () {
return this.totalCount;
},
getPageSize: function () {
return self.page_size;
} }
}; };
}, },
...@@ -66,11 +83,12 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie ...@@ -66,11 +83,12 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie
return this.renderPage(options); return this.renderPage(options);
}, },
renderPage: function(options){ renderPage: function(options) {
var self = this, var self = this,
view = this.view, view = this.view,
xblockInfo = this.model, xblockInfo = this.model,
xblockUrl = xblockInfo.url(); xblockUrl = xblockInfo.url();
return $.ajax({ return $.ajax({
url: decodeURIComponent(xblockUrl) + "/" + view, url: decodeURIComponent(xblockUrl) + "/" + view,
type: 'GET', type: 'GET',
...@@ -101,8 +119,10 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie ...@@ -101,8 +119,10 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/vie
}; };
}, },
getPageCount: function(total_count){ getPageCount: function(total_count) {
if (total_count===0) return 1; if (total_count === 0) {
return 1;
}
return Math.ceil(total_count / this.page_size); return Math.ceil(total_count / this.page_size);
}, },
......
...@@ -116,12 +116,15 @@ ...@@ -116,12 +116,15 @@
sortInfo = this.sortableColumnInfo(sortColumn), sortInfo = this.sortableColumnInfo(sortColumn),
sortField = sortInfo.fieldName, sortField = sortInfo.fieldName,
defaultSortDirection = sortInfo.defaultSortDirection; defaultSortDirection = sortInfo.defaultSortDirection;
if (collection.sortField === sortField) { if (collection.sortField === sortField) {
collection.sortDirection = collection.sortDirection === 'asc' ? 'desc' : 'asc'; collection.sortDirection = collection.sortDirection === 'asc' ? 'desc' : 'asc';
} else { } else {
collection.sortField = sortField; collection.sortField = sortField;
collection.sortDirection = defaultSortDirection; collection.sortDirection = defaultSortDirection;
} }
collection.setSorting(sortField, collection.sortDirection);
this.sortColumn = sortColumn; this.sortColumn = sortColumn;
this.collection.setPage(1); this.collection.setPage(1);
}, },
...@@ -129,8 +132,8 @@ ...@@ -129,8 +132,8 @@
selectFilter: function(filterColumn) { selectFilter: function(filterColumn) {
var collection = this.collection, var collection = this.collection,
filterInfo = this.filterableColumnInfo(filterColumn), filterInfo = this.filterableColumnInfo(filterColumn),
filterField = filterInfo.fieldName, filterField = filterInfo.fieldName;
defaultFilterKey = false;
if (collection.filterField !== filterField) { if (collection.filterField !== filterField) {
collection.filterField = filterField; collection.filterField = filterField;
} }
......
define(["underscore", "backbone", "gettext", "text!templates/paging-header.underscore"], define([
function(_, Backbone, gettext, paging_header_template) { 'underscore',
'backbone',
'gettext',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'text!templates/paging-header.underscore'
], function(_, Backbone, gettext, HtmlUtils, StringUtils, pagingHeaderTemplate) {
'use strict';
/* jshint maxlen:false */
var PagingHeader = Backbone.View.extend({ var PagingHeader = Backbone.View.extend({
events : { events : {
"click .next-page-link": "nextPage", 'click .next-page-link': 'nextPage',
"click .previous-page-link": "previousPage" 'click .previous-page-link': 'previousPage'
}, },
initialize: function(options) { initialize: function(options) {
...@@ -19,83 +26,99 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under ...@@ -19,83 +26,99 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under
render: function() { render: function() {
var view = this.view, var view = this.view,
collection = view.collection, collection = view.collection,
currentPage = collection.currentPage, currentPage = collection.getPageNumber(),
lastPage = collection.totalPages - 1, lastPage = collection.getTotalPages(),
messageHtml = this.messageHtml(); messageHtml = this.messageHtml(),
this.$el.html(_.template(paging_header_template)({ messageHtml: messageHtml})); isNextDisabled = lastPage === 0 || currentPage === lastPage;
this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0).attr('aria-disabled', currentPage === 0);
this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage).attr('aria-disabled', currentPage === lastPage); HtmlUtils.setHtml(this.$el, HtmlUtils.template(pagingHeaderTemplate)({messageHtml: messageHtml}));
this.$('.previous-page-link')
.toggleClass('is-disabled', currentPage === 1)
.attr('aria-disabled', currentPage === 1);
this.$('.next-page-link')
.toggleClass('is-disabled', isNextDisabled)
.attr('aria-disabled', isNextDisabled);
return this; return this;
}, },
messageHtml: function() { messageHtml: function() {
var message = ''; var message = '',
var asset_type = false; assetType = false;
if (this.view.collection.assetType) { if (this.view.collection.assetType) {
if (this.view.collection.sortDirection === 'asc') { if (this.view.collection.sortDirection === 'asc') {
// Translators: sample result: // Translators: sample result:
// "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added ascending" // "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added ascending"
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, filtered by %(asset_type)s, sorted by %(sort_name)s ascending'); message = gettext('Showing {currentItemRange} out of {totalItemsCount}, filtered by {assetType}, sorted by {sortName} ascending');
} else { } else {
// Translators: sample result: // Translators: sample result:
// "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added descending" // "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added descending"
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, filtered by %(asset_type)s, sorted by %(sort_name)s descending'); message = gettext('Showing {currentItemRange} out of {totalItemsCount}, filtered by {assetType}, sorted by {sortName} descending');
}
asset_type = this.filterNameLabel();
} }
else { assetType = this.filterNameLabel();
} else {
if (this.view.collection.sortDirection === 'asc') { if (this.view.collection.sortDirection === 'asc') {
// Translators: sample result: // Translators: sample result:
// "Showing 0-9 out of 25 total, sorted by Date Added ascending" // "Showing 0-9 out of 25 total, sorted by Date Added ascending"
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, sorted by %(sort_name)s ascending'); message = gettext('Showing {currentItemRange} out of {totalItemsCount}, sorted by {sortName} ascending');
} else { } else {
// Translators: sample result: // Translators: sample result:
// "Showing 0-9 out of 25 total, sorted by Date Added descending" // "Showing 0-9 out of 25 total, sorted by Date Added descending"
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, sorted by %(sort_name)s descending'); message = gettext('Showing {currentItemRange} out of {totalItemsCount}, sorted by {sortName} descending');
} }
} }
return '<p>' + interpolate(message, { return HtmlUtils.interpolateHtml(message, {
current_item_range: this.currentItemRangeLabel(), currentItemRange: this.currentItemRangeLabel(),
total_items_count: this.totalItemsCountLabel(), totalItemsCount: this.totalItemsCountLabel(),
asset_type: asset_type, assetType: assetType,
sort_name: this.sortNameLabel() sortName: this.sortNameLabel()
}, true) + "</p>"; });
}, },
currentItemRangeLabel: function() { currentItemRangeLabel: function() {
var view = this.view, var view = this.view,
collection = view.collection, collection = view.collection,
start = collection.start, start = (collection.getPageNumber() - 1) * collection.getPageSize(),
count = collection.size(), count = collection.size(),
end = start + count; end = start + count,
return interpolate('<span class="count-current-shown">%(start)s-%(end)s</span>', { htmlMessage = HtmlUtils.HTML('<span class="count-current-shown">{start}-{end}</span>');
return HtmlUtils.interpolateHtml(htmlMessage, {
start: Math.min(start + 1, end), start: Math.min(start + 1, end),
end: end end: end
}, true); });
}, },
totalItemsCountLabel: function() { totalItemsCountLabel: function() {
var totalItemsLabel; var totalItemsLabel,
htmlMessage = HtmlUtils.HTML('<span class="count-total">{totalItemsLabel}</span>');
// Translators: turns into "25 total" to be used in other sentences, e.g. "Showing 0-9 out of 25 total". // Translators: turns into "25 total" to be used in other sentences, e.g. "Showing 0-9 out of 25 total".
totalItemsLabel = interpolate(gettext('%(total_items)s total'), { totalItemsLabel = StringUtils.interpolate(gettext('{totalItems} total'), {
total_items: this.view.collection.totalCount totalItems: this.view.collection.getTotalRecords()
}, true); });
return interpolate('<span class="count-total">%(total_items_label)s</span>', {
total_items_label: totalItemsLabel return HtmlUtils.interpolateHtml(htmlMessage, {
}, true); totalItemsLabel: totalItemsLabel
});
}, },
sortNameLabel: function() { sortNameLabel: function() {
return interpolate('<span class="sort-order">%(sort_name)s</span>', { var htmlMessage = HtmlUtils.HTML('<span class="sort-order">{sortName}</span>');
sort_name: this.view.sortDisplayName()
}, true); return HtmlUtils.interpolateHtml(htmlMessage, {
sortName: this.view.sortDisplayName()
});
}, },
filterNameLabel: function() { filterNameLabel: function() {
return interpolate('<span class="filter-column">%(filter_name)s</span>', { var htmlMessage = HtmlUtils.HTML('<span class="filter-column">{filterName}</span>');
filter_name: this.view.filterDisplayName()
}, true); return HtmlUtils.interpolateHtml(htmlMessage, {
filterName: this.view.filterDisplayName()
});
}, },
nextPage: function() { nextPage: function() {
...@@ -108,4 +131,4 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under ...@@ -108,4 +131,4 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under
}); });
return PagingHeader; return PagingHeader;
}); // end define(); });
<div class="meta-wrap"> <div class="meta-wrap">
<div class="meta"> <div class="meta">
<%= messageHtml %> <p><%= HtmlUtils.ensureHtml(messageHtml) %></p>
</div> </div>
<nav class="pagination pagination-compact top" aria-label="Compact Pagination"> <nav class="pagination pagination-compact top" aria-label="Compact Pagination">
<ol> <ol>
......
/**
* A generic paging collection for use with a ListView and PagingFooter.
*
* By default this collection is designed to work with Django Rest Framework APIs, but can be configured to work with
* others. There is support for ascending or descending sort on a particular field, as well as filtering on a field.
* While the backend API may use either zero or one indexed page numbers, this collection uniformly exposes a one
* indexed interface to make consumption easier for views.
*
* Subclasses may want to override the following properties:
* - url (string): The base url for the API endpoint.
* - isZeroIndexed (boolean): If true, API calls will use page numbers starting at zero. Defaults to false.
* - perPage (number): Count of elements to fetch for each page.
* - server_api (object): Query parameters for the API call. Subclasses may add entries as necessary. By default,
* a 'sort_order' field is included to specify the field to sort on. This field may be removed for subclasses
* that do not support sort ordering, or support it in a non-standard way. By default filterField and
* sortDirection do not affect the API calls. It is up to subclasses to add this information to the appropriate
* query string parameters in server_api.
*/
;(function (define) {
'use strict';
define(['backbone.paginator'], function (BackbonePaginator) {
var PagingCollection = BackbonePaginator.requestPager.extend({
initialize: function (models, options) {
options = options || {};
if (options.url) {
this.url = options.url;
}
var self = this;
// These must be initialized in the constructor because otherwise all PagingCollections would point
// to the same object references for sortableFields and filterableFields.
this.sortableFields = {};
this.filterableFields = {};
this.paginator_core = {
type: 'GET',
dataType: 'json',
url: function () { return this.url; }
};
this.paginator_ui = {
firstPage: function () { return self.isZeroIndexed ? 0 : 1; },
// Specifies the initial page during collection initialization
currentPage: self.isZeroIndexed ? 0 : 1,
perPage: function () { return self.perPage; }
};
this.currentPage = this.paginator_ui.currentPage;
this.server_api = {
page: function () { return self.currentPage; },
page_size: function () { return self.perPage; },
text_search: function () { return self.searchString ? self.searchString : ''; },
sort_order: function () { return self.sortField; }
};
},
isZeroIndexed: false,
perPage: 10,
isStale: false,
sortField: '',
sortDirection: 'descending',
sortableFields: {},
filterField: '',
filterableFields: {},
searchString: null,
parse: function (response) {
this.totalCount = response.count;
this.currentPage = response.current_page;
this.totalPages = response.num_pages;
this.start = response.start;
// Note: sort_order is not returned when performing a search
if (response.sort_order) {
this.sortField = response.sort_order;
}
return response.results;
},
/**
* Returns the current page number as if numbering starts on page one, regardless of the indexing of the
* underlying server API.
*/
getPage: function () {
// TODO: this.currentPage is currently returning a function sometimes when it is called.
// It is possible it always did this, but we either need to investigate more, or just wait until
// we replace this code with the pattern library.
return this.currentPage + (this.isZeroIndexed ? 1 : 0);
},
/**
* Sets the current page of the collection. Page is assumed to be one indexed, regardless of the indexing
* of the underlying server API. If there is an error fetching the page, the Backbone 'error' event is
* triggered and the page does not change. A 'page_changed' event is triggered on a successful page change.
* @param page one-indexed page to change to
*/
setPage: function (page) {
var oldPage = this.currentPage,
self = this,
deferred = $.Deferred();
this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then(
function () {
self.isStale = false;
self.trigger('page_changed');
deferred.resolve();
},
function () {
self.currentPage = oldPage;
deferred.fail();
}
);
return deferred.promise();
},
/**
* Refreshes the collection if it has been marked as stale.
* @returns {promise} Returns a promise representing the refresh.
*/
refresh: function() {
var deferred = $.Deferred();
if (this.isStale) {
this.setPage(1)
.done(function() {
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
},
/**
* Returns true if the collection has a next page, false otherwise.
*/
hasNextPage: function () {
return this.getPage() < this.totalPages;
},
/**
* Returns true if the collection has a previous page, false otherwise.
*/
hasPreviousPage: function () {
return this.getPage() > 1;
},
/**
* Moves the collection to the next page if it exists.
*/
nextPage: function () {
if (this.hasNextPage()) {
this.setPage(this.getPage() + 1);
}
},
/**
* Moves the collection to the previous page if it exists.
*/
previousPage: function () {
if (this.hasPreviousPage()) {
this.setPage(this.getPage() - 1);
}
},
/**
* Adds the given field to the list of fields that can be sorted on.
* @param fieldName name of the field for the server API
* @param displayName name of the field to display to the user
*/
registerSortableField: function (fieldName, displayName) {
this.addField(this.sortableFields, fieldName, displayName);
},
/**
* Adds the given field to the list of fields that can be filtered on.
* @param fieldName name of the field for the server API
* @param displayName name of the field to display to the user
*/
registerFilterableField: function (fieldName, displayName) {
this.addField(this.filterableFields, fieldName, displayName);
},
/**
* For internal use only. Adds the given field to the given collection of fields.
* @param fields object of existing fields
* @param fieldName name of the field for the server API
* @param displayName name of the field to display to the user
*/
addField: function (fields, fieldName, displayName) {
fields[fieldName] = {
displayName: displayName
};
},
/**
* Returns the display name of the field that the collection is currently sorted on.
*/
sortDisplayName: function () {
return this.sortableFields[this.sortField].displayName;
},
/**
* Returns the display name of the field that the collection is currently filtered on.
*/
filterDisplayName: function () {
return this.filterableFields[this.filterField].displayName;
},
/**
* Sets the field to sort on. Sends a request to the server to fetch the first page of the collection with
* the new sort order. If successful, the collection resets to page one with the new data.
* @param fieldName name of the field to sort on
* @param toggleDirection if true, the sort direction is toggled if the given field was already set
*/
setSortField: function (fieldName, toggleDirection) {
if (toggleDirection) {
if (this.sortField === fieldName) {
this.sortDirection = PagingCollection.SortDirection.flip(this.sortDirection);
} else {
this.sortDirection = PagingCollection.SortDirection.DESCENDING;
}
}
this.sortField = fieldName;
this.isStale = true;
},
/**
* Sets the direction of the sort. Sends a request to the server to fetch the first page of the collection
* with the new sort order. If successful, the collection resets to page one with the new data.
* @param direction either ASCENDING or DESCENDING from PagingCollection.SortDirection.
*/
setSortDirection: function (direction) {
this.sortDirection = direction;
this.isStale = true;
},
/**
* Sets the field to filter on. Sends a request to the server to fetch the first page of the collection
* with the new filter options. If successful, the collection resets to page one with the new data.
* @param fieldName name of the field to filter on
*/
setFilterField: function (fieldName) {
this.filterField = fieldName;
this.isStale = true;
},
/**
* Sets the string to use for a text search. If no string is specified then
* the search is cleared.
* @param searchString A string to search on, or null if no search is to be applied.
*/
setSearchString: function(searchString) {
if (searchString !== this.searchString) {
this.searchString = searchString;
this.isStale = true;
}
}
}, {
SortDirection: {
ASCENDING: 'ascending',
DESCENDING: 'descending',
flip: function (direction) {
return direction === this.ASCENDING ? this.DESCENDING : this.ASCENDING;
}
}
});
return PagingCollection;
});
}).call(this, define || RequireJS.define);
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
define([ define([
'backbone', 'backbone',
'underscore', 'underscore',
'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/paging_header', 'common/js/components/views/paging_header',
'common/js/components/views/paging_footer', 'common/js/components/views/paging_footer',
'common/js/components/views/list', 'common/js/components/views/list',
'text!common/templates/components/paginated-view.underscore' 'text!common/templates/components/paginated-view.underscore'
], function (Backbone, _, PagingHeader, PagingFooter, ListView, paginatedViewTemplate) { ], function (Backbone, _, HtmlUtils, PagingHeader, PagingFooter, ListView, paginatedViewTemplate) {
var PaginatedView = Backbone.View.extend({ var PaginatedView = Backbone.View.extend({
initialize: function () { initialize: function () {
var ItemListView = this.listViewClass.extend({ var ItemListView = this.listViewClass.extend({
...@@ -51,13 +52,14 @@ ...@@ -51,13 +52,14 @@
createFooterView: function() { createFooterView: function() {
return new PagingFooter({ return new PagingFooter({
collection: this.collection, hideWhenOnePage: true, collection: this.collection,
hideWhenOnePage: true,
paginationLabel: this.paginationLabel paginationLabel: this.paginationLabel
}); });
}, },
render: function () { render: function () {
this.$el.html(_.template(this.viewTemplate)({type: this.type})); HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.viewTemplate)({type: this.type}));
this.assign(this.listView, '.' + this.type + '-list'); this.assign(this.listView, '.' + this.type + '-list');
if (this.headerView) { if (this.headerView) {
this.assign(this.headerView, '.' + this.type + '-paging-header'); this.assign(this.headerView, '.' + this.type + '-paging-header');
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(["underscore", "gettext", "backbone", "text!common/templates/components/paging-footer.underscore"], define([
function(_, gettext, Backbone, paging_footer_template) { "underscore",
"gettext",
"backbone",
"edx-ui-toolkit/js/utils/html-utils",
"text!common/templates/components/paging-footer.underscore"
],
function(_, gettext, Backbone, HtmlUtils, pagingFooterTemplate) {
var PagingFooter = Backbone.View.extend({ var PagingFooter = Backbone.View.extend({
events : { events : {
...@@ -17,25 +23,27 @@ ...@@ -17,25 +23,27 @@
this.collection.bind('add', _.bind(this.render, this)); this.collection.bind('add', _.bind(this.render, this));
this.collection.bind('remove', _.bind(this.render, this)); this.collection.bind('remove', _.bind(this.render, this));
this.collection.bind('reset', _.bind(this.render, this)); this.collection.bind('reset', _.bind(this.render, this));
this.render();
}, },
render: function() { render: function() {
var onFirstPage = !this.collection.hasPreviousPage(), var onFirstPage = !this.collection.hasPreviousPage(),
onLastPage = !this.collection.hasNextPage(); onLastPage = !this.collection.hasNextPage();
if (this.hideWhenOnePage) { if (this.hideWhenOnePage) {
if (_.isUndefined(this.collection.totalPages) if (this.collection.getTotalPages() <= 1) {
|| this.collection.totalPages <= 1) {
this.$el.addClass('hidden'); this.$el.addClass('hidden');
} else if (this.$el.hasClass('hidden')) { } else if (this.$el.hasClass('hidden')) {
this.$el.removeClass('hidden'); this.$el.removeClass('hidden');
} }
} }
this.$el.html(_.template(paging_footer_template)({
current_page: this.collection.getPage(), HtmlUtils.setHtml(
total_pages: this.collection.totalPages, this.$el,
HtmlUtils.template(pagingFooterTemplate)({
current_page: this.collection.getPageNumber(),
total_pages: this.collection.getTotalPages(),
paginationLabel: this.paginationLabel paginationLabel: this.paginationLabel
})); })
);
this.$(".previous-page-link").toggleClass("is-disabled", onFirstPage).attr('aria-disabled', onFirstPage); this.$(".previous-page-link").toggleClass("is-disabled", onFirstPage).attr('aria-disabled', onFirstPage);
this.$(".next-page-link").toggleClass("is-disabled", onLastPage).attr('aria-disabled', onLastPage); this.$(".next-page-link").toggleClass("is-disabled", onLastPage).attr('aria-disabled', onLastPage);
return this; return this;
...@@ -43,11 +51,11 @@ ...@@ -43,11 +51,11 @@
changePage: function() { changePage: function() {
var collection = this.collection, var collection = this.collection,
currentPage = collection.getPage(), currentPage = collection.getPageNumber(),
pageInput = this.$("#page-number-input"), pageInput = this.$("#page-number-input"),
pageNumber = parseInt(pageInput.val(), 10), pageNumber = parseInt(pageInput.val(), 10),
validInput = true; validInput = true;
if (!pageNumber || pageNumber > collection.totalPages || pageNumber < 1) { if (!pageNumber || pageNumber > collection.getTotalPages() || pageNumber < 1) {
validInput = false; validInput = false;
} }
// If we still have a page number by this point, // If we still have a page number by this point,
......
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
'backbone', 'backbone',
'underscore', 'underscore',
'gettext', 'gettext',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'text!common/templates/components/paging-header.underscore' 'text!common/templates/components/paging-header.underscore'
], function (Backbone, _, gettext, headerTemplate) { ], function (Backbone, _, gettext, HtmlUtils, StringUtils, headerTemplate) {
var PagingHeader = Backbone.View.extend({ var PagingHeader = Backbone.View.extend({
initialize: function (options) { initialize: function (options) {
this.srInfo = options.srInfo; this.srInfo = options.srInfo;
...@@ -21,25 +23,37 @@ ...@@ -21,25 +23,37 @@
render: function () { render: function () {
var message, var message,
start = _.isUndefined(this.collection.start) ? 0 : this.collection.start, start = (this.collection.getPageNumber() - 1) * this.collection.getPageSize(),
end = start + this.collection.length, end = start + this.collection.size(),
num_items = _.isUndefined(this.collection.totalCount) ? 0 : this.collection.totalCount, numItems = this.collection.getTotalRecords(),
context = {first_index: Math.min(start + 1, end), last_index: end, num_items: num_items}; context = {
firstIndex: Math.min(start + 1, end),
lastIndex: end,
numItems: numItems
};
if (end <= 1) { if (end <= 1) {
message = interpolate(gettext('Showing %(first_index)s out of %(num_items)s total'), context, true); message = StringUtils.interpolate(
gettext('Showing {firstIndex} out of {numItems} total'),
context
);
} else { } else {
message = interpolate( message = StringUtils.interpolate(
gettext('Showing %(first_index)s-%(last_index)s out of %(num_items)s total'), gettext('Showing {firstIndex}-{lastIndex} out of {numItems} total'),
context, true context
); );
} }
this.$el.html(_.template(headerTemplate)({
HtmlUtils.setHtml(
this.$el,
HtmlUtils.template(headerTemplate)({
message: message, message: message,
srInfo: this.srInfo, srInfo: this.srInfo,
sortableFields: this.collection.sortableFields, sortableFields: this.collection.sortableFields,
sortOrder: this.sortOrder, sortOrder: this.sortOrder,
showSortControls: this.showSortControls showSortControls: this.showSortControls
})); })
);
return this; return this;
}, },
......
...@@ -6,8 +6,14 @@ ...@@ -6,8 +6,14 @@
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['backbone', 'jquery', 'underscore', 'text!common/templates/components/search-field.underscore'], define([
function (Backbone, $, _, searchFieldTemplate) { 'backbone',
'jquery',
'underscore',
'edx-ui-toolkit/js/utils/html-utils',
'text!common/templates/components/search-field.underscore'
],
function (Backbone, $, _, HtmlUtils, searchFieldTemplate) {
return Backbone.View.extend({ return Backbone.View.extend({
events: { events: {
...@@ -16,7 +22,7 @@ ...@@ -16,7 +22,7 @@
'keyup .search-field': 'refreshState', 'keyup .search-field': 'refreshState',
'click .action-clear': 'clearSearch', 'click .action-clear': 'clearSearch',
'mouseover .action-clear': 'setMouseOverState', 'mouseover .action-clear': 'setMouseOverState',
'mouseout .action-clear': 'setMouseOutState', 'mouseout .action-clear': 'setMouseOutState'
}, },
initialize: function(options) { initialize: function(options) {
...@@ -29,6 +35,7 @@ ...@@ -29,6 +35,7 @@
var searchField = this.$('.search-field'), var searchField = this.$('.search-field'),
clearButton = this.$('.action-clear'), clearButton = this.$('.action-clear'),
searchString = $.trim(searchField.val()); searchString = $.trim(searchField.val());
if (searchString) { if (searchString) {
clearButton.removeClass('is-hidden'); clearButton.removeClass('is-hidden');
} else { } else {
...@@ -37,11 +44,14 @@ ...@@ -37,11 +44,14 @@
}, },
render: function() { render: function() {
this.$el.html(_.template(searchFieldTemplate)({ HtmlUtils.setHtml(
this.$el,
HtmlUtils.template(searchFieldTemplate)({
type: this.type, type: this.type,
searchString: this.collection.searchString, searchString: this.collection.searchString,
searchLabel: this.label searchLabel: this.label
})); })
);
this.refreshState(); this.refreshState();
return this; return this;
}, },
......
...@@ -2,10 +2,10 @@ define([ ...@@ -2,10 +2,10 @@ define([
'jquery', 'jquery',
'backbone', 'backbone',
'underscore', 'underscore',
'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers',
'common/js/components/views/paginated_view', 'common/js/components/views/paginated_view'
'common/js/components/collections/paging_collection' ], function ($, Backbone, _, PagingCollection, AjaxHelpers, PaginatedView) {
], function ($, Backbone, _, AjaxHelpers, PaginatedView, PagingCollection) {
'use strict'; 'use strict';
describe('PaginatedView', function () { describe('PaginatedView', function () {
var TestItemView = Backbone.View.extend({ var TestItemView = Backbone.View.extend({
...@@ -36,11 +36,18 @@ define([ ...@@ -36,11 +36,18 @@ define([
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="test-container"></div>'); setFixtures('<div class="test-container"></div>');
initialItems = generateItems(5); initialItems = generateItems(5);
testCollection = new PagingCollection({ var TestPagingCollection = PagingCollection.extend({
state: {
pageSize: 5
}
});
testCollection = new TestPagingCollection();
testCollection.url = '/dummy/url';
testCollection.set({
count: 6, count: 6,
num_pages: 2, num_pages: 2,
current_page: 1, page: 1,
start: 0,
results: initialItems results: initialItems
}, {parse: true}); }, {parse: true});
testView = new TestPaginatedView({el: '.test-container', collection: testCollection}).render(); testView = new TestPaginatedView({el: '.test-container', collection: testCollection}).render();
...@@ -76,7 +83,7 @@ define([ ...@@ -76,7 +83,7 @@ define([
function expectFooter(options) { function expectFooter(options) {
var footerEl = testView.$('.test-paging-footer'); var footerEl = testView.$('.test-paging-footer');
expect(footerEl.text()) expect(footerEl.text())
.toMatch(new RegExp(options.currentPage + '\\s+out of\\s+\/\\s+' + testCollection.totalPages)); .toMatch(new RegExp(options.currentPage + '\\s+out of\\s+\/\\s+' + options.totalPages));
expect(footerEl.hasClass('hidden')).toBe(options.isHidden); expect(footerEl.hasClass('hidden')).toBe(options.isHidden);
} }
...@@ -90,11 +97,11 @@ define([ ...@@ -90,11 +97,11 @@ define([
initialItems = generateItems(1); initialItems = generateItems(1);
testCollection.set( testCollection.set(
{ {
"count": 1, count: 1,
"num_pages": 1, num_pages: 1,
"current_page": 1, page: 1,
"start": 0, start: 0,
"results": initialItems results: initialItems
}, },
{parse: true} {parse: true}
); );
...@@ -112,11 +119,10 @@ define([ ...@@ -112,11 +119,10 @@ define([
AjaxHelpers.expectNoRequests(requests); AjaxHelpers.expectNoRequests(requests);
testView.$(nextPageButtonCss).click(); testView.$(nextPageButtonCss).click();
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
"count": 6, count: 6,
"num_pages": 2, num_pages: 2,
"current_page": 2, page: 2,
"start": 5, results: newItems
"results": newItems
}); });
expectHeader('Showing 6-6 out of 6 total'); expectHeader('Showing 6-6 out of 6 total');
expectItems(newItems); expectItems(newItems);
...@@ -129,11 +135,10 @@ define([ ...@@ -129,11 +135,10 @@ define([
initialItems = generateItems(1); initialItems = generateItems(1);
testCollection.set( testCollection.set(
{ {
"count": 6, count: 6,
"num_pages": 2, num_pages: 2,
"current_page": 2, page: 2,
"start": 5, results: initialItems
"results": initialItems
}, },
{parse: true} {parse: true}
); );
...@@ -143,11 +148,10 @@ define([ ...@@ -143,11 +148,10 @@ define([
testView.$(previousPageButtonCss).click(); testView.$(previousPageButtonCss).click();
previousPageItems = generateItems(5); previousPageItems = generateItems(5);
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
"count": 6, count: 6,
"num_pages": 2, num_pages: 2,
"current_page": 1, page: 1,
"start": 0, results: previousPageItems
"results": previousPageItems
}); });
expectHeader('Showing 1-5 out of 6 total'); expectHeader('Showing 1-5 out of 6 total');
expectItems(previousPageItems); expectItems(previousPageItems);
...@@ -159,11 +163,10 @@ define([ ...@@ -159,11 +163,10 @@ define([
spyOn($.fn, 'focus'); spyOn($.fn, 'focus');
testView.$(nextPageButtonCss).click(); testView.$(nextPageButtonCss).click();
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
"count": 6, count: 6,
"num_pages": 2, num_pages: 2,
"current_page": 2, page: 2,
"start": 5, results: generateItems(1)
"results": generateItems(1)
}); });
expect(testView.$('.sr-is-focusable').focus).toHaveBeenCalled(); expect(testView.$('.sr-is-focusable').focus).toHaveBeenCalled();
}); });
......
define(['jquery',
'backbone',
'underscore',
'URI',
'common/js/components/collections/paging_collection',
'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/spec_helpers'
],
function ($, Backbone, _, URI, PagingCollection, AjaxHelpers, SpecHelpers) {
'use strict';
describe('PagingCollection', function () {
var collection;
var server = {
isZeroIndexed: false,
count: 43,
respond: function (requests) {
var request = AjaxHelpers.currentRequest(requests),
params = (new URI(request.url)).query(true),
page = parseInt(params['page'], 10),
page_size = parseInt(params['page_size'], 10),
page_count = Math.ceil(this.count / page_size);
// Make zeroPage consistently start at zero for ease of calculation
var zeroPage = page - (this.isZeroIndexed ? 0 : 1);
if (zeroPage < 0 || zeroPage > page_count) {
AjaxHelpers.respondWithError(requests, 404);
} else {
AjaxHelpers.respondWithJson(requests, {
'count': this.count,
'current_page': page,
'num_pages': page_count,
'start': zeroPage * page_size,
'results': []
});
}
}
};
var assertQueryParams = function (requests, params) {
var request = AjaxHelpers.currentRequest(requests),
urlParams = (new URI(request.url)).query(true);
_.each(params, function (value, key) {
expect(urlParams[key]).toBe(value);
});
};
beforeEach(function () {
collection = new PagingCollection();
collection.perPage = 10;
server.isZeroIndexed = false;
server.count = 43;
});
it('can register sortable fields', function () {
collection.registerSortableField('test_field', 'Test Field');
expect('test_field' in collection.sortableFields).toBe(true);
expect(collection.sortableFields['test_field'].displayName).toBe('Test Field');
});
it('can register filterable fields', function () {
collection.registerFilterableField('test_field', 'Test Field');
expect('test_field' in collection.filterableFields).toBe(true);
expect(collection.filterableFields['test_field'].displayName).toBe('Test Field');
});
it('sets the sort field based on the server response', function () {
var sort_order = 'my_sort_order';
collection = new PagingCollection({sort_order: sort_order}, {parse: true});
expect(collection.sortField).toBe(sort_order);
});
it('can set the sort field', function () {
var requests = AjaxHelpers.requests(this);
collection.registerSortableField('test_field', 'Test Field');
collection.setSortField('test_field', false);
collection.refresh();
assertQueryParams(requests, {'sort_order': 'test_field'});
expect(collection.sortField).toBe('test_field');
expect(collection.sortDisplayName()).toBe('Test Field');
});
it('can set the filter field', function () {
collection.registerFilterableField('test_field', 'Test Field');
collection.setFilterField('test_field');
collection.refresh();
// The default implementation does not send any query params for filtering
expect(collection.filterField).toBe('test_field');
expect(collection.filterDisplayName()).toBe('Test Field');
});
it('can set the sort direction', function () {
collection.setSortDirection(PagingCollection.SortDirection.ASCENDING);
// The default implementation does not send any query params for sort direction
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.ASCENDING);
collection.setSortDirection(PagingCollection.SortDirection.DESCENDING);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
});
it('can toggle the sort direction when setting the sort field', function () {
collection.registerSortableField('test_field', 'Test Field');
collection.registerSortableField('test_field_2', 'Test Field 2');
collection.setSortField('test_field', true);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
collection.setSortField('test_field', true);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.ASCENDING);
collection.setSortField('test_field', true);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
collection.setSortField('test_field_2', true);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
});
SpecHelpers.withData({
'queries with page, page_size, and sort_order parameters when zero indexed': [true, 2],
'queries with page, page_size, and sort_order parameters when one indexed': [false, 3],
}, function (isZeroIndexed, page) {
var requests = AjaxHelpers.requests(this);
collection.isZeroIndexed = isZeroIndexed;
collection.perPage = 5;
collection.sortField = 'test_field';
collection.setPage(3);
assertQueryParams(requests, {'page': page.toString(), 'page_size': '5', 'sort_order': 'test_field'});
});
SpecHelpers.withConfiguration({
'using a zero indexed collection': [true],
'using a one indexed collection': [false]
}, function (isZeroIndexed) {
collection.isZeroIndexed = isZeroIndexed;
server.isZeroIndexed = isZeroIndexed;
}, function () {
describe('setPage', function() {
it('triggers a reset event when the page changes successfully', function () {
var requests = AjaxHelpers.requests(this),
resetTriggered = false;
collection.on('reset', function () { resetTriggered = true; });
collection.setPage(3);
server.respond(requests);
expect(resetTriggered).toBe(true);
});
it('triggers an error event when the requested page is out of range', function () {
var requests = AjaxHelpers.requests(this),
errorTriggered = false;
collection.on('error', function () { errorTriggered = true; });
collection.setPage(17);
server.respond(requests);
expect(errorTriggered).toBe(true);
});
it('triggers an error event if the server responds with a 500', function () {
var requests = AjaxHelpers.requests(this),
errorTriggered = false;
collection.on('error', function () { errorTriggered = true; });
collection.setPage(2);
expect(collection.getPage()).toBe(2);
server.respond(requests);
collection.setPage(3);
AjaxHelpers.respondWithError(requests, 500);
expect(errorTriggered).toBe(true);
expect(collection.getPage()).toBe(2);
});
});
describe('getPage', function () {
it('returns the correct page', function () {
var requests = AjaxHelpers.requests(this);
collection.setPage(1);
server.respond(requests);
expect(collection.getPage()).toBe(1);
collection.setPage(3);
server.respond(requests);
expect(collection.getPage()).toBe(3);
});
});
describe('hasNextPage', function () {
SpecHelpers.withData(
{
'returns false for a single page': [1, 3, false],
'returns true on the first page': [1, 43, true],
'returns true on the penultimate page': [4, 43, true],
'returns false on the last page': [5, 43, false]
},
function (page, count, result) {
var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
server.respond(requests);
expect(collection.hasNextPage()).toBe(result);
}
);
});
describe('hasPreviousPage', function () {
SpecHelpers.withData(
{
'returns false for a single page': [1, 3, false],
'returns true on the last page': [5, 43, true],
'returns true on the second page': [2, 43, true],
'returns false on the first page': [1, 43, false]
},
function (page, count, result) {
var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
server.respond(requests);
expect(collection.hasPreviousPage()).toBe(result);
}
);
});
describe('nextPage', function () {
SpecHelpers.withData(
{
'advances to the next page': [2, 43, 3],
'silently fails on the last page': [5, 43, 5]
},
function (page, count, newPage) {
var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
server.respond(requests);
expect(collection.getPage()).toBe(page);
collection.nextPage();
if (requests.length > 1) {
server.respond(requests);
}
expect(collection.getPage()).toBe(newPage);
}
);
});
describe('previousPage', function () {
SpecHelpers.withData(
{
'moves to the previous page': [2, 43, 1],
'silently fails on the first page': [1, 43, 1]
},
function (page, count, newPage) {
var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
server.respond(requests);
expect(collection.getPage()).toBe(page);
collection.previousPage();
if (requests.length > 1) {
server.respond(requests);
}
expect(collection.getPage()).toBe(newPage);
}
);
});
});
});
}
);
...@@ -2,10 +2,10 @@ define([ ...@@ -2,10 +2,10 @@ define([
'jquery', 'jquery',
'URI', 'URI',
'underscore', 'underscore',
'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers',
'common/js/components/views/paging_footer', 'common/js/components/views/paging_footer'
'common/js/components/collections/paging_collection' ], function ($, URI, _, PagingCollection, AjaxHelpers, PagingFooter) {
], function ($, URI, _, AjaxHelpers, PagingFooter, PagingCollection) {
'use strict'; 'use strict';
describe("PagingFooter", function () { describe("PagingFooter", function () {
var pagingFooter, var pagingFooter,
...@@ -15,10 +15,10 @@ define([ ...@@ -15,10 +15,10 @@ define([
} }
return { return {
count: null, count: null,
current_page: currentPage, page: currentPage,
num_pages: numPages, num_pages: numPages,
start: null, // need to have non-empty collection to render
results: _.map(_.range(collectionLength), function() { return {}; }) // need to have non-empty collection to render results: _.map(_.range(collectionLength), function() { return {}; })
}; };
}, },
nextPageCss = '.next-page-link', nextPageCss = '.next-page-link',
...@@ -29,9 +29,12 @@ define([ ...@@ -29,9 +29,12 @@ define([
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="paging-footer"></div>'); setFixtures('<div class="paging-footer"></div>');
var collection = new PagingCollection(mockPage(1, 2), {parse: true});
collection.url = '/test/url/';
pagingFooter = new PagingFooter({ pagingFooter = new PagingFooter({
el: $('.paging-footer'), el: $('.paging-footer'),
collection: new PagingCollection(mockPage(1, 2), {parse: true}) collection: collection
}).render(); }).render();
}); });
...@@ -75,7 +78,7 @@ define([ ...@@ -75,7 +78,7 @@ define([
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
pagingFooter.$(nextPageCss).click(); pagingFooter.$(nextPageCss).click();
AjaxHelpers.respondWithJson(requests, mockPage(2, 2)); AjaxHelpers.respondWithJson(requests, mockPage(2, 2));
expect(pagingFooter.collection.currentPage).toBe(2); expect(pagingFooter.collection.getPageNumber()).toBe(2);
}); });
it('should be enabled when there is at least one more page', function () { it('should be enabled when there is at least one more page', function () {
...@@ -91,7 +94,7 @@ define([ ...@@ -91,7 +94,7 @@ define([
}); });
}); });
describe("Previous page button", function () { describe('Previous page button', function () {
it('does not move back if a server error occurs', function () { it('does not move back if a server error occurs', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
pagingFooter.collection.reset(mockPage(2, 2), {parse: true}); pagingFooter.collection.reset(mockPage(2, 2), {parse: true});
......
define([ define([
'underscore', 'underscore',
'common/js/components/views/paging_header', 'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/components/collections/paging_collection' 'common/js/components/views/paging_header'
], function (_, PagingHeader, PagingCollection) { ], function (_, PagingCollection, PagingHeader) {
'use strict'; 'use strict';
describe('PagingHeader', function () { describe('PagingHeader', function () {
var pagingHeader, var pagingHeader,
......
define([ define([
'underscore', 'underscore',
'URI',
'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/components/views/search_field', 'common/js/components/views/search_field',
'common/js/components/collections/paging_collection',
'common/js/spec_helpers/ajax_helpers' 'common/js/spec_helpers/ajax_helpers'
], function (_, SearchFieldView, PagingCollection, AjaxHelpers) { ], function (_, URI, PagingCollection, SearchFieldView, AjaxHelpers) {
'use strict'; 'use strict';
describe('SearchFieldView', function () { describe('SearchFieldView', function () {
var searchFieldView, var searchFieldView,
mockUrl = '/api/mock_collection'; mockUrl = '/api/mock_collection';
var newCollection = function (size, perPage) { var newCollection = function (size, perPage) {
var pageSize = 5, var results = _.map(_.range(size), function (i) { return {foo: i}; });
results = _.map(_.range(size), function (i) { return {foo: i}; }); var TestPagingCollection = PagingCollection.extend({
var collection = new PagingCollection( state: {
[], pageSize: 5
{ }
url: mockUrl, });
var collection = new TestPagingCollection({
count: results.length, count: results.length,
num_pages: results.length / pageSize, num_pages: Math.ceil(results.length / perPage),
current_page: 1, page: 1,
start: 0,
results: _.first(results, perPage) results: _.first(results, perPage)
} }, {parse: true});
);
collection.start = 0; collection.url = mockUrl;
collection.totalCount = results.length;
return collection; return collection;
}; };
...@@ -40,6 +41,18 @@ define([ ...@@ -40,6 +41,18 @@ define([
return new SearchFieldView(options); return new SearchFieldView(options);
}; };
var assertQueryParams = function (request, expectedParameters) {
var urlParams = new URI(request.url).query(true);
_.each(expectedParameters, function (value, key) {
expect(urlParams[key]).toBe(value);
});
};
var assertNotInQueryParams = function (request, param) {
var urlParams = new URI(request.url).query(true);
return !urlParams.hasOwnProperty(param);
};
beforeEach(function() { beforeEach(function() {
setFixtures('<section class="test-search"></section>'); setFixtures('<section class="test-search"></section>');
}); });
...@@ -62,17 +75,16 @@ define([ ...@@ -62,17 +75,16 @@ define([
searchFieldView = createSearchFieldView().render(); searchFieldView = createSearchFieldView().render();
searchFieldView.$('.search-field').val('foo'); searchFieldView.$('.search-field').val('foo');
searchFieldView.$('.action-search').click(); searchFieldView.$('.action-search').click();
AjaxHelpers.expectRequestURL(requests, mockUrl, { assertQueryParams(requests[0], {
page: '1', page: '1',
page_size: '10', page_size: '5',
sort_order: '',
text_search: 'foo' text_search: 'foo'
}); });
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
count: 10, count: 10,
current_page: 1, page: 1,
num_pages: 1, num_pages: 1,
start: 0,
results: [] results: []
}); });
expect(searchFieldView.$('.search-field').val(), 'foo'); expect(searchFieldView.$('.search-field').val(), 'foo');
...@@ -84,17 +96,12 @@ define([ ...@@ -84,17 +96,12 @@ define([
searchString: 'foo' searchString: 'foo'
}).render(); }).render();
searchFieldView.$('.action-clear').click(); searchFieldView.$('.action-clear').click();
AjaxHelpers.expectRequestURL(requests, mockUrl, { assertNotInQueryParams('text_search');
page: '1',
page_size: '10',
sort_order: '',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
count: 10, count: 10,
current_page: 1, page: 1,
num_pages: 1, num_pages: 1,
start: 0,
results: [] results: []
}); });
expect(searchFieldView.$('.search-field').val(), ''); expect(searchFieldView.$('.search-field').val(), '');
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
'underscore.string': 'common/js/vendor/underscore.string', 'underscore.string': 'common/js/vendor/underscore.string',
'backbone': 'common/js/vendor/backbone', 'backbone': 'common/js/vendor/backbone',
'backbone.associations': 'js/vendor/backbone-associations-min', 'backbone.associations': 'js/vendor/backbone-associations-min',
'backbone.paginator': 'js/vendor/backbone.paginator.min', 'backbone.paginator': 'common/js/vendor/backbone.paginator',
'backbone-super': 'js/vendor/backbone-super', 'backbone-super': 'js/vendor/backbone-super',
'jasmine-imagediff': 'js/vendor/jasmine-imagediff', 'jasmine-imagediff': 'js/vendor/jasmine-imagediff',
'URI': 'js/vendor/URI.min', 'URI': 'js/vendor/URI.min',
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
}, },
'backbone.paginator': { 'backbone.paginator': {
deps: ['backbone'], deps: ['backbone'],
exports: 'Backbone.Paginator' exports: 'Backbone.PageableCollection'
}, },
"backbone-super": { "backbone-super": {
deps: ["backbone"] deps: ["backbone"]
...@@ -166,7 +166,6 @@ ...@@ -166,7 +166,6 @@
'common/js/spec/components/feedback_spec.js', 'common/js/spec/components/feedback_spec.js',
'common/js/spec/components/list_spec.js', 'common/js/spec/components/list_spec.js',
'common/js/spec/components/paginated_view_spec.js', 'common/js/spec/components/paginated_view_spec.js',
'common/js/spec/components/paging_collection_spec.js',
'common/js/spec/components/paging_header_spec.js', 'common/js/spec/components/paging_header_spec.js',
'common/js/spec/components/paging_footer_spec.js', 'common/js/spec/components/paging_footer_spec.js',
'common/js/spec/components/search_field_spec.js', 'common/js/spec/components/search_field_spec.js',
......
<% if (!_.isUndefined(srInfo)) { %> <% if (!_.isUndefined(srInfo)) { %>
<h2 class="sr" id="<%= srInfo.id %>"><%- srInfo.text %></h2> <h2 class="sr" id="<%- srInfo.id %>"><%- srInfo.text %></h2>
<% } %> <% } %>
<div class="search-tools listing-tools"> <div class="search-tools listing-tools">
<span class="search-count listing-count"> <span class="search-count listing-count">
<%= message %> <%- message %>
</span> </span>
<% if (showSortControls) { %> <% if (showSortControls) { %>
| |
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<label class="field-label" for="paging-header-select"><%- gettext("Sorted by") %></label> <label class="field-label" for="paging-header-select"><%- gettext("Sorted by") %></label>
<select id="paging-header-select" name="paging-header-select" class="field-input input-select listing-sort-select"> <select id="paging-header-select" name="paging-header-select" class="field-input input-select listing-sort-select">
<% _.each(sortableFields, function (option, key) { %> <% _.each(sortableFields, function (option, key) { %>
<option value="<%= key %>" <% if (key === sortOrder) { %> selected="true" <% } %>> <option value="<%- key %>" <% if (key === sortOrder) { %> selected="true" <% } %>>
<%- option.displayName %> <%- option.displayName %>
</option> </option>
<% }) %> <% }) %>
......
<div class="page-header-search wrapper-search-<%= type %>"> <div class="page-header-search wrapper-search-<%- type %>">
<form class="search-form"> <form class="search-form">
<div class="wrapper-search-input"> <div class="wrapper-search-input">
<label for="search-<%= type %>" class="search-label"><%- searchLabel %></label> <label for="search-<%- type %>" class="search-label"><%- searchLabel %></label>
<input id="search-<%= type %>" class="search-field" type="text" value="<%- searchString %>" placeholder="<%- searchLabel %>" /> <input id="search-<%- type %>" class="search-field" type="text" value="<%- searchString %>" placeholder="<%- searchLabel %>" />
<button type="button" class="action action-clear <%= searchLabel ? '' : 'is-hidden' %>" aria-label="<%- gettext('Clear search') %>"> <button type="button" class="action action-clear <%- searchLabel ? '' : 'is-hidden' %>" aria-label="<%- gettext('Clear search') %>">
<i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr"><%- gettext('Search') %></span> <i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr"><%- gettext('Search') %></span>
</button> </button>
</div> </div>
......
/*! backbone.paginator - v0.8.1 - 7/3/2013
* http://github.com/addyosmani/backbone.paginator
* Copyright (c) 2013 Addy Osmani; Licensed MIT */
Backbone.Paginator=function(a,b,c){"use strict";var d=b.map(a.VERSION.split("."),function(a){return parseInt(a,10)}),e={};e.version="0.8.1",e.clientPager=a.Collection.extend({useDiacriticsPlugin:!0,useLevenshteinPlugin:!0,sortColumn:"",sortDirection:"desc",lastSortColumn:"",fieldFilterRules:[],lastFieldFilterRules:[],filterFields:"",filterExpression:"",lastFilterExpression:"",defaults_ui:{firstPage:0,currentPage:1,perPage:5,totalPages:10,pagesInRange:4},initialize:function(){this.on("add",this.addModel,this),this.on("remove",this.removeModel,this),this.setDefaults()},setDefaults:function(){var a=b.defaults(this.paginator_ui,this.defaults_ui);b.defaults(this,a)},addModel:function(a){this.origModels.push(a)},removeModel:function(a){var c=b.indexOf(this.origModels,a);this.origModels.splice(c,1)},sync:function(e,f,g){var h=this;this.setDefaults();var i={};b.each(b.result(h,"server_api"),function(a,c){b.isFunction(a)&&(a=b.bind(a,h),a=a()),i[c]=a});var j=b.clone(h.paginator_core);b.each(j,function(a,c){b.isFunction(a)&&(a=b.bind(a,h),a=a()),j[c]=a}),j=b.defaults(j,{timeout:25e3,cache:!1,type:"GET",dataType:"jsonp"}),j=b.extend(j,{data:decodeURIComponent(c.param(i)),processData:!1,url:b.result(j,"url")},g);var k=!(0===d[0]&&9===d[1]&&10===d[2]),l=j.success;j.success=function(a,b,c){l&&(k?l(a,b,c):l(f,a,j)),f&&f.trigger&&f.trigger("sync",f,a,j)};var m=j.error;j.error=function(a){m&&m(f,a,j),f&&f.trigger&&f.trigger("error",f,a,j)};var n=j.xhr=a.ajax(j);return f&&f.trigger&&f.trigger("request",f,n,j),n},nextPage:function(a){this.currentPage<this.information.totalPages&&(this.currentPage=++this.currentPage,this.pager(a))},previousPage:function(a){this.currentPage>1&&(this.currentPage=--this.currentPage,this.pager(a))},goTo:function(a,b){void 0!==a&&(this.currentPage=parseInt(a,10),this.pager(b))},howManyPer:function(a){if(void 0!==a){var b=this.perPage;this.perPage=parseInt(a,10),this.currentPage=Math.ceil((b*(this.currentPage-1)+1)/a),this.pager()}},setSort:function(a,b){void 0!==a&&void 0!==b&&(this.lastSortColumn=this.sortColumn,this.sortColumn=a,this.sortDirection=b,this.pager(),this.info())},setFieldFilter:function(a){b.isEmpty(a)?(this.lastFieldFilterRules=this.fieldFilterRules,this.fieldFilterRules="",this.pager(),this.info()):(this.lastFieldFilterRules=this.fieldFilterRules,this.fieldFilterRules=a,this.pager(),this.info())},doFakeFieldFilter:function(a){if(!b.isEmpty(a)){var c=this.origModels;return void 0===c&&(c=this.models),c=this._fieldFilter(c,a),""!==this.filterExpression&&(c=this._filter(c,this.filterFields,this.filterExpression)),c.length}},setFilter:function(a,b){void 0!==a&&void 0!==b&&(this.filterFields=a,this.lastFilterExpression=this.filterExpression,this.filterExpression=b,this.pager(),this.info())},doFakeFilter:function(a,c){if(void 0!==a&&void 0!==c){var d=this.origModels;return void 0===d&&(d=this.models),b.isEmpty(this.fieldFilterRules)||(d=this._fieldFilter(d,this.fieldFilterRules)),d=this._filter(d,a,c),d.length}},pager:function(a){var c=this,d=this.perPage,e=(c.currentPage-1)*d,f=e+d;void 0===c.origModels&&(c.origModels=c.models),c.models=c.origModels.slice(),""!==this.sortColumn&&(c.models=c._sort(c.models,this.sortColumn,this.sortDirection)),b.isEmpty(this.fieldFilterRules)||(c.models=c._fieldFilter(c.models,this.fieldFilterRules)),""!==this.filterExpression&&(c.models=c._filter(c.models,this.filterFields,this.filterExpression)),this.lastSortColumn===this.sortColumn&&this.lastFilterExpression===this.filterExpression&&b.isEqual(this.fieldFilterRules,this.lastFieldFilterRules)||(e=0,f=e+d,c.currentPage=1,this.lastSortColumn=this.sortColumn,this.lastFieldFilterRules=this.fieldFilterRules,this.lastFilterExpression=this.filterExpression),c.sortedAndFilteredModels=c.models.slice(),c.info(),c.reset(c.models.slice(e,f)),b.result(a,"success")},_sort:function(a,c,d){return a=a.sort(function(a,e){var f=a.get(c),g=e.get(c);if(b.isUndefined(f)||b.isUndefined(g)||null===f||null===g)return 0;if(f=f.toString().toLowerCase(),g=g.toString().toLowerCase(),"desc"===d)if(!f.match(/[^\-\d\.]/)&&f.match(/-?[\d\.]+/)&&!g.match(/[^\-\d\.]/)&&g.match(/-?[\d\.]+/)){if(g-0>f-0)return 1;if(f-0>g-0)return-1}else{if(g>f)return 1;if(f>g)return-1}else if(!f.match(/[^\-\d\.]/)&&f.match(/-?[\d\.]+/)&&!g.match(/[^\-\d\.]/)&&g.match(/-?[\d\.]+/)){if(g-0>f-0)return-1;if(f-0>g-0)return 1}else{if(g>f)return-1;if(f>g)return 1}if(a.cid&&e.cid){var h=a.cid,i=e.cid;if(i>h)return-1;if(h>i)return 1}return 0})},_fieldFilter:function(a,c){if(b.isEmpty(c))return a;var d=[];return b.each(a,function(a){var e=!0;b.each(c,function(c){if(!e)return!1;if(e=!1,"function"===c.type){var d=b.wrap(c.value,function(b){return b(a.get(c.field))});d()&&(e=!0)}else"required"===c.type?b.isEmpty(a.get(c.field).toString())||(e=!0):"min"===c.type?!b.isNaN(Number(a.get(c.field)))&&!b.isNaN(Number(c.value))&&Number(a.get(c.field))>=Number(c.value)&&(e=!0):"max"===c.type?!b.isNaN(Number(a.get(c.field)))&&!b.isNaN(Number(c.value))&&Number(a.get(c.field))<=Number(c.value)&&(e=!0):"range"===c.type?!b.isNaN(Number(a.get(c.field)))&&b.isObject(c.value)&&!b.isNaN(Number(c.value.min))&&!b.isNaN(Number(c.value.max))&&Number(a.get(c.field))>=Number(c.value.min)&&Number(a.get(c.field))<=Number(c.value.max)&&(e=!0):"minLength"===c.type?a.get(c.field).toString().length>=c.value&&(e=!0):"maxLength"===c.type?a.get(c.field).toString().length<=c.value&&(e=!0):"rangeLength"===c.type?b.isObject(c.value)&&!b.isNaN(Number(c.value.min))&&!b.isNaN(Number(c.value.max))&&a.get(c.field).toString().length>=c.value.min&&a.get(c.field).toString().length<=c.value.max&&(e=!0):"oneOf"===c.type?b.isArray(c.value)&&b.include(c.value,a.get(c.field))&&(e=!0):"equalTo"===c.type?c.value===a.get(c.field)&&(e=!0):"containsAllOf"===c.type?b.isArray(c.value)&&b.isArray(a.get(c.field))&&b.intersection(c.value,a.get(c.field)).length===c.value.length&&(e=!0):"pattern"===c.type?a.get(c.field).toString().match(c.value)&&(e=!0):e=!1}),e&&d.push(a)}),d},_filter:function(c,d,e){var f=this,g={};if(b.isString(d)?g[d]={cmp_method:"regexp"}:b.isArray(d)?b.each(d,function(a){g[a]={cmp_method:"regexp"}}):b.each(d,function(a,c){g[c]=b.defaults(a,{cmp_method:"regexp"})}),d=g,b.has(a.Paginator,"removeDiacritics")&&f.useDiacriticsPlugin&&(e=a.Paginator.removeDiacritics(e)),""===e||!b.isString(e))return c;var h=b.map(e.match(/\w+/gi),function(a){return a.toLowerCase()}),i="("+b.uniq(h).join("|")+")",j=new RegExp(i,"igm"),k=[];return b.each(c,function(c){var g=[];b.each(d,function(d,i){var k=c.get(i);if(k){var l=[];if(k=b.has(a.Paginator,"removeDiacritics")&&f.useDiacriticsPlugin?a.Paginator.removeDiacritics(k.toString()):k.toString(),"levenshtein"===d.cmp_method&&b.has(a.Paginator,"levenshtein")&&f.useLevenshteinPlugin){var m=a.Paginator.levenshtein(k,e);b.defaults(d,{max_distance:0}),m<=d.max_distance&&(l=b.uniq(h))}else l=k.match(j);l=b.map(l,function(a){return a.toString().toLowerCase()}),b.each(l,function(a){g.push(a)})}}),g=b.uniq(b.without(g,"")),b.isEmpty(b.difference(h,g))&&k.push(c)}),k},info:function(){var a=this,b={},c=a.sortedAndFilteredModels?a.sortedAndFilteredModels.length:a.length,d=Math.ceil(c/a.perPage);return b={totalUnfilteredRecords:a.origModels.length,totalRecords:c,currentPage:a.currentPage,perPage:this.perPage,totalPages:d,lastPage:d,previous:!1,next:!1,startRecord:0===c?0:(a.currentPage-1)*this.perPage+1,endRecord:Math.min(c,a.currentPage*this.perPage)},a.currentPage>1&&(b.previous=a.currentPage-1),a.currentPage<b.totalPages&&(b.next=a.currentPage+1),b.pageSet=a.setPagination(b),a.information=b,b},setPagination:function(a){var b=[],c=0,d=0,e=2*this.pagesInRange,f=Math.ceil(a.totalRecords/a.perPage);if(f>1)if(1+e>=f)for(c=1,d=f;d>=c;c++)b.push(c);else if(a.currentPage<=this.pagesInRange+1)for(c=1,d=2+e;d>c;c++)b.push(c);else if(f-this.pagesInRange>a.currentPage&&a.currentPage>this.pagesInRange)for(c=a.currentPage-this.pagesInRange;c<=a.currentPage+this.pagesInRange;c++)b.push(c);else for(c=f-e;f>=c;c++)b.push(c);return b},bootstrap:function(a){return b.extend(this,a),this.goTo(1),this.info(),this}}),e.clientPager.prototype.prevPage=e.clientPager.prototype.previousPage;var f=function(){var a=new c.Deferred;return a.reject(),a.promise()};return e.requestPager=a.Collection.extend({sync:function(e,f,g){var h=this;h.setDefaults();var i={};b.each(b.result(h,"server_api"),function(a,c){b.isFunction(a)&&(a=b.bind(a,h),a=a()),i[c]=a});var j=b.clone(h.paginator_core);b.each(j,function(a,c){b.isFunction(a)&&(a=b.bind(a,h),a=a()),j[c]=a}),j=b.defaults(j,{timeout:25e3,cache:!1,type:"GET",dataType:"jsonp"}),g.data=g.data?decodeURIComponent(c.param(b.extend(i,g.data))):decodeURIComponent(c.param(i)),j=b.extend(j,{data:decodeURIComponent(c.param(i)),processData:!1,url:b.result(j,"url")},g);var k=!(0===d[0]&&9===d[1]&&10===d[2]),l=j.success;j.success=function(a,b,c){l&&(k?l(a,b,c):l(f,a,j)),d[0]<1&&f&&f.trigger&&f.trigger("sync",f,a,j)};var m=j.error;j.error=function(a){m&&m(a),f&&f.trigger&&f.trigger("error",f,a,j)};var n=j.xhr=a.ajax(j);return f&&f.trigger&&f.trigger("request",f,n,j),n},setDefaults:function(){var a=this;b.defaults(a.paginator_ui,{firstPage:0,currentPage:1,perPage:5,totalPages:10,pagesInRange:4}),b.each(a.paginator_ui,function(c,d){b.isUndefined(a[d])&&(a[d]=a.paginator_ui[d])})},requestNextPage:function(a){return void 0!==this.currentPage?(this.currentPage+=1,this.pager(a)):f()},requestPreviousPage:function(a){return void 0!==this.currentPage?(this.currentPage-=1,this.pager(a)):f()},updateOrder:function(a,b){return void 0!==a?(this.sortField=a,this.pager(b)):f()},goTo:function(a,b){return void 0!==a?(this.currentPage=parseInt(a,10),this.pager(b)):f()},howManyPer:function(a,b){return void 0!==a?(this.currentPage=this.firstPage,this.perPage=a,this.pager(b)):f()},info:function(){var a={totalRecords:this.totalRecords||0,currentPage:this.currentPage,firstPage:this.firstPage,totalPages:Math.ceil(this.totalRecords/this.perPage),lastPage:this.totalPages,perPage:this.perPage,previous:!1,next:!1};return this.currentPage>1&&(a.previous=this.currentPage-1),this.currentPage<a.totalPages&&(a.next=this.currentPage+1),a.hasNext=a.next,a.hasPrevious=a.next,a.pageSet=this.setPagination(a),this.information=a,a},setPagination:function(a){var b=[],c=0,d=0,e=2*this.pagesInRange,f=Math.ceil(a.totalRecords/a.perPage);if(f>1)if(1+e>=f)for(c=1,d=f;d>=c;c++)b.push(c);else if(a.currentPage<=this.pagesInRange+1)for(c=1,d=2+e;d>c;c++)b.push(c);else if(f-this.pagesInRange>a.currentPage&&a.currentPage>this.pagesInRange)for(c=a.currentPage-this.pagesInRange;c<=a.currentPage+this.pagesInRange;c++)b.push(c);else for(c=f-e;f>=c;c++)b.push(c);return b},pager:function(a){return b.isObject(a)||(a={}),this.fetch(a)},url:function(){return void 0!==this.paginator_core&&void 0!==this.paginator_core.url?this.paginator_core.url:null},bootstrap:function(a){return b.extend(this,a),this.setDefaults(),this.info(),this}}),e.requestPager.prototype.nextPage=e.requestPager.prototype.requestNextPage,e.requestPager.prototype.prevPage=e.requestPager.prototype.requestPreviousPage,e}(Backbone,_,jQuery);
\ No newline at end of file
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['common/js/components/collections/paging_collection'], define(['edx-ui-toolkit/js/pagination/paging-collection'],
function(PagingCollection) { function(PagingCollection) {
var BaseCollection = PagingCollection.extend({ var BaseCollection = PagingCollection.extend({
initialize: function(options) { constructor: function (models, options) {
this.options = options;
this.url = options.url; this.url = options.url;
this.state.perPage = options.per_page;
PagingCollection.prototype.initialize.call(this);
this.course_id = options.course_id; this.course_id = options.course_id;
this.perPage = options.per_page;
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.teamEvents.bind('teams:update', this.onUpdate, this); this.teamEvents.bind('teams:update', this.onUpdate, this);
this.queryParams = _.extend({}, BaseCollection.prototype.queryParams, this.queryParams);
PagingCollection.prototype.constructor.call(this, models, options);
},
parse: function (response, options) {
if (!response) {
response = {};
}
if (!response.results) {
response.results = [];
}
return PagingCollection.prototype.parse.call(this, response, options);
}, },
onUpdate: function(event) { onUpdate: function(event) {
// Mark the collection as stale so that it knows to refresh when needed. // Mark the collection as stale so that it knows to refresh when needed.
this.isStale = true; this.isStale = true;
},
sync: function (method, model, options) {
// do not send total pages and total records in request
if (method === 'read') {
var params = _.values(_.pick(this.queryParams, ['totalPages', 'totalRecords']));
_.each(params, function (param) {
delete options.data[param];
});
}
return PagingCollection.prototype.sync(method, model, options);
} }
}); });
return BaseCollection; return BaseCollection;
......
...@@ -2,13 +2,20 @@ ...@@ -2,13 +2,20 @@
'use strict'; 'use strict';
define(['teams/js/collections/team'], function (TeamCollection) { define(['teams/js/collections/team'], function (TeamCollection) {
var MyTeamsCollection = TeamCollection.extend({ var MyTeamsCollection = TeamCollection.extend({
initialize: function (teams, options) { queryParams: {
this.url = options.url; username: function () {
TeamCollection.prototype.initialize.call(this, teams, options); return this.options.username;
delete this.server_api.topic_id; },
this.server_api = _.extend(this.server_api, { text_search: function () {
username: options.username return this.searchString || '';
}); },
totalPages: null,
totalRecords: null
},
constructor: function (teams, options) {
TeamCollection.prototype.constructor.call(this, teams, options);
delete this.queryParams.topic_id;
} }
}); });
return MyTeamsCollection; return MyTeamsCollection;
......
...@@ -3,29 +3,46 @@ ...@@ -3,29 +3,46 @@
define(['teams/js/collections/base', 'teams/js/models/team', 'gettext'], define(['teams/js/collections/base', 'teams/js/models/team', 'gettext'],
function(BaseCollection, TeamModel, gettext) { function(BaseCollection, TeamModel, gettext) {
var TeamCollection = BaseCollection.extend({ var TeamCollection = BaseCollection.extend({
sortField: 'last_activity_at', model: TeamModel,
initialize: function(teams, options) { state: {
this.url = options.url; sortKey: 'last_activity_at',
var self = this; order: null
BaseCollection.prototype.initialize.call(this, options); },
this.server_api = _.extend( queryParams: {
this.server_api, topic_id: function () {
{ return this.options.topic_id;
topic_id: this.topic_id = options.topic_id, },
expand: 'user', expand: 'user',
course_id: function () { return encodeURIComponent(self.course_id); }, course_id: function () {
order_by: function () { return self.searchString ? '' : this.sortField; } return this.options.course_id;
},
order_by: function () {
return this.getSearchString() ? '' : this.state.sortKey;
},
text_search: function () {
return this.getSearchString() || '';
} }
); },
delete this.server_api.sort_order; // Sort order is not specified for the Team API
constructor: function(teams, options) {
this.state = _.extend({}, TeamCollection.prototype.state, this.state);
this.queryParams = _.extend({}, TeamCollection.prototype.queryParams, this.queryParams);
this.topic_id = options.topic_id;
BaseCollection.prototype.constructor.call(this, teams, options);
this.registerSortableField('last_activity_at', gettext('last activity')); this.registerSortableField('last_activity_at', gettext('last activity'));
this.registerSortableField('open_slots', gettext('open slots')); this.registerSortableField('open_slots', gettext('open slots'));
}, },
model: TeamModel setFilterField: function (fieldName, value) {
BaseCollection.prototype.setFilterField.call(this, fieldName, value);
this.queryParams[fieldName] = function () {
return value;
};
}
}); });
return TeamCollection; return TeamCollection;
}); });
......
...@@ -3,23 +3,25 @@ ...@@ -3,23 +3,25 @@
define(['underscore', 'gettext', 'teams/js/collections/base', 'teams/js/models/topic'], define(['underscore', 'gettext', 'teams/js/collections/base', 'teams/js/models/topic'],
function(_, gettext, BaseCollection, TopicModel) { function(_, gettext, BaseCollection, TopicModel) {
var TopicCollection = BaseCollection.extend({ var TopicCollection = BaseCollection.extend({
initialize: function(topics, options) { model: TopicModel,
var self = this; state: {
perPage: null,
sortKey: 'name'
},
BaseCollection.prototype.initialize.call(this, options); queryParams: {
course_id: function () { return this.course_id; },
text_search: function () { return this.searchString || ''; }
},
this.perPage = topics.results.length; constructor: function(topics, options) {
BaseCollection.prototype.constructor.call(this, topics, options);
this.server_api = _.extend( this.state.pageSize = topics.results.length;
this.server_api, if (topics.sort_order) {
{ this.state.sortKey = topics.sort_order;
course_id: function () { return encodeURIComponent(self.course_id); },
order_by: function () { return this.sortField; }
} }
);
delete this.server_api['sort_order']; // Sort order is not specified for the Team API
this.registerSortableField('name', gettext('name')); this.registerSortableField('name', gettext('name'));
// Translators: This refers to the number of teams (a count of how many teams there are) // Translators: This refers to the number of teams (a count of how many teams there are)
this.registerSortableField('team_count', gettext('team count')); this.registerSortableField('team_count', gettext('team count'));
...@@ -29,9 +31,7 @@ ...@@ -29,9 +31,7 @@
if (_.contains(['create', 'delete'], event.action)) { if (_.contains(['create', 'delete'], event.action)) {
this.isStale = true; this.isStale = true;
} }
}, }
model: TopicModel
}); });
return TopicCollection; return TopicCollection;
}); });
......
...@@ -21,7 +21,7 @@ define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', ...@@ -21,7 +21,7 @@ define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers',
}; };
it('sets its perPage based on initial page size', function () { it('sets its perPage based on initial page size', function () {
expect(topicCollection.perPage).toBe(5); expect(topicCollection.getPageSize()).toBe(5);
}); });
it('sorts by name', function () { it('sorts by name', function () {
......
...@@ -43,7 +43,6 @@ define([ ...@@ -43,7 +43,6 @@ define([
teams = TeamSpecHelpers.createMockTeams({ teams = TeamSpecHelpers.createMockTeams({
results: [] results: []
}, { }, {
per_page: 2,
url: TeamSpecHelpers.testContext.myTeamsUrl, url: TeamSpecHelpers.testContext.myTeamsUrl,
username: TeamSpecHelpers.testContext.userInfo.username username: TeamSpecHelpers.testContext.userInfo.username
}, MyTeamsCollection), }, MyTeamsCollection),
...@@ -60,7 +59,7 @@ define([ ...@@ -60,7 +59,7 @@ define([
username : TeamSpecHelpers.testContext.userInfo.username, username : TeamSpecHelpers.testContext.userInfo.username,
course_id : TeamSpecHelpers.testContext.courseID, course_id : TeamSpecHelpers.testContext.courseID,
page : '1', page : '1',
page_size : '2', page_size : '5',
text_search: '', text_search: '',
order_by: 'last_activity_at' order_by: 'last_activity_at'
} }
......
...@@ -263,9 +263,9 @@ define([ ...@@ -263,9 +263,9 @@ define([
// Clear the search and submit it again // Clear the search and submit it again
teamsTabView.$('.search-field').val(''); teamsTabView.$('.search-field').val('');
teamsTabView.$('.action-search').click(); teamsTabView.$('.action-search').click();
verifyTeamsRequest({ verifyTeamsRequest({
order_by: 'last_activity_at', order_by: 'last_activity_at'
text_search: ''
}); });
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1'); expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
......
...@@ -63,9 +63,11 @@ define([ ...@@ -63,9 +63,11 @@ define([
return new collectionType( return new collectionType(
createMockTeamsResponse(responseOptions), createMockTeamsResponse(responseOptions),
_.extend({ _.extend({
state: {
pageSize: 5
},
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: testCourseID, course_id: testCourseID,
per_page: 2,
parse: true parse: true
}, options) }, options)
); );
...@@ -287,6 +289,9 @@ define([ ...@@ -287,6 +289,9 @@ define([
sort_order: 'name' sort_order: 'name'
}, },
{ {
state: {
pageSize: 5
},
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: testCourseID, course_id: testCourseID,
parse: true, parse: true,
......
...@@ -3,12 +3,19 @@ ...@@ -3,12 +3,19 @@
*/ */
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['backbone', 'underscore', 'gettext', 'teams/js/views/team_discussion', define([
'backbone',
'underscore',
'gettext',
'edx-ui-toolkit/js/utils/html-utils',
'teams/js/views/team_discussion',
'common/js/components/utils/view_utils', 'common/js/components/utils/view_utils',
'teams/js/views/team_utils', 'teams/js/views/team_utils',
'text!teams/templates/team-profile.underscore', 'text!teams/templates/team-profile.underscore',
'text!teams/templates/team-member.underscore'], 'text!teams/templates/team-member.underscore'
function (Backbone, _, gettext, TeamDiscussionView, ViewUtils, TeamUtils, teamTemplate, teamMemberTemplate) { ],
function (Backbone, _, gettext, HtmlUtils, TeamDiscussionView, ViewUtils, TeamUtils,
teamTemplate, teamMemberTemplate) {
var TeamProfileView = Backbone.View.extend({ var TeamProfileView = Backbone.View.extend({
errorMessage: gettext("An error occurred. Try again."), errorMessage: gettext("An error occurred. Try again."),
...@@ -16,6 +23,7 @@ ...@@ -16,6 +23,7 @@
events: { events: {
'click .leave-team-link': 'leaveTeam' 'click .leave-team-link': 'leaveTeam'
}, },
initialize: function (options) { initialize: function (options) {
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.context = options.context; this.context = options.context;
...@@ -31,7 +39,10 @@ ...@@ -31,7 +39,10 @@
var memberships = this.model.get('membership'), var memberships = this.model.get('membership'),
discussionTopicID = this.model.get('discussion_topic_id'), discussionTopicID = this.model.get('discussion_topic_id'),
isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username); isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username);
this.$el.html(_.template(teamTemplate)({
HtmlUtils.setHtml(
this.$el,
HtmlUtils.template(teamTemplate)({
courseID: this.context.courseID, courseID: this.context.courseID,
discussionTopicID: discussionTopicID, discussionTopicID: discussionTopicID,
readOnly: !(this.context.userInfo.privileged || isMember), readOnly: !(this.context.userInfo.privileged || isMember),
...@@ -41,7 +52,8 @@ ...@@ -41,7 +52,8 @@
isMember: isMember, isMember: isMember,
hasCapacity: memberships.length < this.context.maxTeamSize, hasCapacity: memberships.length < this.context.maxTeamSize,
hasMembers: memberships.length >= 1 hasMembers: memberships.length >= 1
})); })
);
this.discussionView = new TeamDiscussionView({ this.discussionView = new TeamDiscussionView({
el: this.$('.discussion-module') el: this.$('.discussion-module')
}); });
...@@ -56,11 +68,14 @@ ...@@ -56,11 +68,14 @@
renderTeamMembers: function() { renderTeamMembers: function() {
var view = this; var view = this;
_.each(this.model.get('membership'), function(membership) { _.each(this.model.get('membership'), function(membership) {
view.$('.members-info').append(_.template(teamMemberTemplate)({ HtmlUtils.append(
view.$('.members-info'),
HtmlUtils.template(teamMemberTemplate)({
imageUrl: membership.user.profile_image.image_url_medium, imageUrl: membership.user.profile_image.image_url_medium,
username: membership.user.username, username: membership.user.username,
memberProfileUrl: '/u/' + membership.user.username memberProfileUrl: '/u/' + membership.user.username
})); })
);
}); });
}, },
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
type: 'teams', type: 'teams',
srInfo: { srInfo: {
id: "heading-browse-teams", id: 'heading-browse-teams',
text: gettext('All teams') text: gettext('All teams')
}, },
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
'jquery', 'jquery',
'underscore', 'underscore',
'gettext', 'gettext',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'common/js/components/views/search_field', 'common/js/components/views/search_field',
'js/components/header/views/header', 'js/components/header/views/header',
'js/components/header/models/header', 'js/components/header/models/header',
...@@ -25,7 +27,7 @@ ...@@ -25,7 +27,7 @@
'teams/js/views/team_utils', 'teams/js/views/team_utils',
'teams/js/views/instructor_tools', 'teams/js/views/instructor_tools',
'text!teams/templates/teams_tab.underscore'], 'text!teams/templates/teams_tab.underscore'],
function (Backbone, $, _, gettext, SearchFieldView, HeaderView, HeaderModel, function (Backbone, $, _, gettext, HtmlUtils, StringUtils, SearchFieldView, HeaderView, HeaderModel,
TopicModel, TopicCollection, TeamModel, TeamCollection, MyTeamsCollection, TeamAnalytics, TopicModel, TopicCollection, TeamModel, TeamCollection, MyTeamsCollection, TeamAnalytics,
TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView, TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamMembersEditView, TeamProfileHeaderActionsView, TeamUtils, InstructorToolsView, teamsTemplate) { TeamMembersEditView, TeamProfileHeaderActionsView, TeamUtils, InstructorToolsView, teamsTemplate) {
...@@ -44,7 +46,7 @@ ...@@ -44,7 +46,7 @@
}, },
render: function () { render: function () {
this.$el.html(_.template(teamsTemplate)); HtmlUtils.setHtml(this.$el, HtmlUtils.template(teamsTemplate)({}));
this.$('p.error').hide(); this.$('p.error').hide();
this.header.setElement(this.$('.teams-header')).render(); this.header.setElement(this.$('.teams-header')).render();
if (this.instructorTools) { if (this.instructorTools) {
...@@ -121,7 +123,7 @@ ...@@ -121,7 +123,7 @@
course_id: this.context.courseID, course_id: this.context.courseID,
parse: true parse: true
} }
).bootstrap(); );
this.topicsView = new TopicsView({ this.topicsView = new TopicsView({
router: this.router, router: this.router,
...@@ -138,12 +140,15 @@ ...@@ -138,12 +140,15 @@
url: 'my-teams', url: 'my-teams',
view: this.myTeamsView view: this.myTeamsView
}, { }, {
title: interpolate( title: HtmlUtils.interpolateHtml(
// Translators: sr_start and sr_end surround text meant only for screen readers. // Translators: sr_start and sr_end surround text meant only for screen readers.
// The whole string will be shown to users as "Browse teams" if they are using a // The whole string will be shown to users as "Browse teams" if they are using a
// screenreader, and "Browse" otherwise. // screenreader, and "Browse" otherwise.
gettext("Browse %(sr_start)s teams %(sr_end)s"), gettext('Browse {sr_start} teams {sr_end}'),
{"sr_start": '<span class="sr">', "sr_end": '</span>'}, true {
sr_start: HtmlUtils.HTML('<span class="sr">'),
sr_end: HtmlUtils.HTML('</span>')
}
), ),
url: 'browse', url: 'browse',
view: this.topicsView view: this.topicsView
...@@ -219,10 +224,9 @@ ...@@ -219,10 +224,9 @@
collection: view.teamsCollection, collection: view.teamsCollection,
breadcrumbs: view.createBreadcrumbs(topic), breadcrumbs: view.createBreadcrumbs(topic),
title: gettext('Team Search'), title: gettext('Team Search'),
description: interpolate( description: StringUtils.interpolate(
gettext('Showing results for "%(searchString)s"'), gettext('Showing results for "{searchString}"'),
{ searchString: view.teamsCollection.searchString }, {searchString: view.teamsCollection.getSearchString()}
true
), ),
showSortControls: false showSortControls: false
}); });
...@@ -336,8 +340,7 @@ ...@@ -336,8 +340,7 @@
per_page: 10 per_page: 10
}); });
view.teamsCollection = collection; view.teamsCollection = collection;
collection.goTo(1) collection.getPage(1).then(function () {
.done(function() {
var teamsView = view.createTeamsListView({ var teamsView = view.createTeamsListView({
topic: topic, topic: topic,
collection: collection, collection: collection,
...@@ -354,7 +357,6 @@ ...@@ -354,7 +357,6 @@
createTeamsListView: function(options) { createTeamsListView: function(options) {
var topic = options.topic, var topic = options.topic,
collection = options.collection, collection = options.collection,
self = this,
teamsView = new TopicTeamsView({ teamsView = new TopicTeamsView({
router: this.router, router: this.router,
context: this.context, context: this.context,
...@@ -389,7 +391,7 @@ ...@@ -389,7 +391,7 @@
// that the collection doesn't unnecessarily get refreshed again. // that the collection doesn't unnecessarily get refreshed again.
collection.isStale = false; collection.isStale = false;
if (collection.searchString) { if (collection.getSearchString()) {
Backbone.history.navigate(searchUrl, {trigger: true}); Backbone.history.navigate(searchUrl, {trigger: true});
} else if (Backbone.history.getFragment() === searchUrl) { } else if (Backbone.history.getFragment() === searchUrl) {
Backbone.history.navigate('topics/' + topic.get('id'), {trigger: true}); Backbone.history.navigate('topics/' + topic.get('id'), {trigger: true});
...@@ -591,30 +593,27 @@ ...@@ -591,30 +593,27 @@
routeNotFound: function (route) { routeNotFound: function (route) {
this.notFoundError( this.notFoundError(
interpolate( StringUtils.interpolate(
gettext('The page "%(route)s" could not be found.'), gettext('The page "{route}" could not be found.'),
{route: route}, {route: route}
true
) )
); );
}, },
topicNotFound: function (topicID) { topicNotFound: function (topicID) {
this.notFoundError( this.notFoundError(
interpolate( StringUtils.interpolate(
gettext('The topic "%(topic)s" could not be found.'), gettext('The topic "{topic}" could not be found.'),
{topic: topicID}, {topic: topicID}
true
) )
); );
}, },
teamNotFound: function (teamID) { teamNotFound: function (teamID) {
this.notFoundError( this.notFoundError(
interpolate( StringUtils.interpolate(
gettext('The team "%(team)s" could not be found.'), gettext('The team "{team}" could not be found.'),
{team: teamID}, {team: teamID}
true
) )
); );
}, },
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
type: 'topics', type: 'topics',
srInfo: { srInfo: {
id: "heading-browse-topics", id: 'heading-browse-topics',
text: gettext("All topics") text: gettext('All topics')
}, },
initialize: function (options) { initialize: function (options) {
......
<div class="team-profile"> <div class="team-profile">
<div class="page-content-main"> <div class="page-content-main">
<div class="discussion-module" data-course-id="<%= courseID %>" data-discussion-id="<%= discussionTopicID %>" <div class="discussion-module" data-course-id="<%- courseID %>" data-discussion-id="<%- discussionTopicID %>"
data-read-only="<%= readOnly %>" data-read-only="<%- readOnly %>"
data-user-create-comment="<%= !readOnly %>" data-user-create-comment="<%- !readOnly %>"
data-user-create-subcomment="<%= !readOnly %>"> data-user-create-subcomment="<%- !readOnly %>">
<% if ( !readOnly) { %> <% if (!readOnly) { %>
<button type="button" class="btn new-post-btn"><i class="icon fa fa-edit new-post-icon" aria-hidden="true"></i><%- gettext("New Post") %></button> <button type="button" class="btn new-post-btn"><i class="icon fa fa-edit new-post-icon" aria-hidden="true"></i><%- gettext("New Post") %></button>
<% } %> <% } %>
</div> </div>
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['backbone', 'common/js/components/collections/paging_collection', 'js/bookmarks/models/bookmark'], define([
function (Backbone, PagingCollection, BookmarkModel) { 'backbone',
'edx-ui-toolkit/js/pagination/paging-collection',
'js/bookmarks/models/bookmark'
], function (Backbone, PagingCollection, BookmarkModel) {
return PagingCollection.extend({ return PagingCollection.extend({
initialize: function(options) { model: BookmarkModel,
PagingCollection.prototype.initialize.call(this);
this.url = options.url; queryParams: {
this.server_api = _.extend( course_id: function () { return this.options.course_id; },
{ fields : function () { return 'display_name,path'; }
course_id: function () { return encodeURIComponent(options.course_id); },
fields : function () { return encodeURIComponent('display_name,path'); }
},
this.server_api
);
delete this.server_api.sort_order; // Sort order is not specified for the Bookmark API
}, },
model: BookmarkModel,
url: function() { url: function() {
return this.url; return this.url;
},
constructor: function (models, options) {
this.options = options;
this.url = options.url;
PagingCollection.prototype.constructor.call(this, models, options);
} }
}); });
}); });
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define(['gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment', define(['gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment', 'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/paging_header', 'common/js/components/views/paging_footer', 'common/js/components/views/paging_header', 'common/js/components/views/paging_footer',
'text!templates/bookmarks/bookmarks-list.underscore' 'text!templates/bookmarks/bookmarks-list.underscore'
], ],
function (gettext, $, _, Backbone, Logger, _moment, function (gettext, $, _, Backbone, Logger, _moment, HtmlUtils,
PagingHeaderView, PagingFooterView, BookmarksListTemplate) { PagingHeaderView, PagingFooterView, BookmarksListTemplate) {
var moment = _moment || window.moment; var moment = _moment || window.moment;
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
}, },
initialize: function (options) { initialize: function (options) {
this.template = _.template(BookmarksListTemplate); this.template = HtmlUtils.template(BookmarksListTemplate);
this.loadingMessageView = options.loadingMessageView; this.loadingMessageView = options.loadingMessageView;
this.errorMessageView = options.errorMessageView; this.errorMessageView = options.errorMessageView;
this.langCode = $(this.el).data('langCode'); this.langCode = $(this.el).data('langCode');
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
bookmarksCollection: this.collection, bookmarksCollection: this.collection,
humanFriendlyDate: this.humanFriendlyDate humanFriendlyDate: this.humanFriendlyDate
}; };
this.$el.html(this.template(data));
HtmlUtils.setHtml(this.$el, this.template(data));
this.pagingHeaderView.setElement(this.$('.paging-header')).render(); this.pagingHeaderView.setElement(this.$('.paging-header')).render();
this.pagingFooterView.setElement(this.$('.paging-footer')).render(); this.pagingFooterView.setElement(this.$('.paging-footer')).render();
this.delegateEvents(); this.delegateEvents();
...@@ -56,7 +57,7 @@ ...@@ -56,7 +57,7 @@
this.hideErrorMessage(); this.hideErrorMessage();
this.showBookmarksContainer(); this.showBookmarksContainer();
this.collection.goTo(this.defaultPage).done(function () { this.collection.getPage(this.defaultPage).done(function () {
view.render(); view.render();
view.focusBookmarksElement(); view.focusBookmarksElement();
}).fail(function () { }).fail(function () {
......
...@@ -16,13 +16,12 @@ ...@@ -16,13 +16,12 @@
}, },
initialize: function () { initialize: function () {
var bookmarksCollection = new BookmarksCollection( var bookmarksCollection = new BookmarksCollection([],
{ {
course_id: $('.courseware-results').data('courseId'), course_id: $('.courseware-results').data('courseId'),
url: $(".courseware-bookmarks-button").data('bookmarksApiUrl') url: $('.courseware-bookmarks-button').data('bookmarksApiUrl')
} }
); );
bookmarksCollection.bootstrap();
this.bookmarksListView = new BookmarksListView( this.bookmarksListView = new BookmarksListView(
{ {
collection: bookmarksCollection, collection: bookmarksCollection,
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define([ define([
'underscore', 'common/js/components/collections/paging_collection', 'js/edxnotes/models/note' 'underscore', 'edx-ui-toolkit/js/pagination/paging-collection', 'js/edxnotes/models/note'
], function (_, PagingCollection, NoteModel) { ], function (_, PagingCollection, NoteModel) {
return PagingCollection.extend({ return PagingCollection.extend({
model: NoteModel, model: NoteModel,
initialize: function(models, options) { state: {
PagingCollection.prototype.initialize.call(this); pageSize: 10
},
queryParams: {},
constructor: function (models, options) {
this.url = options.url; this.url = options.url;
this.perPage = options.perPage; this.state.pageSize = options.perPage;
this.server_api = _.pick(this.server_api, "page", "page_size");
if (options.text) { if (options.text) {
this.server_api.text = options.text; this.queryParams.text = options.text;
} }
PagingCollection.prototype.constructor.call(this, models, options);
}, },
/** /**
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'jquery', 'underscore', 'backbone', 'gettext', 'js/edxnotes/utils/logger', 'jquery',
'underscore',
'backbone',
'gettext',
'edx-ui-toolkit/js/utils/html-utils',
'js/edxnotes/utils/logger',
'js/edxnotes/collections/notes' 'js/edxnotes/collections/notes'
], function ($, _, Backbone, gettext, NotesLogger, NotesCollection) { ], function ($, _, Backbone, gettext, HtmlUtils, NotesLogger, NotesCollection) {
var SearchBoxView = Backbone.View.extend({ var SearchBoxView = Backbone.View.extend({
events: { events: {
'submit': 'submitHandler' 'submit': 'submitHandler'
}, },
errorMessage: gettext('An error has occurred. Make sure that you are connected to the Internet, and then try refreshing the page.'), errorMessage: gettext('An error has occurred. Make sure that you are connected to the Internet, and then try refreshing the page.'),
emptyFieldMessage: (function () {
var message = gettext('Please enter a term in the %(anchor_start)s search field%(anchor_end)s.'); emptyFieldMessageHtml: (function () {
return interpolate(message, { var message = gettext('Please enter a term in the {anchorStart} search field{anchorEnd}.');
'anchor_start': '<a href="#search-notes-input">', return HtmlUtils.interpolateHtml(message, {
'anchor_end': '</a>' anchorStart: HtmlUtils.HTML('<a href="#search-notes-input">'),
}, true); anchorEnd: HtmlUtils.HTML('</a>')
} ()), });
}()),
initialize: function (options) { initialize: function (options) {
_.bindAll(this, 'onSuccess', 'onError', 'onComplete'); _.bindAll(this, 'onSuccess', 'onError', 'onComplete');
...@@ -91,7 +97,7 @@ define([ ...@@ -91,7 +97,7 @@ define([
validateField: function (searchQuery) { validateField: function (searchQuery) {
if (!($.trim(searchQuery))) { if (!($.trim(searchQuery))) {
this.options.error(this.emptyFieldMessage, searchQuery); this.options.error(this.emptyFieldMessageHtml, searchQuery);
return false; return false;
} }
return true; return true;
...@@ -102,7 +108,7 @@ define([ ...@@ -102,7 +108,7 @@ define([
if (args) { if (args) {
this.options.search.apply(this, args); this.options.search.apply(this, args);
this.logger.emit('edx.course.student_notes.searched', { this.logger.emit('edx.course.student_notes.searched', {
'number_of_results': args[0].totalCount, 'number_of_results': args[0].getTotalRecords(),
'search_string': args[1] 'search_string': args[1]
}); });
} else { } else {
...@@ -147,15 +153,13 @@ define([ ...@@ -147,15 +153,13 @@ define([
* @return {jQuery.Deferred} * @return {jQuery.Deferred}
*/ */
sendRequest: function (text) { sendRequest: function (text) {
this.collection = new NotesCollection( this.collection = new NotesCollection([], {
[],
{
text: text, text: text,
perPage: this.options.perPage, perPage: this.options.perPage,
url: this.el.action url: this.el.action
} });
);
return this.collection.goTo(1); return this.collection.getPage(1);
} }
}); });
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'jquery', 'underscore', 'backbone', 'js/edxnotes/models/tab' 'jquery',
], function ($, _, Backbone, TabModel) { 'underscore',
'backbone',
'edx-ui-toolkit/js/utils/html-utils',
'js/edxnotes/models/tab'
], function ($, _, Backbone, HtmlUtils, TabModel) {
var TabView = Backbone.View.extend({ var TabView = Backbone.View.extend({
PanelConstructor: null, PanelConstructor: null,
...@@ -121,11 +125,11 @@ define([ ...@@ -121,11 +125,11 @@ define([
/** /**
* Shows error message. * Shows error message.
*/ */
showErrorMessage: function (message) { showErrorMessageHtml: function (htmlMessage) {
this.$('.wrapper-msg') var $wrapper = this.$('.wrapper-msg');
.removeClass('is-hidden') $wrapper.removeClass('is-hidden');
.find('.msg-content .copy').html(message);
HtmlUtils.setHtml($wrapper.find('.msg-content .copy'), htmlMessage);
return this; return this;
}, },
......
...@@ -142,7 +142,7 @@ define([ ...@@ -142,7 +142,7 @@ define([
}, },
onSearchError: function (errorMessage) { onSearchError: function (errorMessage) {
this.showErrorMessage(errorMessage); this.showErrorMessageHtml(errorMessage);
if (this.searchDeferred) { if (this.searchDeferred) {
this.searchDeferred.reject(); this.searchDeferred.reject();
} }
......
...@@ -186,7 +186,12 @@ define(['backbone', ...@@ -186,7 +186,12 @@ define(['backbone',
it("calls bookmarks list render on page_changed event", function () { it("calls bookmarks list render on page_changed event", function () {
var renderSpy = spyOn(BookmarksListView.prototype, 'render'); var renderSpy = spyOn(BookmarksListView.prototype, 'render');
var listView = new BookmarksListView({collection: new BookmarksCollection({course_id: 'abc'})}); var listView = new BookmarksListView({
collection: new BookmarksCollection([], {
course_id: 'abc',
url: '/test-bookmarks/url/'
})
});
listView.collection.trigger('page_changed'); listView.collection.trigger('page_changed');
expect(renderSpy).toHaveBeenCalled(); expect(renderSpy).toHaveBeenCalled();
}); });
......
define([ define([
'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'js/edxnotes/views/search_box', 'jquery',
'js/edxnotes/collections/notes', 'js/spec/edxnotes/helpers' 'underscore',
], function($, _, AjaxHelpers, SearchBoxView, NotesCollection, Helpers) { 'common/js/spec_helpers/ajax_helpers',
'edx-ui-toolkit/js/utils/html-utils',
'js/edxnotes/views/search_box',
'js/edxnotes/collections/notes',
'js/spec/edxnotes/helpers'
], function($, _, AjaxHelpers, HtmlUtils, SearchBoxView, NotesCollection, Helpers) {
'use strict'; 'use strict';
describe('EdxNotes SearchBoxView', function() { describe('EdxNotes SearchBoxView', function() {
var getSearchBox, submitForm, assertBoxIsEnabled, assertBoxIsDisabled, searchResponse; var getSearchBox, submitForm, assertBoxIsEnabled, assertBoxIsDisabled, searchResponse;
...@@ -159,7 +164,7 @@ define([ ...@@ -159,7 +164,7 @@ define([
expect(requests).toHaveLength(0); expect(requests).toHaveLength(0);
assertBoxIsEnabled(this.searchBox); assertBoxIsEnabled(this.searchBox);
expect(this.searchBox.options.error).toHaveBeenCalledWith( expect(this.searchBox.options.error).toHaveBeenCalledWith(
'Please enter a term in the <a href="#search-notes-input"> search field</a>.', HtmlUtils.HTML('Please enter a term in the <a href="#search-notes-input"> search field</a>.'),
' ' ' '
); );
}); });
......
define([ define([
'jquery', 'backbone', 'common/js/spec_helpers/template_helpers', 'js/edxnotes/collections/tabs', 'jquery',
'js/edxnotes/views/tabs_list', 'js/edxnotes/views/tab_view' 'backbone',
], function( 'edx-ui-toolkit/js/utils/html-utils',
$, Backbone, TemplateHelpers, TabsCollection, TabsListView, TabView 'common/js/spec_helpers/template_helpers',
) { 'js/edxnotes/collections/tabs',
'js/edxnotes/views/tabs_list',
'js/edxnotes/views/tab_view'
], function ($, Backbone, HtmlUtils, TemplateHelpers, TabsCollection, TabsListView, TabView) {
'use strict'; 'use strict';
describe('EdxNotes TabView', function() { describe('EdxNotes TabView', function() {
var TestSubView = Backbone.View.extend({ var TestSubView = Backbone.View.extend({
...@@ -94,7 +97,8 @@ define([ ...@@ -94,7 +97,8 @@ define([
it('can show/hide error messages', function () { it('can show/hide error messages', function () {
var view = getView(this.tabsCollection), var view = getView(this.tabsCollection),
errorHolder = view.$('.wrapper-msg'); errorHolder = view.$('.wrapper-msg');
view.showErrorMessage('<p>error message is here</p>');
view.showErrorMessageHtml(HtmlUtils.HTML('<p>error message is here</p>'));
expect(errorHolder).not.toHaveClass('is-hidden'); expect(errorHolder).not.toHaveClass('is-hidden');
expect(errorHolder.find('.copy')).toContainHtml('<p>error message is here</p>'); expect(errorHolder.find('.copy')).toContainHtml('<p>error message is here</p>');
...@@ -106,7 +110,7 @@ define([ ...@@ -106,7 +110,7 @@ define([
it('should hide error messages before rendering', function () { it('should hide error messages before rendering', function () {
var view = getView(this.tabsCollection), var view = getView(this.tabsCollection),
errorHolder = view.$('.wrapper-msg'); errorHolder = view.$('.wrapper-msg');
view.showErrorMessage('<p>error message is here</p>'); view.showErrorMessageHtml('<p>error message is here</p>');
view.render(); view.render();
expect(errorHolder).toHaveClass('is-hidden'); expect(errorHolder).toHaveClass('is-hidden');
expect(errorHolder.find('.copy')).toBeEmpty(); expect(errorHolder.find('.copy')).toBeEmpty();
......
...@@ -70,6 +70,7 @@ define([ ...@@ -70,6 +70,7 @@ define([
]); ]);
this.collection = new NotesCollection(notes, {perPage: 10, parse: true}); this.collection = new NotesCollection(notes, {perPage: 10, parse: true});
this.collection.url = '/test/notes/';
this.tabsCollection = new TabsCollection(); this.tabsCollection = new TabsCollection();
}); });
...@@ -79,7 +80,7 @@ define([ ...@@ -79,7 +80,7 @@ define([
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes); Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes);
}); });
it("will not render header and footer if there are no notes", function () { it('will not render header and footer if there are no notes', function () {
var notes = { var notes = {
'count': 0, 'count': 0,
'current_page': 1, 'current_page': 1,
...@@ -95,7 +96,7 @@ define([ ...@@ -95,7 +96,7 @@ define([
expect(view.$('.pagination.pagination-full.bottom')).toHaveLength(0); expect(view.$('.pagination.pagination-full.bottom')).toHaveLength(0);
}); });
it("can go to a page number", function () { it('can go to a page number', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var notes = Helpers.createNotesData( var notes = Helpers.createNotesData(
{ {
...@@ -108,6 +109,7 @@ define([ ...@@ -108,6 +109,7 @@ define([
); );
var collection = new NotesCollection(notes, {perPage: 10, parse: true}); var collection = new NotesCollection(notes, {perPage: 10, parse: true});
collection.url = '/test/notes/';
var view = getView(collection, this.tabsCollection); var view = getView(collection, this.tabsCollection);
Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 12 total", false, 1, 2); Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 12 total", false, 1, 2);
...@@ -134,7 +136,7 @@ define([ ...@@ -134,7 +136,7 @@ define([
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes); Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes);
}); });
it("can navigate forward and backward", function () { it('can navigate forward and backward', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var page1Notes = Helpers.createNotesData( var page1Notes = Helpers.createNotesData(
{ {
...@@ -146,6 +148,7 @@ define([ ...@@ -146,6 +148,7 @@ define([
} }
); );
var collection = new NotesCollection(page1Notes, {perPage: 10, parse: true}); var collection = new NotesCollection(page1Notes, {perPage: 10, parse: true});
collection.url = '/test/notes/';
var view = getView(collection, this.tabsCollection); var view = getView(collection, this.tabsCollection);
Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 15 total", false, 1, 2); Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 15 total", false, 1, 2);
...@@ -180,7 +183,7 @@ define([ ...@@ -180,7 +183,7 @@ define([
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, page1Notes); Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, page1Notes);
}); });
it("sends correct page size value", function () { it('sends correct page size value', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var notes = Helpers.createNotesData( var notes = Helpers.createNotesData(
{ {
...@@ -192,6 +195,7 @@ define([ ...@@ -192,6 +195,7 @@ define([
} }
); );
var collection = new NotesCollection(notes, {perPage: 5, parse: true}); var collection = new NotesCollection(notes, {perPage: 5, parse: true});
collection.url = '/test/notes/';
var view = getView(collection, this.tabsCollection); var view = getView(collection, this.tabsCollection);
view.$('.pagination .next-page-link').click(); view.$('.pagination .next-page-link').click();
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
'underscore.string': 'common/js/vendor/underscore.string', 'underscore.string': 'common/js/vendor/underscore.string',
'backbone': 'common/js/vendor/backbone', 'backbone': 'common/js/vendor/backbone',
'backbone.associations': 'xmodule_js/common_static/js/vendor/backbone-associations-min', 'backbone.associations': 'xmodule_js/common_static/js/vendor/backbone-associations-min',
'backbone.paginator': 'xmodule_js/common_static/js/vendor/backbone.paginator.min', 'backbone.paginator': 'common/js/vendor/backbone.paginator',
'backbone-super': 'js/vendor/backbone-super', 'backbone-super': 'js/vendor/backbone-super',
'URI': 'xmodule_js/common_static/js/vendor/URI.min', 'URI': 'xmodule_js/common_static/js/vendor/URI.min',
'tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min', 'tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min',
...@@ -202,11 +202,14 @@ ...@@ -202,11 +202,14 @@
}, },
'backbone.paginator': { 'backbone.paginator': {
deps: ['backbone'], deps: ['backbone'],
exports: 'Backbone.Paginator' exports: 'Backbone.PageableCollection'
}, },
"backbone-super": { "backbone-super": {
deps: ["backbone"] deps: ["backbone"]
}, },
'paging-collection': {
deps: ['jquery', 'underscore', 'backbone.paginator']
},
'youtube': { 'youtube': {
exports: 'YT' exports: 'YT'
}, },
......
define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_helpers', define([
'backbone',
'jquery',
'underscore',
'URI',
'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/spec_helpers/ajax_helpers',
'js/spec/student_profile/helpers', 'js/spec/student_profile/helpers',
'js/student_profile/views/badge_list_container', 'js/student_profile/views/badge_list_container'
'common/js/components/collections/paging_collection'
], ],
function (Backbone, $, _, URI, AjaxHelpers, LearnerProfileHelpers, BadgeListContainer, PagingCollection) { function (Backbone, $, _, URI, PagingCollection, AjaxHelpers, LearnerProfileHelpers, BadgeListContainer) {
'use strict'; 'use strict';
describe('edx.user.BadgeListContainer', function () { describe('edx.user.BadgeListContainer', function () {
var view, requests; var view, requests;
var createView = function (requests, badge_list_object) { var createView = function (requests, pageNum, badge_list_object) {
var badgeCollection = new PagingCollection(); var BadgeCollection = PagingCollection.extend({
queryParams: {
currentPage: 'current_page'
}
});
var badgeCollection = new BadgeCollection();
badgeCollection.url = '/api/badges/v1/assertions/user/staff/'; badgeCollection.url = '/api/badges/v1/assertions/user/staff/';
var models = []; var models = [];
_.each(_.range(badge_list_object.count), function (idx) { _.each(_.range(badge_list_object.count), function (idx) {
models.push(LearnerProfileHelpers.makeBadge(idx)); models.push(LearnerProfileHelpers.makeBadge(idx));
}); });
badge_list_object.results = models; badge_list_object.results = models;
badgeCollection.fetch(); badgeCollection.setPage(pageNum);
var request = AjaxHelpers.currentRequest(requests); var request = AjaxHelpers.currentRequest(requests);
var path = new URI(request.url).path(); var path = new URI(request.url).path();
expect(path).toBe('/api/badges/v1/assertions/user/staff/'); expect(path).toBe('/api/badges/v1/assertions/user/staff/');
AjaxHelpers.respondWithJson(requests, badge_list_object); AjaxHelpers.respondWithJson(requests, badge_list_object);
var badge_list_container = new BadgeListContainer({ var badgeListContainer = new BadgeListContainer({
'collection': badgeCollection 'collection': badgeCollection
}); });
badge_list_container.render(); badgeListContainer.render();
return badge_list_container; return badgeListContainer;
}; };
afterEach(function () { afterEach(function () {
...@@ -36,7 +46,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_ ...@@ -36,7 +46,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_
it('displays all badges', function () { it('displays all badges', function () {
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
view = createView(requests, { view = createView(requests, 1, {
count: 30, count: 30,
previous: '/arbitrary/url', previous: '/arbitrary/url',
num_pages: 3, num_pages: 3,
...@@ -51,7 +61,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_ ...@@ -51,7 +61,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_
it('displays placeholder on last page', function () { it('displays placeholder on last page', function () {
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
view = createView(requests, { view = createView(requests, 3, {
count: 30, count: 30,
previous: '/arbitrary/url', previous: '/arbitrary/url',
num_pages: 3, num_pages: 3,
...@@ -66,7 +76,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_ ...@@ -66,7 +76,7 @@ define(['backbone', 'jquery', 'underscore', 'URI', 'common/js/spec_helpers/ajax_
it('does not display placeholder on first page', function () { it('does not display placeholder on first page', function () {
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
view = createView(requests, { view = createView(requests, 1, {
count: 30, count: 30,
previous: '/arbitrary/url', previous: '/arbitrary/url',
num_pages: 3, num_pages: 3,
......
define(['backbone', 'jquery', 'underscore', define([
'backbone',
'jquery',
'underscore',
'edx-ui-toolkit/js/pagination/paging-collection',
'js/spec/student_profile/helpers', 'js/spec/student_profile/helpers',
'js/student_profile/views/badge_list_view', 'js/student_profile/views/badge_list_view'
'common/js/components/collections/paging_collection'
], ],
function (Backbone, $, _, LearnerProfileHelpers, BadgeListView, PagingCollection) { function (Backbone, $, _, PagingCollection, LearnerProfileHelpers, BadgeListView) {
"use strict"; "use strict";
describe("edx.user.BadgeListView", function () { describe("edx.user.BadgeListView", function () {
......
...@@ -49,7 +49,6 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -49,7 +49,6 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
}; };
it("renders the full profile for a user", function() { it("renders the full profile for a user", function() {
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
var context = createProfilePage(true), var context = createProfilePage(true),
......
define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/template_helpers', define(['backbone',
'jquery',
'underscore',
'edx-ui-toolkit/js/pagination/paging-collection',
'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/template_helpers',
'js/spec/student_account/helpers', 'js/spec/student_account/helpers',
'js/spec/student_profile/helpers', 'js/spec/student_profile/helpers',
'js/views/fields', 'js/views/fields',
...@@ -8,12 +13,11 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -8,12 +13,11 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
'js/student_profile/views/learner_profile_view', 'js/student_profile/views/learner_profile_view',
'js/student_profile/views/badge_list_container', 'js/student_profile/views/badge_list_container',
'js/student_account/views/account_settings_fields', 'js/student_account/views/account_settings_fields',
'common/js/components/collections/paging_collection',
'js/views/message_banner' 'js/views/message_banner'
], ],
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews, function (Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers,
UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView, FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
BadgeListContainer, AccountSettingsFieldViews, PagingCollection, MessageBannerView) { BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) {
'use strict'; 'use strict';
describe("edx.user.LearnerProfileView", function () { describe("edx.user.LearnerProfileView", function () {
......
...@@ -6,18 +6,24 @@ ...@@ -6,18 +6,24 @@
'text!templates/student_profile/badge_list.underscore'], 'text!templates/student_profile/badge_list.underscore'],
function (gettext, $, _, PaginatedView, BadgeView, BadgeListView, BadgeListTemplate) { function (gettext, $, _, PaginatedView, BadgeView, BadgeListView, BadgeListTemplate) {
var BadgeListContainer = PaginatedView.extend({ var BadgeListContainer = PaginatedView.extend({
initialize: function (options) {
BadgeListContainer.__super__.initialize.call(this, options);
this.listView.find_courses_url = options.find_courses_url;
this.listView.badgeMeta = options.badgeMeta;
this.listView.ownProfile = options.ownProfile;
},
type: 'badge', type: 'badge',
itemViewClass: BadgeView, itemViewClass: BadgeView,
listViewClass: BadgeListView, listViewClass: BadgeListView,
viewTemplate: BadgeListTemplate, viewTemplate: BadgeListTemplate,
isZeroIndexed: true, isZeroIndexed: true,
paginationLabel: gettext("Accomplishments Pagination")
paginationLabel: gettext('Accomplishments Pagination'),
initialize: function (options) {
BadgeListContainer.__super__.initialize.call(this, options);
this.listView.find_courses_url = options.find_courses_url;
this.listView.badgeMeta = options.badgeMeta;
this.listView.ownProfile = options.ownProfile;
}
}); });
return BadgeListContainer; return BadgeListContainer;
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'common/js/components/views/list', 'js/student_profile/views/badge_view', 'gettext',
'text!templates/student_profile/badge_placeholder.underscore'], 'jquery',
function (gettext, $, _, ListView, BadgeView, badgePlaceholder) { 'underscore',
'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/list',
'js/student_profile/views/badge_view',
'text!templates/student_profile/badge_placeholder.underscore'
],
function (gettext, $, _, HtmlUtils, ListView, BadgeView, badgePlaceholder) {
var BadgeListView = ListView.extend({ var BadgeListView = ListView.extend({
tagName: 'div', tagName: 'div',
template: _.template(badgePlaceholder),
template: HtmlUtils.template(badgePlaceholder),
renderCollection: function () { renderCollection: function () {
var self = this,
$row;
this.$el.empty(); this.$el.empty();
var self = this;
var row;
// Split into two columns. // Split into two columns.
this.collection.each(function (badge, index) { this.collection.each(function (badge, index) {
if (index % 2 === 0) { if (index % 2 === 0) {
row = $('<div class="row">'); $row = $('<div class="row">');
this.$el.append(row); this.$el.append($row);
} }
var item = new BadgeView({ var $item = new BadgeView({
model: badge, model: badge,
badgeMeta: this.badgeMeta, badgeMeta: this.badgeMeta,
ownProfile: this.ownProfile ownProfile: this.ownProfile
}).render().el; }).render().el;
row.append(item);
this.itemViews.push(item); if ($row) {
$row.append($item);
}
this.itemViews.push($item);
}, this); }, this);
// Placeholder must always be at the end, and may need a new row. // Placeholder must always be at the end, and may need a new row.
if (!this.collection.hasNextPage()) { if (!this.collection.hasNextPage()) {
// find_courses_url set by BadgeListContainer during initialization. // find_courses_url set by BadgeListContainer during initialization.
var placeholder = this.template({find_courses_url: self.find_courses_url});
if (this.collection.length % 2 === 0) { if (this.collection.length % 2 === 0) {
row = $('<div class="row">'); $row = $('<div class="row">');
this.$el.append(row); this.$el.append($row);
}
if ($row) {
HtmlUtils.append(
$row,
this.template({find_courses_url: self.find_courses_url})
);
} }
row.append(placeholder);
} }
return this; return this;
} }
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'backbone', 'logger', 'gettext',
'jquery',
'underscore',
'backbone',
'logger',
'edx-ui-toolkit/js/pagination/paging-collection',
'js/student_account/models/user_account_model', 'js/student_account/models/user_account_model',
'js/student_account/models/user_preferences_model', 'js/student_account/models/user_preferences_model',
'js/views/fields', 'js/views/fields',
'js/student_profile/views/learner_profile_fields', 'js/student_profile/views/learner_profile_fields',
'js/student_profile/views/learner_profile_view', 'js/student_profile/views/learner_profile_view',
'js/student_profile/models/badges_model', 'js/student_profile/models/badges_model',
'common/js/components/collections/paging_collection',
'js/student_profile/views/badge_list_container', 'js/student_profile/views/badge_list_container',
'js/student_account/views/account_settings_fields', 'js/student_account/views/account_settings_fields',
'js/views/message_banner', 'js/views/message_banner',
'string_utils' 'string_utils'
], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView, ], function (gettext, $, _, Backbone, Logger, PagingCollection, AccountSettingsModel, AccountPreferencesModel,
LearnerProfileFieldsView, LearnerProfileView, BadgeModel, PagingCollection, BadgeListContainer, FieldsView, LearnerProfileFieldsView, LearnerProfileView, BadgeModel, BadgeListContainer,
AccountSettingsFieldViews, MessageBannerView) { AccountSettingsFieldViews, MessageBannerView) {
return function (options) { return function (options) {
...@@ -64,7 +68,7 @@ ...@@ -64,7 +68,7 @@
var profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({ var profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({
model: accountSettingsModel, model: accountSettingsModel,
valueAttribute: "profile_image", valueAttribute: 'profile_image',
editable: editable === 'toggle', editable: editable === 'toggle',
messageView: messageView, messageView: messageView,
imageMaxBytes: options['profile_image_max_bytes'], imageMaxBytes: options['profile_image_max_bytes'],
...@@ -125,7 +129,12 @@ ...@@ -125,7 +129,12 @@
}) })
]; ];
var badgeCollection = new PagingCollection(); var BadgeCollection = PagingCollection.extend({
queryParams: {
currentPage: 'current_page'
}
});
var badgeCollection = new BadgeCollection();
badgeCollection.url = options.badges_api_url; badgeCollection.url = options.badges_api_url;
var badgeListContainer = new BadgeListContainer({ var badgeListContainer = new BadgeListContainer({
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
"logger": "js/src/logger", "logger": "js/src/logger",
"backbone": "common/js/vendor/backbone", "backbone": "common/js/vendor/backbone",
"backbone-super": "js/vendor/backbone-super", "backbone-super": "js/vendor/backbone-super",
"backbone.paginator": "js/vendor/backbone.paginator.min", "backbone.paginator": "common/js/vendor/backbone.paginator",
"underscore": "common/js/vendor/underscore", "underscore": "common/js/vendor/underscore",
"underscore.string": "common/js/vendor/underscore.string", "underscore.string": "common/js/vendor/underscore.string",
// The jquery-migrate library was added in upgrading from // The jquery-migrate library was added in upgrading from
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
}, },
"backbone.paginator": { "backbone.paginator": {
deps: ["backbone"], deps: ["backbone"],
exports: "Backbone.Paginator" exports: "Backbone.PageableCollection"
}, },
"backbone-super": { "backbone-super": {
deps: ["backbone"] deps: ["backbone"]
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"backbone": "~1.3.2", "backbone": "~1.3.2",
"coffee-script": "1.6.1", "coffee-script": "1.6.1",
"edx-pattern-library": "~0.12.4", "edx-pattern-library": "~0.12.4",
"edx-ui-toolkit": "~0.9.1", "edx-ui-toolkit": "~1.1.0",
"jquery": "~2.2.0", "jquery": "~2.2.0",
"jquery-migrate": "^1.4.1", "jquery-migrate": "^1.4.1",
"jquery.scrollto": "~2.1.2", "jquery.scrollto": "~2.1.2",
......
...@@ -52,6 +52,7 @@ NPM_INSTALLED_LIBRARIES = [ ...@@ -52,6 +52,7 @@ NPM_INSTALLED_LIBRARIES = [
'underscore.string/dist/underscore.string.js', 'underscore.string/dist/underscore.string.js',
'picturefill/dist/picturefill.js', 'picturefill/dist/picturefill.js',
'backbone/backbone.js', 'backbone/backbone.js',
'edx-ui-toolkit/node_modules/backbone.paginator/lib/backbone.paginator.js',
] ]
# Directory to install static vendor files # Directory to install static vendor files
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment