Commit f9c45586 by Andy Armstrong

Add pagination to Studio's Files and Uploads page

These changes implement STUD-813. The commit consists of the
following logical changes:
 - a REST API has been implemented for a course's assets
 - the page itself now fetches the assets client-side
 - the Backbone.Paginator library is used to support pagination
 - the AssetCollection has been refactored to extend
   Backbone.Paginator.requestPager so that it can be paged
 - an abstract PagingView class has been added to generalize
   the communication with a paging REST API
 - the AssetsView has been reimplemented to extend PagingView
 - two new child views have been added:
   - PagingHeader: the paging controls above the list of assets
   - PagingFooter: the paging controls below the assets
parent 5dccff51
......@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Studio: Added pagination to the Files & Uploads page.
Blades: Video player improvements:
- Disable edX controls on iPhone/iPod (native controls are used).
- Disable unsupported controls (volume, playback rate) on iPad/Android.
......
......@@ -41,7 +41,7 @@ class AssetsTestCase(CourseTestCase):
class AssetsToyCourseTestCase(CourseTestCase):
"""
Tests the assets returned from assets_handler (full page content) for the toy test course.
Tests the assets returned from assets_handler for the toy test course.
"""
def test_toy_assets(self):
module_store = modulestore('direct')
......@@ -56,10 +56,17 @@ class AssetsToyCourseTestCase(CourseTestCase):
location = loc_mapper().translate_location(course.location.course_id, course.location, False, True)
url = location.url_reverse('assets/', '')
resp = self.client.get(url, HTTP_ACCEPT='text/html')
# Test a small portion of the asset data passed to the client.
self.assertContains(resp, "new AssetCollection([{")
self.assertContains(resp, "/c4x/edX/toy/asset/handouts_sample_handout.txt")
self.assert_correct_asset_response(url, 0, 3, 3)
self.assert_correct_asset_response(url + "?page_size=2", 0, 2, 3)
self.assert_correct_asset_response(url + "?page_size=2&page=1", 2, 1, 3)
def assert_correct_asset_response(self, url, expected_start, expected_length, expected_total):
resp = self.client.get(url, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content)
assets = json_response['assets']
self.assertEquals(json_response['start'], expected_start)
self.assertEquals(len(assets), expected_length)
self.assertEquals(json_response['totalCount'], expected_total)
class UploadTestCase(CourseTestCase):
......@@ -82,10 +89,6 @@ class UploadTestCase(CourseTestCase):
resp = self.client.post(self.url, {"name": "file.txt"}, "application/json")
self.assertEquals(resp.status_code, 400)
def test_get(self):
with self.assertRaises(NotImplementedError):
self.client.get(self.url)
class AssetToJsonTestCase(TestCase):
"""
......@@ -163,80 +166,3 @@ class LockAssetTestCase(CourseTestCase):
resp_asset = post_asset_update(False)
self.assertFalse(resp_asset['locked'])
verify_asset_locked_state(False)
class TestAssetIndex(CourseTestCase):
"""
Test getting asset lists via http (Note, the assets don't actually exist)
"""
def setUp(self):
"""
Create fake asset entries for the other tests to use
"""
super(TestAssetIndex, self).setUp()
self.entry_filter = self.create_asset_entries(contentstore(), 100)
location = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
self.url = location.url_reverse('assets/', '')
def tearDown(self):
"""
Get rid of the entries
"""
contentstore().fs_files.remove(self.entry_filter)
def create_asset_entries(self, cstore, number):
"""
Create the fake entries
"""
course_filter = Location(
XASSET_LOCATION_TAG, category='asset', course=self.course.location.course, org=self.course.location.org
)
# purge existing entries (a bit brutal but hopefully tests are independent enuf to not trip on this)
cstore.fs_files.remove(location_to_query(course_filter))
base_entry = {
'displayname': 'foo.jpg',
'chunkSize': 262144,
'length': 0,
'uploadDate': datetime(2012, 1, 2, 0, 0),
'contentType': 'image/jpeg',
}
for i in range(number):
base_entry['displayname'] = '{:03x}.jpeg'.format(i)
base_entry['uploadDate'] += timedelta(hours=i)
base_entry['_id'] = course_filter.replace(name=base_entry['displayname']).dict()
cstore.fs_files.insert(base_entry)
return course_filter.dict()
ASSET_LIST_RE = re.compile(r'AssetCollection\((.*)\);$', re.MULTILINE)
def check_page_content(self, resp_content, entry_count, last_date=None):
"""
:param entry_count:
:param last_date:
"""
match = self.ASSET_LIST_RE.search(resp_content)
asset_list = json.loads(match.group(1))
self.assertEqual(len(asset_list), entry_count)
for row in asset_list:
datetext = row['date_added']
parsed_date = datetime.strptime(datetext, "%b %d, %Y at %H:%M UTC")
if last_date is None:
last_date = parsed_date
else:
self.assertGreaterEqual(last_date, parsed_date)
return last_date
def test_query_assets(self):
"""
The actual test
"""
# get all
resp = self.client.get(self.url, HTTP_ACCEPT='text/html')
self.check_page_content(resp.content, 100)
# get first page of 10
resp = self.client.get(self.url + "?max=10", HTTP_ACCEPT='text/html')
last_date = self.check_page_content(resp.content, 10)
# get next of 20
resp = self.client.get(self.url + "?start=10&max=20", HTTP_ACCEPT='text/html')
self.check_page_content(resp.content, 20, last_date)
......@@ -176,7 +176,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
Lock an arbitrary asset in the course
:param course_location:
"""
course_assets = content_store.get_all_content_for_course(course_location)
course_assets,__ = content_store.get_all_content_for_course(course_location)
self.assertGreater(len(course_assets), 0, "No assets to lock")
content_store.set_attr(course_assets[0]['_id'], 'locked', True)
return course_assets[0]['_id']
......@@ -585,7 +585,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertIsNotNone(course)
# make sure we have some assets in our contentstore
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,__ = content_store.get_all_content_for_course(course_location)
self.assertGreater(len(all_assets), 0)
# make sure we have some thumbnails in our contentstore
......@@ -698,7 +698,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# make sure there's something in the trashcan
course_location = CourseDescriptor.id_to_location('edX/toy/6.002_Spring_2012')
all_assets = trash_store.get_all_content_for_course(course_location)
all_assets,__ = trash_store.get_all_content_for_course(course_location)
self.assertGreater(len(all_assets), 0)
# make sure we have some thumbnails in our trashcan
......@@ -713,8 +713,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
empty_asset_trashcan([course_location])
# make sure trashcan is empty
all_assets = trash_store.get_all_content_for_course(course_location)
all_assets,count = trash_store.get_all_content_for_course(course_location)
self.assertEqual(len(all_assets), 0)
self.assertEqual(count, 0)
all_thumbnails = trash_store.get_all_content_thumbnails_for_course(course_location)
self.assertEqual(len(all_thumbnails), 0)
......@@ -923,8 +924,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertEqual(len(items), 0)
# assert that all content in the asset library is also deleted
assets = content_store.get_all_content_for_course(location)
assets,count = content_store.get_all_content_for_course(location)
self.assertEqual(len(assets), 0)
self.assertEqual(count, 0)
def verify_content_existence(self, store, root_dir, location, dirname, category_name, filename_suffix=''):
filesystem = OSFS(root_dir / 'test_export')
......
......@@ -84,9 +84,10 @@ class ContentStoreImportNoStaticTest(ModuleStoreTestCase):
_, content_store, course, course_location = self.load_test_import_course()
# make sure we have ONE asset in our contentstore ("should_be_imported.html")
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,count = content_store.get_all_content_for_course(course_location)
print "len(all_assets)=%d" % len(all_assets)
self.assertEqual(len(all_assets), 1)
self.assertEqual(count, 1)
content = None
try:
......@@ -114,9 +115,10 @@ class ContentStoreImportNoStaticTest(ModuleStoreTestCase):
module_store.get_item(course_location)
# make sure we have NO assets in our contentstore
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,count = content_store.get_all_content_for_course(course_location)
print "len(all_assets)=%d" % len(all_assets)
self.assertEqual(len(all_assets), 0)
self.assertEqual(count, 0)
def test_no_static_link_rewrites_on_import(self):
module_store = modulestore('direct')
......
......@@ -41,9 +41,10 @@ def assets_handler(request, tag=None, package_id=None, branch=None, version_guid
deleting assets, and changing the "locked" state of an asset.
GET
html: return html page of all course assets (note though that a range of assets can be requested using start
and max query parameters)
json: not currently supported
html: return html page which will show all course assets. Note that only the asset container
is returned and that the actual assets are filled in with a client-side request.
json: returns a page of assets. A page parameter specifies the desired page, and the
optional page_size parameter indicates the number of items per page (defaults to 50).
POST
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
PUT
......@@ -55,9 +56,10 @@ def assets_handler(request, tag=None, package_id=None, branch=None, version_guid
if not has_access(request.user, location):
raise PermissionDenied()
if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
response_format = request.REQUEST.get('format', 'html')
if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
if request.method == 'GET':
raise NotImplementedError('coming soon')
return _assets_json(request, location)
else:
return _update_asset(request, location, asset_id)
elif request.method == 'GET': # assume html
......@@ -73,22 +75,32 @@ def _asset_index(request, location):
Supports start (0-based index into the list of assets) and max query parameters.
"""
old_location = loc_mapper().translate_locator_to_location(location)
course_module = modulestore().get_item(old_location)
maxresults = request.REQUEST.get('max', None)
start = request.REQUEST.get('start', None)
return render_to_response('asset_index.html', {
'context_course': course_module,
'asset_callback_url': location.url_reverse('assets/', '')
})
def _assets_json(request, location):
"""
Display an editable asset library.
Supports start (0-based index into the list of assets) and max query parameters.
"""
requested_page = int(request.REQUEST.get('page', 0))
requested_page_size = int(request.REQUEST.get('page_size', 50))
current_page = max(requested_page, 0)
start = current_page * requested_page_size
old_location = loc_mapper().translate_locator_to_location(location)
course_reference = StaticContent.compute_location(old_location.org, old_location.course, old_location.name)
if maxresults is not None:
maxresults = int(maxresults)
start = int(start) if start else 0
assets = contentstore().get_all_content_for_course(
course_reference, start=start, maxresults=maxresults,
sort=[('uploadDate', DESCENDING)]
)
else:
assets = contentstore().get_all_content_for_course(
course_reference, sort=[('uploadDate', DESCENDING)]
)
assets, total_count = contentstore().get_all_content_for_course(
course_reference, start=start, maxresults=requested_page_size, sort=[('uploadDate', DESCENDING)]
)
end = start + len(assets)
asset_json = []
for asset in assets:
......@@ -101,10 +113,13 @@ def _asset_index(request, location):
asset_locked = asset.get('locked', False)
asset_json.append(_get_asset_json(asset['displayname'], asset['uploadDate'], asset_location, thumbnail_location, asset_locked))
return render_to_response('asset_index.html', {
'context_course': course_module,
'asset_list': json.dumps(asset_json),
'asset_callback_url': location.url_reverse('assets/', '')
return JsonResponse({
'start': start,
'end': end,
'page': current_page,
'pageSize': requested_page_size,
'totalCount': total_count,
'assets': asset_json
})
......
......@@ -24,6 +24,7 @@ requirejs.config({
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min",
"tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/tiny_mce",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"xmodule": "xmodule_js/src/xmodule",
......@@ -38,6 +39,7 @@ requirejs.config({
"jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async",
"draggabilly": "xmodule_js/common_static/js/vendor/draggabilly.pkgd",
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
......@@ -115,6 +117,10 @@ requirejs.config({
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {
deps: ["backbone"],
exports: "Backbone.Paginator"
},
"youtube": {
exports: "YT"
},
......@@ -139,6 +145,9 @@ requirejs.config({
]
MathJax.Hub.Configured()
},
"URI": {
exports: "URI"
},
"xmodule": {
exports: "XModule"
},
......@@ -197,10 +206,13 @@ define([
"js/spec/transcripts/videolist_spec", "js/spec/transcripts/message_manager_spec",
"js/spec/transcripts/file_uploader_spec",
"js/spec/utils/module_spec",
"js/spec/models/explicit_url_spec"
"js/spec/views/baseview_spec",
"js/spec/utils/handle_iframe_binding_spec",
"js/spec/utils/module_spec",
"js/spec/views/baseview_spec",
"js/spec/views/paging_spec",
# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js
......
......@@ -23,6 +23,7 @@ requirejs.config({
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min",
"tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/tiny_mce",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"xmodule": "xmodule_js/src/xmodule",
......@@ -34,6 +35,7 @@ requirejs.config({
"jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async",
"draggabilly": "xmodule_js/common_static/js/vendor/draggabilly.pkgd",
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
......@@ -106,6 +108,10 @@ requirejs.config({
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {
deps: ["backbone"],
exports: "Backbone.Paginator"
},
"youtube": {
exports: "YT"
},
......@@ -130,6 +136,9 @@ requirejs.config({
]
MathJax.Hub.Configured();
},
"URI": {
exports: "URI"
},
"xmodule": {
exports: "XModule"
},
......@@ -166,4 +175,3 @@ jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
define([
"coffee/spec/views/assets_spec"
])
......@@ -2,7 +2,10 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
(jasmine, create_sinon, Squire) ->
feedbackTpl = readFixtures('system-feedback.underscore')
assetLibraryTpl = readFixtures('asset-library.underscore')
assetTpl = readFixtures('asset.underscore')
pagingHeaderTpl = readFixtures('paging-header.underscore')
pagingFooterTpl = readFixtures('paging-footer.underscore')
describe "Asset view", ->
beforeEach ->
......@@ -44,7 +47,7 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
spyOn(@model, "save").andCallThrough()
@collection = new AssetCollection([@model])
@collection.url = "update-asset-url"
@collection.url = "assets-url"
@view = new AssetView({model: @model})
waitsFor (=> @view), "AssetView was not created", 1000
......@@ -131,7 +134,10 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
describe "Assets view", ->
beforeEach ->
setFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
setFixtures($("<script>", {id: "asset-library-tpl", type: "text/template"}).text(assetLibraryTpl))
appendSetFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
appendSetFixtures($("<script>", {id: "paging-header-tpl", type: "text/template"}).text(pagingHeaderTpl))
appendSetFixtures($("<script>", {id: "paging-footer-tpl", type: "text/template"}).text(pagingFooterTpl))
window.analytics = jasmine.createSpyObj('analytics', ['track'])
window.course_location_analytics = jasmine.createSpy()
appendSetFixtures(sandbox({id: "asset_table_body"}))
......@@ -145,31 +151,43 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
"Warning": @promptSpies.constructor
})
@mockAsset1 = {
display_name: "test asset 1"
url: 'actual_asset_url_1'
portable_url: 'portable_url_1'
date_added: 'date_1'
thumbnail: null
id: 'id_1'
}
@mockAsset2 = {
display_name: "test asset 2"
url: 'actual_asset_url_2'
portable_url: 'portable_url_2'
date_added: 'date_2'
thumbnail: null
id: 'id_2'
}
@mockAssetsResponse = {
assets: [ @mockAsset1, @mockAsset2 ],
start: 0,
end: 1,
page: 0,
pageSize: 5,
totalCount: 2
}
runs =>
@injector.require ["js/models/asset", "js/collections/asset", "js/views/assets"],
(AssetModel, AssetCollection, AssetsView) =>
@AssetModel = AssetModel
@collection = new AssetCollection [
display_name: "test asset 1"
url: 'actual_asset_url_1'
portable_url: 'portable_url_1'
date_added: 'date_1'
thumbnail: null
id: 'id_1'
,
display_name: "test asset 2"
url: 'actual_asset_url_2'
portable_url: 'portable_url_2'
date_added: 'date_2'
thumbnail: null
id: 'id_2'
]
@collection.url = "update-asset-url"
@collection = new AssetCollection();
@collection.url = "assets-url"
@view = new AssetsView
collection: @collection
el: $('#asset_table_body')
@view.render()
waitsFor (=> @view), "AssetView was not created", 1000
waitsFor (=> @view), "AssetsView was not created", 1000
$.ajax()
......@@ -181,33 +199,38 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
@injector.remove()
describe "Basic", ->
# Separate setup method to work-around mis-parenting of beforeEach methods
setup = (response) ->
requests = create_sinon.requests(this)
@view.setPage(0)
create_sinon.respondWithJson(requests, response)
return requests
it "should render both assets", ->
@view.render()
requests = setup.call(this, @mockAssetsResponse)
expect(@view.$el).toContainText("test asset 1")
expect(@view.$el).toContainText("test asset 2")
it "should remove the deleted asset from the view", ->
requests = create_sinon["requests"](this)
requests = setup.call(this, @mockAssetsResponse)
# Delete the 2nd asset with success from server.
@view.render().$(".remove-asset-button")[1].click()
@view.$(".remove-asset-button")[1].click()
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
req.respond(200) for req in requests
expect(@view.$el).toContainText("test asset 1")
expect(@view.$el).not.toContainText("test asset 2")
it "does not remove asset if deletion failed", ->
requests = create_sinon["requests"](this)
requests = setup.call(this, @mockAssetsResponse)
# Delete the 2nd asset, but mimic a failure from the server.
@view.render().$(".remove-asset-button")[1].click()
@view.$(".remove-asset-button")[1].click()
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
req.respond(404) for req in requests
expect(@view.$el).toContainText("test asset 1")
expect(@view.$el).toContainText("test asset 2")
it "adds an asset if asset does not already exist", ->
@view.render()
requests = setup.call(this, @mockAssetsResponse)
model = new @AssetModel
display_name: "new asset"
url: 'new_actual_asset_url'
......@@ -216,12 +239,29 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
thumbnail: null
id: 'idx'
@view.addAsset(model)
create_sinon.respondWithJson(requests,
{
assets: [ @mockAsset1, @mockAsset2,
{
display_name: "new asset"
url: 'new_actual_asset_url'
portable_url: 'portable_url'
date_added: 'date'
thumbnail: null
id: 'idx'
}
],
start: 0,
end: 2,
page: 0,
pageSize: 5,
totalCount: 3
})
expect(@view.$el).toContainText("new asset")
expect(@collection.models.indexOf(model)).toBe(0)
expect(@collection.models.length).toBe(3)
it "does not add an asset if asset already exists", ->
@view.render()
setup.call(this, @mockAssetsResponse)
spyOn(@collection, "add").andCallThrough()
model = @collection.models[1]
@view.addAsset(model)
......
define(["backbone", "js/models/asset"], function(Backbone, AssetModel){
var AssetCollection = Backbone.Collection.extend({
model : AssetModel
});
return AssetCollection;
define(["backbone.paginator", "js/models/asset"], function(BackbonePaginator, AssetModel) {
var AssetCollection = BackbonePaginator.requestPager.extend({
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; },
'format': 'json'
},
parse: function(response) {
var totalCount = response.totalCount,
start = response.start,
currentPage = response.page,
pageSize = response.pageSize,
totalPages = Math.ceil(totalCount / pageSize);
this.totalCount = totalCount;
this.totalPages = Math.max(totalPages, 1); // Treat an empty collection as having 1 page...
this.currentPage = currentPage;
this.start = start;
return response.assets;
}
});
return AssetCollection;
});
......@@ -33,7 +33,7 @@ define(["sinon"], function(sinon) {
var requests = [];
var xhr = sinon.useFakeXMLHttpRequest();
xhr.onCreate = function(request) {
requests.push(request)
requests.push(request);
};
that.after(function() {
......@@ -43,8 +43,16 @@ define(["sinon"], function(sinon) {
return requests;
};
var respondWithJson = function(requests, jsonResponse, requestIndex) {
requestIndex = requestIndex || requests.length - 1;
requests[requestIndex].respond(200,
{ "Content-Type": "application/json" },
JSON.stringify(jsonResponse));
};
return {
"server": fakeServer,
"requests": fakeRequests
"requests": fakeRequests,
"respondWithJson": respondWithJson
};
});
define([ "jquery", "js/spec/create_sinon", "URI",
"js/views/paging", "js/views/paging_header", "js/views/paging_footer",
"js/models/asset", "js/collections/asset" ],
function ($, create_sinon, URI, PagingView, PagingHeader, PagingFooter, AssetModel, AssetCollection) {
var createMockAsset = function(index) {
var id = 'asset_' + index;
return {
id: id,
display_name: id,
url: id
};
};
var mockFirstPage = {
assets: [
createMockAsset(1),
createMockAsset(2),
createMockAsset(3)
],
pageSize: 3,
totalCount: 4,
page: 0,
start: 0,
end: 2
};
var mockSecondPage = {
assets: [
createMockAsset(4)
],
pageSize: 3,
totalCount: 4,
page: 1,
start: 3,
end: 4
};
var mockEmptyPage = {
assets: [],
pageSize: 3,
totalCount: 0,
page: 0,
start: 0,
end: 0
};
var respondWithMockAssets = function(requests) {
var requestIndex = requests.length - 1;
var request = requests[requestIndex];
var url = new URI(request.url);
var queryParameters = url.query(true); // Returns an object with each query parameter stored as a value
var page = queryParameters.page;
var response = page === "0" ? mockFirstPage : mockSecondPage;
create_sinon.respondWithJson(requests, response, requestIndex);
};
var MockPagingView = PagingView.extend({
renderPageItems: function() {}
});
describe("Paging", function() {
var pagingView;
beforeEach(function () {
var assets = new AssetCollection();
assets.url = "assets_url";
var feedbackTpl = readFixtures('system-feedback.underscore');
setFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" }).text(feedbackTpl));
pagingView = new MockPagingView({collection: assets});
});
describe("PagingView", function () {
it('can set the current page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(0);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(1);
});
it('should not change page after a server error', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingView.setPage(1);
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
});
it('does not move forward after a server error', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingView.nextPage();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingView.nextPage();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(1);
});
it('can not move forward from the final page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingView.nextPage();
expect(requests.length).toBe(1);
});
it('can move back a page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingView.previousPage();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(0);
});
it('can not move back from the first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingView.previousPage();
expect(requests.length).toBe(1);
});
it('does not move back after a server error', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingView.previousPage();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(1);
});
});
describe("PagingHeader", function () {
var pagingHeader;
beforeEach(function () {
var pagingHeaderTpl = readFixtures('paging-header.underscore');
appendSetFixtures($("<script>", { id: "paging-header-tpl", type: "text/template" }).text(pagingHeaderTpl));
pagingHeader = new PagingHeader({view: pagingView});
});
describe("Next page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingView.render();
pagingHeader.render();
});
it('does not move forward if a server error occurs', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingHeader.$('.next-page-link').click();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingHeader.$('.next-page-link').click();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(1);
});
it('should be enabled when there is at least one more page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingHeader.$('.next-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled on the final page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingHeader.$('.next-page-link')).toHaveClass('is-disabled');
});
it('should be disabled on an empty page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingHeader.$('.next-page-link')).toHaveClass('is-disabled');
});
});
describe("Previous page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingView.render();
pagingHeader.render();
});
it('does not move back if a server error occurs', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingHeader.$('.previous-page-link').click();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(1);
});
it('can go back a page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingHeader.$('.previous-page-link').click();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(0);
});
it('should be disabled on the first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingHeader.$('.previous-page-link')).toHaveClass('is-disabled');
});
it('should be enabled on the second page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingHeader.$('.previous-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled for an empty page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingHeader.$('.previous-page-link')).toHaveClass('is-disabled');
});
});
describe("Asset count label", function () {
it('should show correct count on first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingHeader.$('.count-current-shown')).toHaveHtml('1-3');
});
it('should show correct count on second page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingHeader.$('.count-current-shown')).toHaveHtml('4-4');
});
it('should show correct count for an empty collection', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingHeader.$('.count-current-shown')).toHaveHtml('0-0');
});
});
describe("Asset total label", function () {
it('should show correct total on the first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingHeader.$('.count-total')).toHaveText('4 total');
});
it('should show correct total on the second page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingHeader.$('.count-total')).toHaveText('4 total');
});
it('should show zero total for an empty collection', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingHeader.$('.count-total')).toHaveText('0 total');
});
});
});
describe("PagingFooter", function () {
var pagingFooter;
beforeEach(function () {
var pagingFooterTpl = readFixtures('paging-footer.underscore');
appendSetFixtures($("<script>", { id: "paging-footer-tpl", type: "text/template" }).text(pagingFooterTpl));
pagingFooter = new PagingFooter({view: pagingView});
});
describe("Next page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingView.render();
pagingFooter.render();
});
it('does not move forward if a server error occurs', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingFooter.$('.next-page-link').click();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
});
it('can move to the next page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingFooter.$('.next-page-link').click();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(1);
});
it('should be enabled when there is at least one more page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingFooter.$('.next-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled on the final page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingFooter.$('.next-page-link')).toHaveClass('is-disabled');
});
it('should be disabled on an empty page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingFooter.$('.next-page-link')).toHaveClass('is-disabled');
});
});
describe("Previous page button", function () {
beforeEach(function () {
// Render the page and header so that they can react to events
pagingView.render();
pagingFooter.render();
});
it('does not move back if a server error occurs', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingFooter.$('.previous-page-link').click();
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(1);
});
it('can go back a page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
pagingFooter.$('.previous-page-link').click();
respondWithMockAssets(requests);
expect(pagingView.collection.currentPage).toBe(0);
});
it('should be disabled on the first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingFooter.$('.previous-page-link')).toHaveClass('is-disabled');
});
it('should be enabled on the second page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingFooter.$('.previous-page-link')).not.toHaveClass('is-disabled');
});
it('should be disabled for an empty page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingFooter.$('.previous-page-link')).toHaveClass('is-disabled');
});
});
describe("Page total label", function () {
it('should show 1 on the first page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingFooter.$('.current-page')).toHaveText('1');
});
it('should show 2 on the second page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(1);
respondWithMockAssets(requests);
expect(pagingFooter.$('.current-page')).toHaveText('2');
});
it('should show 1 for an empty collection', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingFooter.$('.current-page')).toHaveText('1');
});
});
describe("Page total label", function () {
it('should show the correct value with more than one page', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingFooter.$('.total-pages')).toHaveText('2');
});
it('should show page 1 when there are no assets', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
create_sinon.respondWithJson(requests, mockEmptyPage);
expect(pagingFooter.$('.total-pages')).toHaveText('1');
});
});
describe("Page input field", function () {
var input;
beforeEach(function () {
pagingFooter.render();
});
it('should initially have a blank page input', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
expect(pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should handle invalid page requests', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingFooter.$('.page-number-input').val('abc');
pagingFooter.$('.page-number-input').trigger('change');
expect(pagingView.collection.currentPage).toBe(0);
expect(pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should switch pages via the input field', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingFooter.$('.page-number-input').val('2');
pagingFooter.$('.page-number-input').trigger('change');
create_sinon.respondWithJson(requests, mockSecondPage);
expect(pagingView.collection.currentPage).toBe(1);
expect(pagingFooter.$('.page-number-input')).toHaveValue('');
});
it('should handle AJAX failures when switching pages via the input field', function () {
var requests = create_sinon.requests(this);
pagingView.setPage(0);
respondWithMockAssets(requests);
pagingFooter.$('.page-number-input').val('2');
pagingFooter.$('.page-number-input').trigger('change');
requests[1].respond(500);
expect(pagingView.collection.currentPage).toBe(0);
expect(pagingFooter.$('.page-number-input')).toHaveValue('');
});
});
});
});
});
define(["js/views/baseview", "js/views/asset"], function(BaseView, AssetView) {
define(["js/views/paging", "js/views/asset", "js/views/paging_header", "js/views/paging_footer"],
function(PagingView, AssetView, PagingHeader, PagingFooter) {
var AssetsView = BaseView.extend({
var AssetsView = PagingView.extend({
// takes AssetCollection as model
initialize : function() {
this.listenTo(this.collection, 'destroy', this.handleDestroy);
this.render();
PagingView.prototype.initialize.call(this);
var collection = this.collection;
this.template = _.template($("#asset-library-tpl").text());
this.listenTo(collection, 'destroy', this.handleDestroy);
},
render: function() {
this.$el.empty();
this.$el.html(this.template());
this.tableBody = this.$('#asset-table-body');
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
this.pagingFooter = new PagingFooter({view: this, el: $('#asset-paging-footer')});
this.pagingHeader.render();
this.pagingFooter.render();
var self = this;
this.collection.each(
function(asset) {
var view = new AssetView({model: asset});
self.$el.append(view.render().el);
});
// Hide the contents until the collection has loaded the first time
this.$('.asset-library').hide();
this.$('.no-asset-content').hide();
return this;
},
handleDestroy: function(model, collection, options) {
var index = options.index;
this.$el.children().eq(index).remove();
renderPageItems: function() {
var self = this,
assets = this.collection,
hasAssets = assets.length > 0;
self.tableBody.empty();
if (hasAssets) {
assets.each(
function(asset) {
var view = new AssetView({model: asset});
self.tableBody.append(view.render().el);
});
}
self.$('.asset-library').toggle(hasAssets);
self.$('.no-asset-content').toggle(!hasAssets);
return this;
},
handleDestroy: function(model, collection, options) {
this.collection.fetch({reset: true}); // reload the collection to get a fresh page full of items
analytics.track('Deleted Asset', {
'course': course_location_analytics,
'id': model.get('url')
......@@ -32,17 +52,12 @@ var AssetsView = BaseView.extend({
},
addAsset: function (model) {
// If asset is not already being shown, add it.
if (this.collection.findWhere({'url': model.get('url')}) === undefined) {
this.collection.add(model, {at: 0});
var view = new AssetView({model: model});
this.$el.prepend(view.render().el);
analytics.track('Uploaded a File', {
'course': course_location_analytics,
'asset_url': model.get('url')
});
}
this.setPage(0);
analytics.track('Uploaded a File', {
'course': course_location_analytics,
'asset_url': model.get('url')
});
}
});
......
define(["backbone", "js/views/feedback_alert", "gettext"], function(Backbone, AlertView, gettext) {
var PagingView = Backbone.View.extend({
// takes a Backbone Paginator as a model
initialize: function() {
Backbone.View.prototype.initialize.call(this);
var collection = this.collection;
collection.bind('add', _.bind(this.renderPageItems, this));
collection.bind('remove', _.bind(this.renderPageItems, this));
collection.bind('reset', _.bind(this.renderPageItems, this));
},
setPage: function(page) {
var self = this,
collection = self.collection,
oldPage = collection.currentPage;
collection.goTo(page, {
reset: true,
success: function() {
window.scrollTo(0, 0);
},
error: function(collection, response, options) {
collection.currentPage = oldPage;
}
});
},
nextPage: function() {
var collection = this.collection,
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1;
if (currentPage < lastPage) {
this.setPage(currentPage + 1);
}
},
previousPage: function() {
var collection = this.collection,
currentPage = collection.currentPage;
if (currentPage > 0) {
this.setPage(currentPage - 1);
}
}
});
return PagingView;
}); // end define();
define(["backbone", "underscore"], function(Backbone, _) {
var PagingFooter = Backbone.View.extend({
events : {
"click .next-page-link": "nextPage",
"click .previous-page-link": "previousPage",
"change .page-number-input": "changePage"
},
initialize: function(options) {
var view = options.view,
collection = view.collection;
this.view = view;
this.template = _.template($("#paging-footer-tpl").text());
collection.bind('add', _.bind(this.render, this));
collection.bind('remove', _.bind(this.render, this));
collection.bind('reset', _.bind(this.render, this));
this.render();
},
render: function() {
var view = this.view,
collection = view.collection,
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1;
this.$el.html(this.template({
current_page: collection.currentPage,
total_pages: collection.totalPages
}));
this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0);
this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage);
return this;
},
changePage: function() {
var view = this.view,
collection = view.collection,
currentPage = collection.currentPage + 1,
pageInput = this.$("#page-number-input"),
pageNumber = parseInt(pageInput.val(), 10);
if (pageNumber && pageNumber !== currentPage) {
view.setPage(pageNumber - 1);
}
pageInput.val(""); // Clear the value as the label will show beneath it
},
nextPage: function() {
this.view.nextPage();
},
previousPage: function() {
this.view.previousPage();
}
});
return PagingFooter;
}); // end define();
define(["backbone", "underscore", "gettext"], function(Backbone, _, gettext) {
var PagingHeader = Backbone.View.extend({
events : {
"click .next-page-link": "nextPage",
"click .previous-page-link": "previousPage"
},
initialize: function(options) {
var view = options.view,
collection = view.collection;
this.view = view;
this.template = _.template($("#paging-header-tpl").text());
collection.bind('add', _.bind(this.render, this));
collection.bind('remove', _.bind(this.render, this));
collection.bind('reset', _.bind(this.render, this));
},
render: function() {
var view = this.view,
collection = view.collection,
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1,
messageHtml = this.messageHtml();
this.$el.html(this.template({
messageHtml: messageHtml
}));
this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0);
this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage);
return this;
},
messageHtml: function() {
var view = this.view,
collection = view.collection,
start = collection.start,
count = collection.size(),
end = start + count,
total = collection.totalCount,
fmts = gettext('Showing %(current_span)s%(start)s-%(end)s%(end_span)s out of %(total_span)s%(total)s total%(end_span)s, sorted by %(order_span)s%(sort_order)s%(end_span)s');
return '<p>' + interpolate(fmts, {
start: Math.min(start + 1, end),
end: end,
total: total,
sort_order: gettext('Date Added'),
current_span: '<span class="count-current-shown">',
total_span: '<span class="count-total">',
order_span: '<span class="sort-order">',
end_span: '</span>'
}, true) + "</p>";
},
nextPage: function() {
this.view.nextPage();
},
previousPage: function() {
this.view.previousPage();
}
});
return PagingHeader;
}); // end define();
......@@ -38,6 +38,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/underscore.string.min.js
- xmodule_js/common_static/js/vendor/backbone-min.js
- xmodule_js/common_static/js/vendor/backbone-associations-min.js
- xmodule_js/common_static/js/vendor/backbone.paginator.min.js
- xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker.js
- xmodule_js/common_static/js/vendor/jquery.leanModal.min.js
- xmodule_js/common_static/js/vendor/jquery.ajaxQueue.js
......@@ -55,6 +56,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/draggabilly.pkgd.js
- xmodule_js/common_static/js/vendor/date.js
- xmodule_js/common_static/js/vendor/domReady.js
- xmodule_js/common_static/js/vendor/URI.min.js
- xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min.js
- xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
- xmodule_js/common_static/coffee/src/xblock
......
......@@ -38,6 +38,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/underscore.string.min.js
- xmodule_js/common_static/js/vendor/backbone-min.js
- xmodule_js/common_static/js/vendor/backbone-associations-min.js
- xmodule_js/common_static/js/vendor/backbone.paginator.min.js
- xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker.js
- xmodule_js/common_static/js/vendor/jquery.leanModal.min.js
- xmodule_js/common_static/js/vendor/jquery.form.js
......@@ -49,6 +50,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js
- xmodule_js/common_static/js/vendor/jasmine.async.js
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
- xmodule_js/common_static/js/vendor/URI.min.js
- xmodule_js/src/xmodule.js
- xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
- xmodule_js/common_static/js/test/i18n.js
......
......@@ -27,9 +27,167 @@
}
.no-asset-content {
@extend %ui-well;
padding: ($baseline*2);
background-color: $gray-l4;
text-align: center;
color: $gray;
.new-button {
@include font-size(14);
margin-left: $baseline;
[class^="icon-"] {
margin-right: ($baseline/2);
}
}
}
.asset-library {
@include clearfix;
.meta-wrap {
margin-bottom: $baseline;
}
.meta {
@extend %t-copy-sub2;
display: inline-block;
vertical-align: top;
width: flex-grid(9, 12);
color: $gray-l1;
.count-current-shown,
.count-total,
.sort-order {
font-weight: 700;
}
}
.pagination {
@include clearfix;
display: inline-block;
width: flex-grid(3, 12);
&.pagination-compact {
text-align: right;
}
&.pagination-full {
display: block;
width: flex-grid(4, 12);
margin: $baseline auto;
}
.nav-item {
position: relative;
display: inline-block;
}
.nav-link {
@include transition(all $tmg-f2 ease-in-out 0s);
display: block;
padding: ($baseline/4) ($baseline*.75);
&.previous {
margin-right: ($baseline/2);
}
&.next {
margin-left: ($baseline/2);
}
&:hover {
background-color: $blue;
border-radius: 3px;
color: $white;
}
&.is-disabled {
background-color: transparent;
color: $gray-l2;
pointer-events: none;
}
}
.nav-label {
@extend .sr;
}
.pagination-form,
.current-page,
.page-divider,
.total-pages {
display: inline-block;
}
.current-page,
.page-number-input,
.total-pages {
@extend %t-copy-base;
width: ($baseline*2.5);
margin: 0 ($baseline*.75);
padding: ($baseline/4);
text-align: center;
color: $gray;
font-weight: 600;
}
.current-page {
@extend %ui-depth1;
position: absolute;
left: -($baseline/4);
}
.page-divider {
@extend %t-title4;
vertical-align: middle;
color: $gray-l2;
font-weight: 400;
}
.pagination-form {
@extend %ui-depth2;
position: relative;
.page-number-label,
.submit-pagination-form {
@extend .sr;
}
.page-number-input {
@include transition(all $tmg-f2 ease-in-out 0s);
border: 1px solid transparent;
border-bottom: 1px dotted $gray-l2;
border-radius: 0;
box-shadow: none;
background: none;
&:hover {
background-color: $white;
opacity: 0.6;
}
&:focus {
// borrowing the base input focus styles to match overall app
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
opacity: 1.0;
box-shadow: 0 0 3px $shadow-d1 inset;
background-color: $white;
border: 1px solid transparent;
border-radius: 3px;
}
}
}
}
table {
width: 100%;
word-wrap: break-word;
......@@ -41,6 +199,11 @@
vertical-align: middle;
text-align: left;
color: $gray;
.current-sort {
font-weight: 700;
border-bottom: 1px solid $gray-l3;
}
}
td {
......
......@@ -9,21 +9,26 @@
<%namespace name='static' file='static_content.html'/>
<%block name="header_extras">
<script type="text/template" id="asset-tpl">
<%static:include path="js/asset.underscore"/>
</script>
% for template_name in ["asset-library", "asset", "paging-header", "paging-footer"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady", "jquery", "gettext", "js/models/asset", "js/collections/asset",
"js/views/assets", "js/views/feedback_prompt",
"js/views/feedback_notification", "js/utils/modal", "jquery.fileupload"],
function(domReady, $, gettext, AssetModel, AssetCollection, AssetsView, PromptView, NotificationView, ModalUtils) {
var assets = new AssetCollection(${asset_list});
"js/views/feedback_notification", "js/views/paging_header", "js/views/paging_footer",
"js/utils/modal", "jquery.fileupload"],
function(domReady, $, gettext, AssetModel, AssetCollection, AssetsView, PromptView, NotificationView,
PagingHeader, PagingFooter, ModalUtils) {
var assets = new AssetCollection();
assets.url = "${asset_callback_url}";
var assetsView = new AssetsView({collection: assets, el: $('#asset_table_body')});
var assetsView = new AssetsView({collection: assets, el: $('#asset-library')});
assetsView.render();
assetsView.setPage(0);
var hideModal = function (e) {
if (e) {
......@@ -142,30 +147,7 @@ require(["domReady", "jquery", "gettext", "js/models/asset", "js/collections/ass
<div class="wrapper-content wrapper">
<section class="content">
<article class="asset-library content-primary" role="main">
<table>
<caption class="sr">${_("List of uploaded files and assets in this course")}</caption>
<colgroup>
<col class="thumb-cols" />
<col class="name-cols" />
<col class="date-cols" />
<col class="embed-cols" />
<col class="actions-cols" />
</colgroup>
<thead>
<tr>
<th class="thumb-col">${_("Preview")}</th>
<th class="name-col">${_("Name")}</th>
<th class="date-col">${_("Date Added")}</th>
<th class="embed-col">${_("URL")}</th>
<th class="actions-col"><span class="sr">${_("Actions")}</span></th>
</tr>
</thead>
<tbody id="asset_table_body" >
</tbody>
</table>
</article>
<article id="asset-library" class="content-primary" role="main"></article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
......
......@@ -69,6 +69,7 @@
"underscore.string": "js/vendor/underscore.string.min",
"backbone": "js/vendor/backbone-min",
"backbone.associations": "js/vendor/backbone-associations-min",
"backbone.paginator": "js/vendor/backbone.paginator.min",
"tinymce": "js/vendor/tiny_mce/tiny_mce",
"jquery.tinymce": "js/vendor/tiny_mce/jquery.tinymce",
"xmodule": "/xmodule/xmodule",
......@@ -76,6 +77,7 @@
"utility": "js/src/utility",
"accessibility": "js/src/accessibility_tools",
"draggabilly": "js/vendor/draggabilly.pkgd",
"URI": "/js/vendor/URI.min",
// externally hosted files
"tender": "//edxedge.tenderapp.com/tender_widget",
......@@ -163,6 +165,10 @@
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {
deps: ["backbone"],
exports: "Backbone.Paginator"
},
"youtube": {
exports: "YT"
},
......@@ -193,6 +199,9 @@
MathJax.Hub.Configured();
}
},
"URI": {
exports: "URI"
},
"xblock/core": {
exports: "XBlock",
deps: ["jquery", "jquery.immediateDescendents"]
......
<div class="asset-library">
<div id="asset-paging-header"></div>
<table>
<caption class="sr"><%= gettext("List of uploaded files and assets in this course") %></caption>
<colgroup>
<col class="thumb-cols" />
<col class="name-cols" />
<col class="date-cols" />
<col class="embed-cols" />
<col class="actions-cols" />
</colgroup>
<thead>
<tr>
<th class="thumb-col"><%= gettext("Preview") %></th>
<th class="name-col"><%= gettext("Name") %></th>
<th class="date-col"><span class="current-sort" href=""><%= gettext("Date Added") %></span></th>
<th class="embed-col"><%= gettext("URL") %></th>
<th class="actions-col"><span class="sr"><%= gettext("Actions") %></span></th>
</tr>
</thead>
<tbody id="asset-table-body" ></tbody>
</table>
<div id="asset-paging-footer"></div>
</div>
<div class="no-asset-content">
<p><%= gettext("You haven't added any assets to this course yet.") %> <a href="#" class="button upload-button new-button"><i class="icon-plus"></i><%= gettext("Upload your first asset") %></a></p>
</div>
<nav class="pagination pagination-full bottom">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item page">
<div class="pagination-form">
<label class="page-number-label" for="page-number"><%= gettext("Page number") %></label>
<input id="page-number-input" class="page-number-input" name="page-number" type="text" size="4" />
</div>
<span class="current-page"><%= current_page + 1 %></span>
<span class="page-divider">/</span>
<span class="total-pages"><%= total_pages %></span>
</li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
<div class="meta-wrap">
<div class="meta">
<%= messageHtml %>
</div>
<nav class="pagination pagination-compact top">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</div>
......@@ -177,7 +177,10 @@ class ContentStore(object):
def get_all_content_for_course(self, location, start=0, maxresults=-1, sort=None):
'''
Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example:
Returns a list of static assets for a course, followed by the total number of assets.
By default all assets are returned, but start and maxresults can be provided to limit the query.
The return format is a list of dictionary elements. Example:
[
......
......@@ -128,7 +128,7 @@ class MongoContentStore(ContentStore):
directory as the other policy files.
"""
policy = {}
assets = self.get_all_content_for_course(course_location)
assets,__ = self.get_all_content_for_course(course_location)
for asset in assets:
asset_location = Location(asset['_id'])
......@@ -141,7 +141,7 @@ class MongoContentStore(ContentStore):
json.dump(policy, f)
def get_all_content_thumbnails_for_course(self, location):
return self._get_all_content_for_course(location, get_thumbnails=True)
return self._get_all_content_for_course(location, get_thumbnails=True)[0]
def get_all_content_for_course(self, location, start=0, maxresults=-1, sort=None):
return self._get_all_content_for_course(
......@@ -178,7 +178,8 @@ class MongoContentStore(ContentStore):
)
else:
items = self.fs_files.find(location_to_query(course_filter), sort=sort)
return list(items)
count = items.count()
return list(items), count
def set_attr(self, location, attr, value=True):
"""
......
......@@ -19,7 +19,7 @@ def empty_asset_trashcan(course_locs):
store.delete(id)
# then delete all of the assets
assets = store.get_all_content_for_course(course_loc)
assets,__ = store.get_all_content_for_course(course_loc)
for asset in assets:
asset_loc = Location(asset["_id"])
id = StaticContent.get_id_from_location(asset_loc)
......
......@@ -199,7 +199,7 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
# now iterate through all of the assets, also updating the thumbnail pointer
assets = contentstore.get_all_content_for_course(source_location)
assets,__ = contentstore.get_all_content_for_course(source_location)
for asset in assets:
asset_loc = Location(asset["_id"])
content = contentstore.find(asset_loc)
......@@ -260,7 +260,7 @@ def delete_course(modulestore, contentstore, source_location, commit=False):
_delete_assets(contentstore, thumbs, commit)
# then delete all of the assets
assets = contentstore.get_all_content_for_course(source_location)
assets,__ = contentstore.get_all_content_for_course(source_location)
_delete_assets(contentstore, assets, commit)
# then delete all course modules
......
......@@ -223,7 +223,7 @@ class TestMongoModuleStore(object):
Test getting, setting, and defaulting the locked attr and arbitrary attrs.
"""
location = Location('i4x', 'edX', 'toy', 'course', '2012_Fall')
course_content = TestMongoModuleStore.content_store.get_all_content_for_course(location)
course_content,__ = TestMongoModuleStore.content_store.get_all_content_for_course(location)
assert len(course_content) > 0
# a bit overkill, could just do for content[0]
for content in course_content:
......
window.gettext = window.ngettext = function(s){return s;};
function interpolate(fmt, obj, named) {
if (named) {
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
} else {
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
}
}
/*! http://mths.be/punycode v1.2.3 by @mathias */
/*!
* URI.js - Mutating URLs
* IPv6 Support
*
* Version: 1.11.2
*
* Author: Rodney Rehm
* Web: http://medialize.github.com/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
/*!
* URI.js - Mutating URLs
* Second Level Domain (SLD) Support
*
* Version: 1.11.2
*
* Author: Rodney Rehm
* Web: http://medialize.github.com/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
/*!
* URI.js - Mutating URLs
*
* Version: 1.11.2
*
* Author: Rodney Rehm
* Web: http://medialize.github.com/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
(function(e){function S(e){throw RangeError(g[e])}function x(e,t){var n=e.length;while(n--)e[n]=t(e[n]);return e}function T(e,t){return x(e.split(m),t).join(".")}function N(e){var t=[],n=0,r=e.length,i,s;while(n<r)i=e.charCodeAt(n++),i>=55296&&i<=56319&&n<r?(s=e.charCodeAt(n++),(s&64512)==56320?t.push(((i&1023)<<10)+(s&1023)+65536):(t.push(i),n--)):t.push(i);return t}function C(e){return x(e,function(e){var t="";return e>65535&&(e-=65536,t+=w(e>>>10&1023|55296),e=56320|e&1023),t+=w(e),t}).join("")}function k(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:o}function L(e,t){return e+22+75*(e<26)-((t!=0)<<5)}function A(e,t,n){var r=0;e=n?b(e/l):e>>1,e+=b(e/t);for(;e>y*a>>1;r+=o)e=b(e/y);return b(r+(y+1)*e/(e+f))}function O(e){var t=[],n=e.length,r,i=0,f=h,l=c,d,v,m,g,y,w,E,x,T,N;d=e.lastIndexOf(p),d<0&&(d=0);for(v=0;v<d;++v)e.charCodeAt(v)>=128&&S("not-basic"),t.push(e.charCodeAt(v));for(m=d>0?d+1:0;m<n;){for(g=i,y=1,w=o;;w+=o){m>=n&&S("invalid-input"),E=k(e.charCodeAt(m++)),(E>=o||E>b((s-i)/y))&&S("overflow"),i+=E*y,x=w<=l?u:w>=l+a?a:w-l;if(E<x)break;N=o-x,y>b(s/N)&&S("overflow"),y*=N}r=t.length+1,l=A(i-g,r,g==0),b(i/r)>s-f&&S("overflow"),f+=b(i/r),i%=r,t.splice(i++,0,f)}return C(t)}function M(e){var t,n,r,i,f,l,d,v,m,g,y,E=[],x,T,C,k;e=N(e),x=e.length,t=h,n=0,f=c;for(l=0;l<x;++l)y=e[l],y<128&&E.push(w(y));r=i=E.length,i&&E.push(p);while(r<x){for(d=s,l=0;l<x;++l)y=e[l],y>=t&&y<d&&(d=y);T=r+1,d-t>b((s-n)/T)&&S("overflow"),n+=(d-t)*T,t=d;for(l=0;l<x;++l){y=e[l],y<t&&++n>s&&S("overflow");if(y==t){for(v=n,m=o;;m+=o){g=m<=f?u:m>=f+a?a:m-f;if(v<g)break;k=v-g,C=o-g,E.push(w(L(g+k%C,0))),v=b(k/C)}E.push(w(L(v,0))),f=A(n,T,r==i),n=0,++r}}++n,++t}return E.join("")}function _(e){return T(e,function(e){return d.test(e)?O(e.slice(4).toLowerCase()):e})}function D(e){return T(e,function(e){return v.test(e)?"xn--"+M(e):e})}var t=typeof exports=="object"&&exports,n=typeof module=="object"&&module&&module.exports==t&&module,r=typeof global=="object"&&global;if(r.global===r||r.window===r)e=r;var i,s=2147483647,o=36,u=1,a=26,f=38,l=700,c=72,h=128,p="-",d=/^xn--/,v=/[^ -~]/,m=/\x2E|\u3002|\uFF0E|\uFF61/g,g={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},y=o-u,b=Math.floor,w=String.fromCharCode,E;i={version:"1.2.3",ucs2:{decode:N,encode:C},decode:O,encode:M,toASCII:D,toUnicode:_};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd)define("punycode",[],function(){return i});else if(t&&!t.nodeType)if(n)n.exports=i;else for(E in i)i.hasOwnProperty(E)&&(t[E]=i[E]);else e.punycode=i})(this),function(e,t){typeof exports=="object"?module.exports=t():typeof define=="function"&&define.amd?define("IPv6",t):e.IPv6=t(e)}(this,function(e){function n(e){var t=e.toLowerCase(),n=t.split(":"),r=n.length,i=8;n[0]===""&&n[1]===""&&n[2]===""?(n.shift(),n.shift()):n[0]===""&&n[1]===""?n.shift():n[r-1]===""&&n[r-2]===""&&n.pop(),r=n.length,n[r-1].indexOf(".")!==-1&&(i=7);var s;for(s=0;s<r;s++)if(n[s]==="")break;if(s<i){n.splice(s,1,"0000");while(n.length<i)n.splice(s,0,"0000");r=n.length}var o;for(var u=0;u<i;u++){o=n[u].split("");for(var a=0;a<3;a++){if(!(o[0]==="0"&&o.length>1))break;o.splice(0,1)}n[u]=o.join("")}var f=-1,l=0,c=0,h=-1,p=!1;for(u=0;u<i;u++)p?n[u]==="0"?c+=1:(p=!1,c>l&&(f=h,l=c)):n[u]=="0"&&(p=!0,h=u,c=1);c>l&&(f=h,l=c),l>1&&n.splice(f,l,""),r=n.length;var d="";n[0]===""&&(beststr=":");for(u=0;u<r;u++){d+=n[u];if(u===r-1)break;d+=":"}return n[r-1]===""&&(d+=":"),d}function r(){return e.IPv6===this&&(e.IPv6=t),this}var t=e&&e.IPv6;return{best:n,noConflict:r}}),function(e,t){typeof exports=="object"?module.exports=t():typeof define=="function"&&define.amd?define("SecondLevelDomains",t):e.SecondLevelDomains=t(e)}(this,function(e){var t=e&&e.SecondLevelDomains,n=Object.prototype.hasOwnProperty,r={list:{ac:"com|gov|mil|net|org",ae:"ac|co|gov|mil|name|net|org|pro|sch",af:"com|edu|gov|net|org",al:"com|edu|gov|mil|net|org",ao:"co|ed|gv|it|og|pb",ar:"com|edu|gob|gov|int|mil|net|org|tur",at:"ac|co|gv|or",au:"asn|com|csiro|edu|gov|id|net|org",ba:"co|com|edu|gov|mil|net|org|rs|unbi|unmo|unsa|untz|unze",bb:"biz|co|com|edu|gov|info|net|org|store|tv",bh:"biz|cc|com|edu|gov|info|net|org",bn:"com|edu|gov|net|org",bo:"com|edu|gob|gov|int|mil|net|org|tv",br:"adm|adv|agr|am|arq|art|ato|b|bio|blog|bmd|cim|cng|cnt|com|coop|ecn|edu|eng|esp|etc|eti|far|flog|fm|fnd|fot|fst|g12|ggf|gov|imb|ind|inf|jor|jus|lel|mat|med|mil|mus|net|nom|not|ntr|odo|org|ppg|pro|psc|psi|qsl|rec|slg|srv|tmp|trd|tur|tv|vet|vlog|wiki|zlg",bs:"com|edu|gov|net|org",bz:"du|et|om|ov|rg",ca:"ab|bc|mb|nb|nf|nl|ns|nt|nu|on|pe|qc|sk|yk",ck:"biz|co|edu|gen|gov|info|net|org",cn:"ac|ah|bj|com|cq|edu|fj|gd|gov|gs|gx|gz|ha|hb|he|hi|hl|hn|jl|js|jx|ln|mil|net|nm|nx|org|qh|sc|sd|sh|sn|sx|tj|tw|xj|xz|yn|zj",co:"com|edu|gov|mil|net|nom|org",cr:"ac|c|co|ed|fi|go|or|sa",cy:"ac|biz|com|ekloges|gov|ltd|name|net|org|parliament|press|pro|tm","do":"art|com|edu|gob|gov|mil|net|org|sld|web",dz:"art|asso|com|edu|gov|net|org|pol",ec:"com|edu|fin|gov|info|med|mil|net|org|pro",eg:"com|edu|eun|gov|mil|name|net|org|sci",er:"com|edu|gov|ind|mil|net|org|rochest|w",es:"com|edu|gob|nom|org",et:"biz|com|edu|gov|info|name|net|org",fj:"ac|biz|com|info|mil|name|net|org|pro",fk:"ac|co|gov|net|nom|org",fr:"asso|com|f|gouv|nom|prd|presse|tm",gg:"co|net|org",gh:"com|edu|gov|mil|org",gn:"ac|com|gov|net|org",gr:"com|edu|gov|mil|net|org",gt:"com|edu|gob|ind|mil|net|org",gu:"com|edu|gov|net|org",hk:"com|edu|gov|idv|net|org",id:"ac|co|go|mil|net|or|sch|web",il:"ac|co|gov|idf|k12|muni|net|org","in":"ac|co|edu|ernet|firm|gen|gov|i|ind|mil|net|nic|org|res",iq:"com|edu|gov|i|mil|net|org",ir:"ac|co|dnssec|gov|i|id|net|org|sch",it:"edu|gov",je:"co|net|org",jo:"com|edu|gov|mil|name|net|org|sch",jp:"ac|ad|co|ed|go|gr|lg|ne|or",ke:"ac|co|go|info|me|mobi|ne|or|sc",kh:"com|edu|gov|mil|net|org|per",ki:"biz|com|de|edu|gov|info|mob|net|org|tel",km:"asso|com|coop|edu|gouv|k|medecin|mil|nom|notaires|pharmaciens|presse|tm|veterinaire",kn:"edu|gov|net|org",kr:"ac|busan|chungbuk|chungnam|co|daegu|daejeon|es|gangwon|go|gwangju|gyeongbuk|gyeonggi|gyeongnam|hs|incheon|jeju|jeonbuk|jeonnam|k|kg|mil|ms|ne|or|pe|re|sc|seoul|ulsan",kw:"com|edu|gov|net|org",ky:"com|edu|gov|net|org",kz:"com|edu|gov|mil|net|org",lb:"com|edu|gov|net|org",lk:"assn|com|edu|gov|grp|hotel|int|ltd|net|ngo|org|sch|soc|web",lr:"com|edu|gov|net|org",lv:"asn|com|conf|edu|gov|id|mil|net|org",ly:"com|edu|gov|id|med|net|org|plc|sch",ma:"ac|co|gov|m|net|org|press",mc:"asso|tm",me:"ac|co|edu|gov|its|net|org|priv",mg:"com|edu|gov|mil|nom|org|prd|tm",mk:"com|edu|gov|inf|name|net|org|pro",ml:"com|edu|gov|net|org|presse",mn:"edu|gov|org",mo:"com|edu|gov|net|org",mt:"com|edu|gov|net|org",mv:"aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro",mw:"ac|co|com|coop|edu|gov|int|museum|net|org",mx:"com|edu|gob|net|org",my:"com|edu|gov|mil|name|net|org|sch",nf:"arts|com|firm|info|net|other|per|rec|store|web",ng:"biz|com|edu|gov|mil|mobi|name|net|org|sch",ni:"ac|co|com|edu|gob|mil|net|nom|org",np:"com|edu|gov|mil|net|org",nr:"biz|com|edu|gov|info|net|org",om:"ac|biz|co|com|edu|gov|med|mil|museum|net|org|pro|sch",pe:"com|edu|gob|mil|net|nom|org|sld",ph:"com|edu|gov|i|mil|net|ngo|org",pk:"biz|com|edu|fam|gob|gok|gon|gop|gos|gov|net|org|web",pl:"art|bialystok|biz|com|edu|gda|gdansk|gorzow|gov|info|katowice|krakow|lodz|lublin|mil|net|ngo|olsztyn|org|poznan|pwr|radom|slupsk|szczecin|torun|warszawa|waw|wroc|wroclaw|zgora",pr:"ac|biz|com|edu|est|gov|info|isla|name|net|org|pro|prof",ps:"com|edu|gov|net|org|plo|sec",pw:"belau|co|ed|go|ne|or",ro:"arts|com|firm|info|nom|nt|org|rec|store|tm|www",rs:"ac|co|edu|gov|in|org",sb:"com|edu|gov|net|org",sc:"com|edu|gov|net|org",sh:"co|com|edu|gov|net|nom|org",sl:"com|edu|gov|net|org",st:"co|com|consulado|edu|embaixada|gov|mil|net|org|principe|saotome|store",sv:"com|edu|gob|org|red",sz:"ac|co|org",tr:"av|bbs|bel|biz|com|dr|edu|gen|gov|info|k12|name|net|org|pol|tel|tsk|tv|web",tt:"aero|biz|cat|co|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel",tw:"club|com|ebiz|edu|game|gov|idv|mil|net|org",mu:"ac|co|com|gov|net|or|org",mz:"ac|co|edu|gov|org",na:"co|com",nz:"ac|co|cri|geek|gen|govt|health|iwi|maori|mil|net|org|parliament|school",pa:"abo|ac|com|edu|gob|ing|med|net|nom|org|sld",pt:"com|edu|gov|int|net|nome|org|publ",py:"com|edu|gov|mil|net|org",qa:"com|edu|gov|mil|net|org",re:"asso|com|nom",ru:"ac|adygeya|altai|amur|arkhangelsk|astrakhan|bashkiria|belgorod|bir|bryansk|buryatia|cbg|chel|chelyabinsk|chita|chukotka|chuvashia|com|dagestan|e-burg|edu|gov|grozny|int|irkutsk|ivanovo|izhevsk|jar|joshkar-ola|kalmykia|kaluga|kamchatka|karelia|kazan|kchr|kemerovo|khabarovsk|khakassia|khv|kirov|koenig|komi|kostroma|kranoyarsk|kuban|kurgan|kursk|lipetsk|magadan|mari|mari-el|marine|mil|mordovia|mosreg|msk|murmansk|nalchik|net|nnov|nov|novosibirsk|nsk|omsk|orenburg|org|oryol|penza|perm|pp|pskov|ptz|rnd|ryazan|sakhalin|samara|saratov|simbirsk|smolensk|spb|stavropol|stv|surgut|tambov|tatarstan|tom|tomsk|tsaritsyn|tsk|tula|tuva|tver|tyumen|udm|udmurtia|ulan-ude|vladikavkaz|vladimir|vladivostok|volgograd|vologda|voronezh|vrn|vyatka|yakutia|yamal|yekaterinburg|yuzhno-sakhalinsk",rw:"ac|co|com|edu|gouv|gov|int|mil|net",sa:"com|edu|gov|med|net|org|pub|sch",sd:"com|edu|gov|info|med|net|org|tv",se:"a|ac|b|bd|c|d|e|f|g|h|i|k|l|m|n|o|org|p|parti|pp|press|r|s|t|tm|u|w|x|y|z",sg:"com|edu|gov|idn|net|org|per",sn:"art|com|edu|gouv|org|perso|univ",sy:"com|edu|gov|mil|net|news|org",th:"ac|co|go|in|mi|net|or",tj:"ac|biz|co|com|edu|go|gov|info|int|mil|name|net|nic|org|test|web",tn:"agrinet|com|defense|edunet|ens|fin|gov|ind|info|intl|mincom|nat|net|org|perso|rnrt|rns|rnu|tourism",tz:"ac|co|go|ne|or",ua:"biz|cherkassy|chernigov|chernovtsy|ck|cn|co|com|crimea|cv|dn|dnepropetrovsk|donetsk|dp|edu|gov|if|in|ivano-frankivsk|kh|kharkov|kherson|khmelnitskiy|kiev|kirovograd|km|kr|ks|kv|lg|lugansk|lutsk|lviv|me|mk|net|nikolaev|od|odessa|org|pl|poltava|pp|rovno|rv|sebastopol|sumy|te|ternopil|uzhgorod|vinnica|vn|zaporizhzhe|zhitomir|zp|zt",ug:"ac|co|go|ne|or|org|sc",uk:"ac|bl|british-library|co|cym|gov|govt|icnet|jet|lea|ltd|me|mil|mod|national-library-scotland|nel|net|nhs|nic|nls|org|orgn|parliament|plc|police|sch|scot|soc",us:"dni|fed|isa|kids|nsn",uy:"com|edu|gub|mil|net|org",ve:"co|com|edu|gob|info|mil|net|org|web",vi:"co|com|k12|net|org",vn:"ac|biz|com|edu|gov|health|info|int|name|net|org|pro",ye:"co|com|gov|ltd|me|net|org|plc",yu:"ac|co|edu|gov|org",za:"ac|agric|alt|bourse|city|co|cybernet|db|edu|gov|grondar|iaccess|imt|inca|landesign|law|mil|net|ngo|nis|nom|olivetti|org|pix|school|tm|web",zm:"ac|co|com|edu|gov|net|org|sch"},has_expression:null,is_expression:null,has:function(e){return!!e.match(r.has_expression)},is:function(e){return!!e.match(r.is_expression)},get:function(e){var t=e.match(r.has_expression);return t&&t[1]||null},noConflict:function(){return e.SecondLevelDomains===this&&(e.SecondLevelDomains=t),this},init:function(){var e="";for(var t in r.list){if(!n.call(r.list,t))continue;var i="("+r.list[t]+")."+t;e+="|("+i+")"}r.has_expression=new RegExp("\\.("+e.substr(1)+")$","i"),r.is_expression=new RegExp("^("+e.substr(1)+")$","i")}};return r.init(),r}),function(e,t){typeof exports=="object"?module.exports=t(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):typeof define=="function"&&define.amd?define("URI",["./punycode","./IPv6","./SecondLevelDomains"],t):e.URI=t(e.punycode,e.IPv6,e.SecondLevelDomains,e)}(this,function(e,t,n,r){function s(e,t){return this instanceof s?(e===undefined&&(typeof location!="undefined"?e=location.href+"":e=""),this.href(e),t!==undefined?this.absoluteTo(t):this):new s(e,t)}function a(e){return e.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function f(e){return e===undefined?"Undefined":String(Object.prototype.toString.call(e)).slice(8,-1)}function l(e){return f(e)==="Array"}function c(e,t){var n={},r,i;if(l(t))for(r=0,i=t.length;r<i;r++)n[t[r]]=!0;else n[t]=!0;for(r=0,i=e.length;r<i;r++)n[e[r]]!==undefined&&(e.splice(r,1),i--,r--);return e}function h(e,t){var n,r;if(l(t)){for(n=0,r=t.length;n<r;n++)if(!h(e,t[n]))return!1;return!0}var i=f(t);for(n=0,r=e.length;n<r;n++)if(i==="RegExp"){if(typeof e[n]=="string"&&e[n].match(t))return!0}else if(e[n]===t)return!0;return!1}function p(e,t){if(!l(e)||!l(t))return!1;if(e.length!==t.length)return!1;e.sort(),t.sort();for(var n=0,r=e.length;n<r;n++)if(e[n]!==t[n])return!1;return!0}function d(e){return escape(e)}function v(e){return encodeURIComponent(e).replace(/[!'()*]/g,d).replace(/\*/g,"%2A")}var i=r&&r.URI,o=s.prototype,u=Object.prototype.hasOwnProperty;s._parts=function(){return{protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null,duplicateQueryParameters:s.duplicateQueryParameters,escapeQuerySpace:s.escapeQuerySpace}},s.duplicateQueryParameters=!1,s.escapeQuerySpace=!0,s.protocol_expression=/^[a-z][a-z0-9-+-]*$/i,s.idn_expression=/[^a-z0-9\.-]/i,s.punycode_expression=/(xn--)/i,s.ip4_expression=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,s.ip6_expression=/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,s.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig,s.defaultPorts={http:"80",https:"443",ftp:"21",gopher:"70",ws:"80",wss:"443"},s.invalid_hostname_characters=/[^a-zA-Z0-9\.-]/,s.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src"},s.getDomAttribute=function(e){if(!e||!e.nodeName)return undefined;var t=e.nodeName.toLowerCase();return t==="input"&&e.type!=="image"?undefined:s.domAttributes[t]},s.encode=v,s.decode=decodeURIComponent,s.iso8859=function(){s.encode=escape,s.decode=unescape},s.unicode=function(){s.encode=v,s.decode=decodeURIComponent},s.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}}},s.encodeQuery=function(e,t){var n=s.encode(e+"");return t?n.replace(/%20/g,"+"):n},s.decodeQuery=function(e,t){e+="";try{return s.decode(t?e.replace(/\+/g,"%20"):e)}catch(n){return e}},s.recodePath=function(e){var t=(e+"").split("/");for(var n=0,r=t.length;n<r;n++)t[n]=s.encodePathSegment(s.decode(t[n]));return t.join("/")},s.decodePath=function(e){var t=(e+"").split("/");for(var n=0,r=t.length;n<r;n++)t[n]=s.decodePathSegment(t[n]);return t.join("/")};var m={encode:"encode",decode:"decode"},g,y=function(e,t){return function(n){return s[t](n+"").replace(s.characters[e][t].expression,function(n){return s.characters[e][t].map[n]})}};for(g in m)s[g+"PathSegment"]=y("pathname",m[g]);s.encodeReserved=y("reserved","encode"),s.parse=function(e,t){var n;return t||(t={}),n=e.indexOf("#"),n>-1&&(t.fragment=e.substring(n+1)||null,e=e.substring(0,n)),n=e.indexOf("?"),n>-1&&(t.query=e.substring(n+1)||null,e=e.substring(0,n)),e.substring(0,2)==="//"?(t.protocol=null,e=e.substring(2),e=s.parseAuthority(e,t)):(n=e.indexOf(":"),n>-1&&(t.protocol=e.substring(0,n)||null,t.protocol&&!t.protocol.match(s.protocol_expression)?t.protocol=undefined:t.protocol==="file"?e=e.substring(n+3):e.substring(n+1,n+3)==="//"?(e=e.substring(n+3),e=s.parseAuthority(e,t)):(e=e.substring(n+1),t.urn=!0))),t.path=e,t},s.parseHost=function(e,t){var n=e.indexOf("/"),r,i;return n===-1&&(n=e.length),e.charAt(0)==="["?(r=e.indexOf("]"),t.hostname=e.substring(1,r)||null,t.port=e.substring(r+2,n)||null):e.indexOf(":")!==e.lastIndexOf(":")?(t.hostname=e.substring(0,n)||null,t.port=null):(i=e.substring(0,n).split(":"),t.hostname=i[0]||null,t.port=i[1]||null),t.hostname&&e.substring(n).charAt(0)!=="/"&&(n++,e="/"+e),e.substring(n)||"/"},s.parseAuthority=function(e,t){return e=s.parseUserinfo(e,t),s.parseHost(e,t)},s.parseUserinfo=function(e,t){var n=e.indexOf("/"),r=n>-1?e.lastIndexOf("@",n):e.indexOf("@"),i;return r>-1&&(n===-1||r<n)?(i=e.substring(0,r).split(":"),t.username=i[0]?s.decode(i[0]):null,i.shift(),t.password=i[0]?s.decode(i.join(":")):null,e=e.substring(r+1)):(t.username=null,t.password=null),e},s.parseQuery=function(e,t){if(!e)return{};e=e.replace(/&+/g,"&").replace(/^\?*&*|&+$/g,"");if(!e)return{};var n={},r=e.split("&"),i=r.length,o,u,a;for(var f=0;f<i;f++)o=r[f].split("="),u=s.decodeQuery(o.shift(),t),a=o.length?s.decodeQuery(o.join("="),t):null,n[u]?(typeof n[u]=="string"&&(n[u]=[n[u]]),n[u].push(a)):n[u]=a;return n},s.build=function(e){var t="";return e.protocol&&(t+=e.protocol+":"),!e.urn&&(t||e.hostname)&&(t+="//"),t+=s.buildAuthority(e)||"",typeof e.path=="string"&&(e.path.charAt(0)!=="/"&&typeof e.hostname=="string"&&(t+="/"),t+=e.path),typeof e.query=="string"&&e.query&&(t+="?"+e.query),typeof e.fragment=="string"&&e.fragment&&(t+="#"+e.fragment),t},s.buildHost=function(e){var t="";return e.hostname?(s.ip6_expression.test(e.hostname)?e.port?t+="["+e.hostname+"]:"+e.port:t+=e.hostname:(t+=e.hostname,e.port&&(t+=":"+e.port)),t):""},s.buildAuthority=function(e){return s.buildUserinfo(e)+s.buildHost(e)},s.buildUserinfo=function(e){var t="";return e.username&&(t+=s.encode(e.username),e.password&&(t+=":"+s.encode(e.password)),t+="@"),t},s.buildQuery=function(e,t,n){var r="",i,o,a,f;for(o in e)if(u.call(e,o)&&o)if(l(e[o])){i={};for(a=0,f=e[o].length;a<f;a++)e[o][a]!==undefined&&i[e[o][a]+""]===undefined&&(r+="&"+s.buildQueryParameter(o,e[o][a],n),t!==!0&&(i[e[o][a]+""]=!0))}else e[o]!==undefined&&(r+="&"+s.buildQueryParameter(o,e[o],n));return r.substring(1)},s.buildQueryParameter=function(e,t,n){return s.encodeQuery(e,n)+(t!==null?"="+s.encodeQuery(t,n):"")},s.addQuery=function(e,t,n){if(typeof t=="object")for(var r in t)u.call(t,r)&&s.addQuery(e,r,t[r]);else{if(typeof t!="string")throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");if(e[t]===undefined){e[t]=n;return}typeof e[t]=="string"&&(e[t]=[e[t]]),l(n)||(n=[n]),e[t]=e[t].concat(n)}},s.removeQuery=function(e,t,n){var r,i,o;if(l(t))for(r=0,i=t.length;r<i;r++)e[t[r]]=undefined;else if(typeof t=="object")for(o in t)u.call(t,o)&&s.removeQuery(e,o,t[o]);else{if(typeof t!="string")throw new TypeError("URI.addQuery() accepts an object, string as the first parameter");n!==undefined?e[t]===n?e[t]=undefined:l(e[t])&&(e[t]=c(e[t],n)):e[t]=undefined}},s.hasQuery=function(e,t,n,r){if(typeof t=="object"){for(var i in t)if(u.call(t,i)&&!s.hasQuery(e,i,t[i]))return!1;return!0}if(typeof t!="string")throw new TypeError("URI.hasQuery() accepts an object, string as the name parameter");switch(f(n)){case"Undefined":return t in e;case"Boolean":var o=Boolean(l(e[t])?e[t].length:e[t]);return n===o;case"Function":return!!n(e[t],t,e);case"Array":if(!l(e[t]))return!1;var a=r?h:p;return a(e[t],n);case"RegExp":if(!l(e[t]))return Boolean(e[t]&&e[t].match(n));if(!r)return!1;return h(e[t],n);case"Number":n=String(n);case"String":if(!l(e[t]))return e[t]===n;if(!r)return!1;return h(e[t],n);default:throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter")}},s.commonPath=function(e,t){var n=Math.min(e.length,t.length),r;for(r=0;r<n;r++)if(e.charAt(r)!==t.charAt(r)){r--;break}if(r<1)return e.charAt(0)===t.charAt(0)&&e.charAt(0)==="/"?"/":"";if(e.charAt(r)!=="/"||t.charAt(r)!=="/")r=e.substring(0,r).lastIndexOf("/");return e.substring(0,r+1)},s.withinString=function(e,t){return e.replace(s.find_uri_expression,t)},s.ensureValidHostname=function(t){if(t.match(s.invalid_hostname_characters)){if(!e)throw new TypeError("Hostname '"+t+"' contains characters other than [A-Z0-9.-] and Punycode.js is not available");if(e.toASCII(t).match(s.invalid_hostname_characters))throw new TypeError("Hostname '"+t+"' contains characters other than [A-Z0-9.-]")}},s.noConflict=function(e){if(e){var n={URI:this.noConflict()};return URITemplate&&typeof URITemplate.noConflict=="function"&&(n.URITemplate=URITemplate.noConflict()),t&&typeof t.noConflict=="function"&&(n.IPv6=t.noConflict()),SecondLevelDomains&&typeof SecondLevelDomains.noConflict=="function"&&(n.SecondLevelDomains=SecondLevelDomains.noConflict()),n}return r.URI===this&&(r.URI=i),this},o.build=function(e){if(e===!0)this._deferred_build=!0;else if(e===undefined||this._deferred_build)this._string=s.build(this._parts),this._deferred_build=!1;return this},o.clone=function(){return new s(this)},o.valueOf=o.toString=function(){return this.build(!1)._string},m={protocol:"protocol",username:"username",password:"password",hostname:"hostname",port:"port"},y=function(e){return function(t,n){return t===undefined?this._parts[e]||"":(this._parts[e]=t||null,this.build(!n),this)}};for(g in m)o[g]=y(m[g]);m={query:"?",fragment:"#"},y=function(e,t){return function(n,r){return n===undefined?this._parts[e]||"":(n!==null&&(n+="",n.charAt(0)===t&&(n=n.substring(1))),this._parts[e]=n,this.build(!r),this)}};for(g in m)o[g]=y(g,m[g]);m={search:["?","query"],hash:["#","fragment"]},y=function(e,t){return function(n,r){var i=this[e](n,r);return typeof i=="string"&&i.length?t+i:i}};for(g in m)o[g]=y(m[g][1],m[g][0]);o.pathname=function(e,t){if(e===undefined||e===!0){var n=this._parts.path||(this._parts.hostname?"/":"");return e?s.decodePath(n):n}return this._parts.path=e?s.recodePath(e):"/",this.build(!t),this},o.path=o.pathname,o.href=function(e,t){var n;if(e===undefined)return this.toString();this._string="",this._parts=s._parts();var r=e instanceof s,i=typeof e=="object"&&(e.hostname||e.path||e.pathname);if(e.nodeName){var o=s.getDomAttribute(e);e=e[o]||"",i=!1}!r&&i&&e.pathname!==undefined&&(e=e.toString());if(typeof e=="string")this._parts=s.parse(e,this._parts);else{if(!r&&!i)throw new TypeError("invalid input");var a=r?e._parts:e;for(n in a)u.call(this._parts,n)&&(this._parts[n]=a[n])}return this.build(!t),this},o.is=function(e){var t=!1,r=!1,i=!1,o=!1,u=!1,a=!1,f=!1,l=!this._parts.urn;this._parts.hostname&&(l=!1,r=s.ip4_expression.test(this._parts.hostname),i=s.ip6_expression.test(this._parts.hostname),t=r||i,o=!t,u=o&&n&&n.has(this._parts.hostname),a=o&&s.idn_expression.test(this._parts.hostname),f=o&&s.punycode_expression.test(this._parts.hostname));switch(e.toLowerCase()){case"relative":return l;case"absolute":return!l;case"domain":case"name":return o;case"sld":return u;case"ip":return t;case"ip4":case"ipv4":case"inet4":return r;case"ip6":case"ipv6":case"inet6":return i;case"idn":return a;case"url":return!this._parts.urn;case"urn":return!!this._parts.urn;case"punycode":return f}return null};var b=o.protocol,w=o.port,E=o.hostname;o.protocol=function(e,t){if(e!==undefined&&e){e=e.replace(/:(\/\/)?$/,"");if(e.match(/[^a-zA-z0-9\.+-]/))throw new TypeError("Protocol '"+e+"' contains characters other than [A-Z0-9.+-]")}return b.call(this,e,t)},o.scheme=o.protocol,o.port=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e!==undefined){e===0&&(e=null);if(e){e+="",e.charAt(0)===":"&&(e=e.substring(1));if(e.match(/[^0-9]/))throw new TypeError("Port '"+e+"' contains characters other than [0-9]")}}return w.call(this,e,t)},o.hostname=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e!==undefined){var n={};s.parseHost(e,n),e=n.hostname}return E.call(this,e,t)},o.host=function(e,t){return this._parts.urn?e===undefined?"":this:e===undefined?this._parts.hostname?s.buildHost(this._parts):"":(s.parseHost(e,this._parts),this.build(!t),this)},o.authority=function(e,t){return this._parts.urn?e===undefined?"":this:e===undefined?this._parts.hostname?s.buildAuthority(this._parts):"":(s.parseAuthority(e,this._parts),this.build(!t),this)},o.userinfo=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e===undefined){if(!this._parts.username)return"";var n=s.buildUserinfo(this._parts);return n.substring(0,n.length-1)}return e[e.length-1]!=="@"&&(e+="@"),s.parseUserinfo(e,this._parts),this.build(!t),this},o.resource=function(e,t){var n;return e===undefined?this.path()+this.search()+this.hash():(n=s.parse(e),this._parts.path=n.path,this._parts.query=n.query,this._parts.fragment=n.fragment,this.build(!t),this)},o.subdomain=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e===undefined){if(!this._parts.hostname||this.is("IP"))return"";var n=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0,n)||""}var r=this._parts.hostname.length-this.domain().length,i=this._parts.hostname.substring(0,r),o=new RegExp("^"+a(i));return e&&e.charAt(e.length-1)!=="."&&(e+="."),e&&s.ensureValidHostname(e),this._parts.hostname=this._parts.hostname.replace(o,e),this.build(!t),this},o.domain=function(e,t){if(this._parts.urn)return e===undefined?"":this;typeof e=="boolean"&&(t=e,e=undefined);if(e===undefined){if(!this._parts.hostname||this.is("IP"))return"";var n=this._parts.hostname.match(/\./g);if(n&&n.length<2)return this._parts.hostname;var r=this._parts.hostname.length-this.tld(t).length-1;return r=this._parts.hostname.lastIndexOf(".",r-1)+1,this._parts.hostname.substring(r)||""}if(!e)throw new TypeError("cannot set domain empty");s.ensureValidHostname(e);if(!this._parts.hostname||this.is("IP"))this._parts.hostname=e;else{var i=new RegExp(a(this.domain())+"$");this._parts.hostname=this._parts.hostname.replace(i,e)}return this.build(!t),this},o.tld=function(e,t){if(this._parts.urn)return e===undefined?"":this;typeof e=="boolean"&&(t=e,e=undefined);if(e===undefined){if(!this._parts.hostname||this.is("IP"))return"";var r=this._parts.hostname.lastIndexOf("."),i=this._parts.hostname.substring(r+1);return t!==!0&&n&&n.list[i.toLowerCase()]?n.get(this._parts.hostname)||i:i}var s;if(!e)throw new TypeError("cannot set TLD empty");if(e.match(/[^a-zA-Z0-9-]/)){if(!n||!n.is(e))throw new TypeError("TLD '"+e+"' contains characters other than [A-Z0-9]");s=new RegExp(a(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(s,e)}else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");s=new RegExp(a(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(s,e)}return this.build(!t),this},o.directory=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e===undefined||e===!0){if(!this._parts.path&&!this._parts.hostname)return"";if(this._parts.path==="/")return"/";var n=this._parts.path.length-this.filename().length-1,r=this._parts.path.substring(0,n)||(this._parts.hostname?"/":"");return e?s.decodePath(r):r}var i=this._parts.path.length-this.filename().length,o=this._parts.path.substring(0,i),u=new RegExp("^"+a(o));return this.is("relative")||(e||(e="/"),e.charAt(0)!=="/"&&(e="/"+e)),e&&e.charAt(e.length-1)!=="/"&&(e+="/"),e=s.recodePath(e),this._parts.path=this._parts.path.replace(u,e),this.build(!t),this},o.filename=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e===undefined||e===!0){if(!this._parts.path||this._parts.path==="/")return"";var n=this._parts.path.lastIndexOf("/"),r=this._parts.path.substring(n+1);return e?s.decodePathSegment(r):r}var i=!1;e.charAt(0)==="/"&&(e=e.substring(1)),e.match(/\.?\//)&&(i=!0);var o=new RegExp(a(this.filename())+"$");return e=s.recodePath(e),this._parts.path=this._parts.path.replace(o,e),i?this.normalizePath(t):this.build(!t),this},o.suffix=function(e,t){if(this._parts.urn)return e===undefined?"":this;if(e===undefined||e===!0){if(!this._parts.path||this._parts.path==="/")return"";var n=this.filename(),r=n.lastIndexOf("."),i,o;return r===-1?"":(i=n.substring(r+1),o=/^[a-z0-9%]+$/i.test(i)?i:"",e?s.decodePathSegment(o):o)}e.charAt(0)==="."&&(e=e.substring(1));var u=this.suffix(),f;if(!u){if(!e)return this;this._parts.path+="."+s.recodePath(e)}else e?f=new RegExp(a(u)+"$"):f=new RegExp(a("."+u)+"$");return f&&(e=s.recodePath(e),this._parts.path=this._parts.path.replace(f,e)),this.build(!t),this},o.segment=function(e,t,n){var r=this._parts.urn?":":"/",i=this.path(),s=i.substring(0,1)==="/",o=i.split(r);e!==undefined&&typeof e!="number"&&(n=t,t=e,e=undefined);if(e!==undefined&&typeof e!="number")throw new Error("Bad segment '"+e+"', must be 0-based integer");s&&o.shift(),e<0&&(e=Math.max(o.length+e,0));if(t===undefined)return e===undefined?o:o[e];if(e===null||o[e]===undefined){if(l(t)){o=[];for(var u=0,a=t.length;u<a;u++){if(!t[u].length&&(!o.length||!o[o.length-1].length))continue;o.length&&!o[o.length-1].length&&o.pop(),o.push(t[u])}}else if(t||typeof t=="string")o[o.length-1]===""?o[o.length-1]=t:o.push(t)}else t||typeof t=="string"&&t.length?o[e]=t:o.splice(e,1);return s&&o.unshift(""),this.path(o.join(r),n)},o.segmentCoded=function(e,t,n){var r,i,o;typeof e!="number"&&(n=t,t=e,e=undefined);if(t===undefined){r=this.segment(e,t,n);if(!l(r))r=r!==undefined?s.decode(r):undefined;else for(i=0,o=r.length;i<o;i++)r[i]=s.decode(r[i]);return r}if(!l(t))t=typeof t=="string"?s.encode(t):t;else for(i=0,o=t.length;i<o;i++)t[i]=s.decode(t[i]);return this.segment(e,t,n)};var S=o.query;return o.query=function(e,t){if(e===!0)return s.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if(typeof e=="function"){var n=s.parseQuery(this._parts.query,this._parts.escapeQuerySpace),r=e.call(this,n);return this._parts.query=s.buildQuery(r||n,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),this.build(!t),this}return e!==undefined&&typeof e!="string"?(this._parts.query=s.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),this.build(!t),this):S.call(this,e,t)},o.setQuery=function(e,t,n){var r=s.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if(typeof e=="object")for(var i in e)u.call(e,i)&&(r[i]=e[i]);else{if(typeof e!="string")throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");r[e]=t!==undefined?t:null}return this._parts.query=s.buildQuery(r,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),typeof e!="string"&&(n=t),this.build(!n),this},o.addQuery=function(e,t,n){var r=s.parseQuery(this._parts.query,this._parts.escapeQuerySpace);return s.addQuery(r,e,t===undefined?null:t),this._parts.query=s.buildQuery(r,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),typeof e!="string"&&(n=t),this.build(!n),this},o.removeQuery=function(e,t,n){var r=s.parseQuery(this._parts.query,this._parts.escapeQuerySpace);return s.removeQuery(r,e,t),this._parts.query=s.buildQuery(r,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),typeof e!="string"&&(n=t),this.build(!n),this},o.hasQuery=function(e,t,n){var r=s.parseQuery(this._parts.query,this._parts.escapeQuerySpace);return s.hasQuery(r,e,t,n)},o.setSearch=o.setQuery,o.addSearch=o.addQuery,o.removeSearch=o.removeQuery,o.hasSearch=o.hasQuery,o.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizeQuery(!1).normalizeFragment(!1).build():this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()},o.normalizeProtocol=function(e){return typeof this._parts.protocol=="string"&&(this._parts.protocol=this._parts.protocol.toLowerCase(),this.build(!e)),this},o.normalizeHostname=function(n){return this._parts.hostname&&(this.is("IDN")&&e?this._parts.hostname=e.toASCII(this._parts.hostname):this.is("IPv6")&&t&&(this._parts.hostname=t.best(this._parts.hostname)),this._parts.hostname=this._parts.hostname.toLowerCase(),this.build(!n)),this},o.normalizePort=function(e){return typeof this._parts.protocol=="string"&&this._parts.port===s.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!e)),this},o.normalizePath=function(e){if(this._parts.urn)return this;if(!this._parts.path||this._parts.path==="/")return this;var t,n=this._parts.path,r,i;n.charAt(0)!=="/"&&(t=!0,n="/"+n),n=n.replace(/(\/(\.\/)+)|(\/\.$)/g,"/").replace(/\/{2,}/g,"/");for(;;){r=n.indexOf("/../");if(r===-1)break;if(r===0){n=n.substring(3);break}i=n.substring(0,r).lastIndexOf("/"),i===-1&&(i=r),n=n.substring(0,i)+n.substring(r+3)}return t&&this.is("relative")&&(n=n.substring(1)),n=s.recodePath(n),this._parts.path=n,this.build(!e),this},o.normalizePathname=o.normalizePath,o.normalizeQuery=function(e){return typeof this._parts.query=="string"&&(this._parts.query.length?this.query(s.parseQuery(this._parts.query,this._parts.escapeQuerySpace)):this._parts.query=null,this.build(!e)),this},o.normalizeFragment=function(e){return this._parts.fragment||(this._parts.fragment=null,this.build(!e)),this},o.normalizeSearch=o.normalizeQuery,o.normalizeHash=o.normalizeFragment,o.iso8859=function(){var e=s.encode,t=s.decode;return s.encode=escape,s.decode=decodeURIComponent,this.normalize(),s.encode=e,s.decode=t,this},o.unicode=function(){var e=s.encode,t=s.decode;return s.encode=v,s.decode=unescape,this.normalize(),s.encode=e,s.decode=t,this},o.readable=function(){var t=this.clone();t.username("").password("").normalize();var n="";t._parts.protocol&&(n+=t._parts.protocol+"://"),t._parts.hostname&&(t.is("punycode")&&e?(n+=e.toUnicode(t._parts.hostname),t._parts.port&&(n+=":"+t._parts.port)):n+=t.host()),t._parts.hostname&&t._parts.path&&t._parts.path.charAt(0)!=="/"&&(n+="/"),n+=t.path(!0);if(t._parts.query){var r="";for(var i=0,o=t._parts.query.split("&"),u=o.length;i<u;i++){var a=(o[i]||"").split("=");r+="&"+s.decodeQuery(a[0],this._parts.escapeQuerySpace).replace(/&/g,"%26"),a[1]!==undefined&&(r+="="+s.decodeQuery(a[1],this._parts.escapeQuerySpace).replace(/&/g,"%26"))}n+="?"+r.substring(1)}return n+=s.decodeQuery(t.hash(),!0),n},o.absoluteTo=function(e){var t=this.clone(),n=["protocol","username","password","hostname","port"],r,i,o;if(this._parts.urn)throw new Error("URNs do not have any generally defined hierarchical components");e instanceof s||(e=new s(e)),t._parts.protocol||(t._parts.protocol=e._parts.protocol);if(this._parts.hostname)return t;for(i=0;o=n[i];i++)t._parts[o]=e._parts[o];n=["query","path"];for(i=0;o=n[i];i++)!t._parts[o]&&e._parts[o]&&(t._parts[o]=e._parts[o]);return t.path().charAt(0)!=="/"&&(r=e.directory(),t._parts.path=(r?r+"/":"")+t._parts.path,t.normalizePath()),t.build(),t},o.relativeTo=function(e){var t=this.clone().normalize(),n,r,i,o,u;if(t._parts.urn)throw new Error("URNs do not have any generally defined hierarchical components");e=(new s(e)).normalize(),n=t._parts,r=e._parts,o=t.path(),u=e.path();if(o.charAt(0)!=="/")throw new Error("URI is already relative");if(u.charAt(0)!=="/")throw new Error("Cannot calculate a URI relative to another relative URI");n.protocol===r.protocol&&(n.protocol=null);if(n.username!==r.username||n.password!==r.password)return t.build();if(n.protocol!==null||n.username!==null||n.password!==null)return t.build();if(n.hostname!==r.hostname||n.port!==r.port)return t.build();n.hostname=null,n.port=null;if(o===u)return n.path="",t.build();i=s.commonPath(t.path(),e.path());if(!i)return t.build();var a=r.path.substring(i.length).replace(/[^\/]*$/,"").replace(/.*?\//g,"../");return n.path=a+n.path.substring(i.length),t.build()},o.equals=function(e){var t=this.clone(),n=new s(e),r={},i={},o={},a,f,c;t.normalize(),n.normalize();if(t.toString()===n.toString())return!0;a=t.query(),f=n.query(),t.query(""),n.query("");if(t.toString()!==n.toString())return!1;if(a.length!==f.length)return!1;r=s.parseQuery(a,this._parts.escapeQuerySpace),i=s.parseQuery(f,this._parts.escapeQuerySpace);for(c in r)if(u.call(r,c)){if(!l(r[c])){if(r[c]!==i[c])return!1}else if(!p(r[c],i[c]))return!1;o[c]=!0}for(c in i)if(u.call(i,c)&&!o[c])return!1;return!0},o.duplicateQueryParameters=function(e){return this._parts.duplicateQueryParameters=!!e,this},o.escapeQuerySpace=function(e){return this._parts.escapeQuerySpace=!!e,this},s});
\ No newline at end of file
/*! 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
......@@ -18,7 +18,7 @@
-e git+https://github.com/edx/XBlock.git@fa88607#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.6#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.1.4#egg=js_test_tool
-e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool
-e git+https://github.com/edx/django-waffle.git@823a102e48#egg=django-waffle
-e git+https://github.com/edx/event-tracking.git@f0211d702d#egg=event-tracking
-e git+https://github.com/edx/bok-choy.git@bc6f1adbe439618162079f1004b2b3db3b6f8916#egg=bok_choy
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