Create backbone models/views for assets.
cleanup Updates. Testing changes. Testing changes. asset-index progress state cleanjup. cleanup
This commit is contained in:
11
cms/static/coffee/spec/models/asset_spec.coffee
Normal file
11
cms/static/coffee/spec/models/asset_spec.coffee
Normal file
@@ -0,0 +1,11 @@
|
||||
describe "CMS.Models.Asset", ->
|
||||
beforeEach ->
|
||||
CMS.URL.UPDATE_ASSET = "/update_asset/"
|
||||
@model = new CMS.Models.Asset({id: "/c4x/id"})
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.UPDATE_ASSET
|
||||
|
||||
it "should have a url set", ->
|
||||
expect(@model.url()).toEqual("/update_asset//c4x/id")
|
||||
|
||||
133
cms/static/coffee/spec/views/assets_spec.coffee
Normal file
133
cms/static/coffee/spec/views/assets_spec.coffee
Normal file
@@ -0,0 +1,133 @@
|
||||
feedbackTpl = readFixtures('system-feedback.underscore')
|
||||
assetTpl = readFixtures('asset.underscore')
|
||||
|
||||
describe "CMS.Views.Asset", ->
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
|
||||
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}))
|
||||
@model = new CMS.Models.Asset({display_name: "test asset", url: 'actual_asset_url', portable_url: 'portable_url', date_added: 'date', thumbnail: null, id: 'id'})
|
||||
spyOn(@model, "destroy").andCallThrough()
|
||||
@collection = new CMS.Models.AssetCollection([@model])
|
||||
@view = new CMS.Views.Asset({model: @model})
|
||||
|
||||
@promptSpies = spyOnConstructor(CMS.Views.Prompt, "Warning", ["show", "hide"])
|
||||
@promptSpies.show.andReturn(@promptSpies)
|
||||
|
||||
describe "Basic", ->
|
||||
it "should render properly", ->
|
||||
@view.render()
|
||||
expect(@view.$el).toContainText("test asset")
|
||||
|
||||
it "should pop a delete confirmation when the delete button is clicked", ->
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
expect(@promptSpies.constructor).toHaveBeenCalled()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
expect(ctorOptions.title).toMatch('Delete File Confirmation')
|
||||
# hasn't actually been removed
|
||||
expect(@model.destroy).not.toHaveBeenCalled()
|
||||
expect(@collection).toContain(@model)
|
||||
|
||||
describe "AJAX", ->
|
||||
beforeEach ->
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
@savingSpies = spyOnConstructor(CMS.Views.Notification, "Confirmation", ["show"])
|
||||
@savingSpies.show.andReturn(@savingSpies)
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
|
||||
it "should destroy itself on confirmation", ->
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
expect(@requests.length).toEqual(1)
|
||||
expect(@savingSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
# return a success response
|
||||
@requests[0].respond(200)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
|
||||
expect(savingOptions.title).toMatch("Your file has been deleted.")
|
||||
expect(@collection.contains(@model)).toBeFalsy()
|
||||
|
||||
it "should not destroy itself if server errors", ->
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
# return an error response
|
||||
@requests[0].respond(404)
|
||||
expect(@savingSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
|
||||
|
||||
describe "CMS.Views.Assets", ->
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
appendSetFixtures(sandbox({id: "asset_table_body"}))
|
||||
@collection = new CMS.Models.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'}
|
||||
])
|
||||
@view = new CMS.Views.Assets({collection: @collection, el: $('#asset_table_body')})
|
||||
|
||||
@promptSpies = spyOnConstructor(CMS.Views.Prompt, "Warning", ["show", "hide"])
|
||||
@promptSpies.show.andReturn(@promptSpies)
|
||||
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
|
||||
describe "Basic", ->
|
||||
it "should render both assets", ->
|
||||
@view.render()
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).toContainText("test asset 2")
|
||||
|
||||
it "should remove the deleted asset from the view", ->
|
||||
# Delete the 2nd asset with success from server.
|
||||
@view.render().$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
|
||||
@requests[0].respond(200)
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).not.toContainText("test asset 2")
|
||||
|
||||
it "does not remove asset if deletion failed", ->
|
||||
# Delete the 2nd asset, but mimic a failure from the server.
|
||||
@view.render().$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
|
||||
@requests[0].respond(404)
|
||||
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()
|
||||
model = new CMS.Models.Asset({display_name: "new asset", url: 'new_actual_asset_url', portable_url: 'portable_url', date_added: 'date', thumbnail: null, id: 'idx'})
|
||||
@view.addAsset(model)
|
||||
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()
|
||||
spyOn(@collection, "add").andCallThrough()
|
||||
model = @collection.models[1]
|
||||
@view.addAsset(model)
|
||||
expect(@collection.add).not.toHaveBeenCalled()
|
||||
@@ -11,15 +11,11 @@ describe "CMS.Views.UploadDialog", ->
|
||||
@model = new CMS.Models.FileUpload(
|
||||
mimeTypes: ['application/pdf']
|
||||
)
|
||||
@chapter = new CMS.Models.Chapter()
|
||||
@dialogResponse = dialogResponse = []
|
||||
@view = new CMS.Views.UploadDialog(
|
||||
model: @model,
|
||||
onSuccess: (response) =>
|
||||
options = {}
|
||||
if !@chapter.get('name')
|
||||
options.name = response.displayname
|
||||
options.asset_path = response.url
|
||||
@chapter.set(options)
|
||||
dialogResponse.push(response.response)
|
||||
)
|
||||
spyOn(@view, 'remove').andCallThrough()
|
||||
|
||||
@@ -66,7 +62,6 @@ describe "CMS.Views.UploadDialog", ->
|
||||
expect(@view.$el).toContain("#upload_error")
|
||||
expect(@view.$(".action-upload")).toHaveClass("disabled")
|
||||
|
||||
|
||||
it "adds body class on show()", ->
|
||||
@view.show()
|
||||
expect(@view.options.shown).toBeTruthy()
|
||||
@@ -99,11 +94,10 @@ describe "CMS.Views.UploadDialog", ->
|
||||
expect(request.method).toEqual("POST")
|
||||
|
||||
request.respond(200, {"Content-Type": "application/json"},
|
||||
'{"displayname": "starfish", "url": "/uploaded/starfish.pdf"}')
|
||||
'{"response": "dummy_response"}')
|
||||
expect(@model.get("uploading")).toBeFalsy()
|
||||
expect(@model.get("finished")).toBeTruthy()
|
||||
expect(@chapter.get("name")).toEqual("starfish")
|
||||
expect(@chapter.get("asset_path")).toEqual("/uploaded/starfish.pdf")
|
||||
expect(@dialogResponse.pop()).toEqual("dummy_response")
|
||||
|
||||
it "can handle upload errors", ->
|
||||
@view.upload()
|
||||
@@ -114,7 +108,7 @@ describe "CMS.Views.UploadDialog", ->
|
||||
it "removes itself after two seconds on successful upload", ->
|
||||
@view.upload()
|
||||
@requests[0].respond(200, {"Content-Type": "application/json"},
|
||||
'{"displayname": "starfish", "url": "/uploaded/starfish.pdf"}')
|
||||
'{"response": "dummy_response"}')
|
||||
expect(@view.remove).not.toHaveBeenCalled()
|
||||
@clock.tick(2001)
|
||||
expect(@view.remove).toHaveBeenCalled()
|
||||
|
||||
@@ -421,11 +421,6 @@ function _deleteItem($el, type) {
|
||||
confirm.show();
|
||||
}
|
||||
|
||||
function markAsLoaded() {
|
||||
$('.upload-modal .copy-button').css('display', 'inline-block');
|
||||
$('.upload-modal .progress-bar').addClass('loaded');
|
||||
}
|
||||
|
||||
function hideModal(e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
||||
17
cms/static/js/models/asset.js
Normal file
17
cms/static/js/models/asset.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Simple model for an asset.
|
||||
*/
|
||||
CMS.Models.Asset = Backbone.Model.extend({
|
||||
defaults: {
|
||||
display_name: "",
|
||||
thumbnail: "",
|
||||
date_added: "",
|
||||
url: "",
|
||||
portable_url: "",
|
||||
is_locked: false
|
||||
},
|
||||
|
||||
url: function() {
|
||||
return CMS.URL.UPDATE_ASSET + this.id;
|
||||
}
|
||||
});
|
||||
3
cms/static/js/models/assets.js
Normal file
3
cms/static/js/models/assets.js
Normal file
@@ -0,0 +1,3 @@
|
||||
CMS.Models.AssetCollection = Backbone.Collection.extend({
|
||||
model : CMS.Models.Asset
|
||||
});
|
||||
57
cms/static/js/views/asset_view.js
Normal file
57
cms/static/js/views/asset_view.js
Normal file
@@ -0,0 +1,57 @@
|
||||
CMS.Views.Asset = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
this.template = _.template($("#asset-tpl").text());
|
||||
},
|
||||
|
||||
tagName: "tr",
|
||||
|
||||
events: {
|
||||
"click .remove-asset-button": "confirmDelete"
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template({
|
||||
display_name: this.model.get('display_name'),
|
||||
thumbnail: this.model.get('thumbnail'),
|
||||
date_added: this.model.get('date_added'),
|
||||
url: this.model.get('url'),
|
||||
portable_url: this.model.get('portable_url')}));
|
||||
return this;
|
||||
},
|
||||
|
||||
confirmDelete: function(e) {
|
||||
if(e && e.preventDefault) { e.preventDefault(); }
|
||||
var asset = this.model, collection = this.model.collection;
|
||||
new CMS.Views.Prompt.Warning({
|
||||
title: gettext("Delete File Confirmation"),
|
||||
message: gettext("Are you sure you wish to delete this item. It cannot be reversed!\n\nAlso any content that links/refers to this item will no longer work (e.g. broken images and/or links)"),
|
||||
actions: {
|
||||
primary: {
|
||||
text: gettext("Delete"),
|
||||
click: function (view) {
|
||||
view.hide();
|
||||
asset.destroy({
|
||||
wait: true, // Don't remove the asset from the collection until successful.
|
||||
success: function () {
|
||||
new CMS.Views.Notification.Confirmation({
|
||||
title: gettext("Your file has been deleted."),
|
||||
closeIcon: false,
|
||||
maxShown: 2000
|
||||
}).show()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
text: gettext("Cancel"),
|
||||
click: function (view) {
|
||||
view.hide();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
});
|
||||
@@ -1,147 +0,0 @@
|
||||
$(document).ready(function() {
|
||||
$('.uploads .upload-button').bind('click', showUploadModal);
|
||||
$('.upload-modal .close-button').bind('click', hideModal);
|
||||
$('.upload-modal .choose-file-button').bind('click', showFileSelectionMenu);
|
||||
$('.remove-asset-button').bind('click', removeAsset);
|
||||
});
|
||||
|
||||
function removeAsset(e){
|
||||
e.preventDefault();
|
||||
|
||||
var that = this;
|
||||
var msg = new CMS.Views.Prompt.Warning({
|
||||
title: gettext("Delete File Confirmation"),
|
||||
message: gettext("Are you sure you wish to delete this item. It cannot be reversed!\n\nAlso any content that links/refers to this item will no longer work (e.g. broken images and/or links)"),
|
||||
actions: {
|
||||
primary: {
|
||||
text: gettext("OK"),
|
||||
click: function(view) {
|
||||
// call the back-end to actually remove the asset
|
||||
var url = $('.asset-library').data('remove-asset-callback-url');
|
||||
var row = $(that).closest('tr');
|
||||
$.post(url,
|
||||
{ 'location': row.data('id') },
|
||||
function() {
|
||||
// show the post-commit confirmation
|
||||
var deleted = new CMS.Views.Notification.Confirmation({
|
||||
title: gettext("Your file has been deleted."),
|
||||
closeIcon: false,
|
||||
maxShown: 2000
|
||||
});
|
||||
deleted.show();
|
||||
row.remove();
|
||||
analytics.track('Deleted Asset', {
|
||||
'course': course_location_analytics,
|
||||
'id': row.data('id')
|
||||
});
|
||||
}
|
||||
);
|
||||
view.hide();
|
||||
}
|
||||
},
|
||||
secondary: [{
|
||||
text: gettext("Cancel"),
|
||||
click: function(view) {
|
||||
view.hide();
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
return msg.show();
|
||||
}
|
||||
|
||||
function showUploadModal(e) {
|
||||
e.preventDefault();
|
||||
resetUploadModal();
|
||||
$modal = $('.upload-modal').show();
|
||||
$('.upload-modal .file-chooser').fileupload({
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
maxChunkSize: 100 * 1000 * 1000, // 100 MB
|
||||
autoUpload: true,
|
||||
progressall: function(e, data) {
|
||||
var percentComplete = parseInt((100 * data.loaded) / data.total, 10);
|
||||
showUploadFeedback(e, percentComplete);
|
||||
},
|
||||
maxFileSize: 100 * 1000 * 1000, // 100 MB
|
||||
maxNumberofFiles: 100,
|
||||
add: function(e, data) {
|
||||
data.process().done(function () {
|
||||
data.submit();
|
||||
});
|
||||
},
|
||||
done: function(e, data) {
|
||||
displayFinishedUpload(data.result);
|
||||
}
|
||||
|
||||
});
|
||||
$('.file-input').bind('change', startUpload);
|
||||
$modalCover.show();
|
||||
}
|
||||
|
||||
function showFileSelectionMenu(e) {
|
||||
e.preventDefault();
|
||||
$('.file-input').click();
|
||||
}
|
||||
|
||||
function startUpload(e) {
|
||||
var files = $('.file-input').get(0).files;
|
||||
if (files.length === 0)
|
||||
return;
|
||||
|
||||
$('.upload-modal h1').html(gettext('Uploading…'));
|
||||
$('.upload-modal .file-name').html(files[0].name);
|
||||
$('.upload-modal .choose-file-button').hide();
|
||||
$('.upload-modal .progress-bar').removeClass('loaded').show();
|
||||
}
|
||||
|
||||
function resetUploadBar() {
|
||||
var percentVal = '0%';
|
||||
$('.upload-modal .progress-fill').width(percentVal);
|
||||
$('.upload-modal .progress-fill').html(percentVal);
|
||||
}
|
||||
|
||||
function resetUploadModal() {
|
||||
// Reset modal so it no longer displays information about previously
|
||||
// completed uploads.
|
||||
resetUploadBar();
|
||||
$('.upload-modal .file-name').html('');
|
||||
$('.upload-modal h1').html(gettext('Upload New File'));
|
||||
$('.upload-modal .choose-file-button').html(gettext('Choose File'));
|
||||
$('.upload-modal .embeddable-xml-input').val('');
|
||||
$('.upload-modal .embeddable').hide();
|
||||
}
|
||||
|
||||
function showUploadFeedback(event, percentComplete) {
|
||||
var percentVal = percentComplete + '%';
|
||||
$('.upload-modal .progress-fill').width(percentVal);
|
||||
$('.upload-modal .progress-fill').html(percentVal);
|
||||
}
|
||||
|
||||
function displayFinishedUpload(resp) {
|
||||
if (resp.status == 200) {
|
||||
markAsLoaded();
|
||||
}
|
||||
|
||||
$('.upload-modal .embeddable-xml-input').val(resp.portable_url);
|
||||
$('.upload-modal .embeddable').show();
|
||||
$('.upload-modal .file-name').hide();
|
||||
$('.upload-modal .progress-fill').html(resp.msg);
|
||||
$('.upload-modal .choose-file-button').html(gettext('Load Another File')).show();
|
||||
$('.upload-modal .progress-fill').width('100%');
|
||||
|
||||
// see if this id already exists, if so, then user must have updated an existing piece of content
|
||||
$("tr[data-id='" + resp.url + "']").remove();
|
||||
|
||||
var template = $('#new-asset-element').html();
|
||||
var html = Mustache.to_html(template, resp);
|
||||
$('table > tbody').prepend(html);
|
||||
|
||||
// re-bind the listeners to delete it
|
||||
$('.remove-asset-button').bind('click', removeAsset);
|
||||
|
||||
analytics.track('Uploaded a File', {
|
||||
'course': course_location_analytics,
|
||||
'asset_url': resp.url
|
||||
});
|
||||
}
|
||||
50
cms/static/js/views/assets_view.js
Normal file
50
cms/static/js/views/assets_view.js
Normal file
@@ -0,0 +1,50 @@
|
||||
CMS.Views.Assets = Backbone.View.extend({
|
||||
// takes CMS.Models.AssetCollection as model
|
||||
|
||||
initialize : function() {
|
||||
this.listenTo(this.collection, 'destroy', this.handleDestroy);
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
|
||||
var self = this;
|
||||
_.each(this.collection.models,
|
||||
function(asset) {
|
||||
var view = new CMS.Views.Asset({model: asset});
|
||||
self.$el.append(view.render().el);
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
handleDestroy: function(model, collection, options) {
|
||||
var index = options.index;
|
||||
this.$el.children().eq(index).remove();
|
||||
|
||||
analytics.track('Deleted Asset', {
|
||||
'course': course_location_analytics,
|
||||
'id': model.get('url')
|
||||
});
|
||||
},
|
||||
|
||||
addAsset: function (model) {
|
||||
// If asset is not already being shown, add it.
|
||||
if (_.all(
|
||||
this.collection.models,
|
||||
function (asset) {
|
||||
return asset.get('url') !== model.get('url');
|
||||
})) {
|
||||
|
||||
this.collection.add(model, {at: 0});
|
||||
var view = new CMS.Views.Asset({model: model});
|
||||
this.$el.prepend(view.render().el);
|
||||
|
||||
analytics.track('Uploaded a File', {
|
||||
'course': course_location_analytics,
|
||||
'asset_url': model.get('url')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -60,7 +60,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
|
||||
this.$el.find('#' + this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
|
||||
|
||||
var imageURL = this.model.get('course_image_asset_path');
|
||||
this.$el.find('#course-image-url').val(imageURL)
|
||||
this.$el.find('#course-image-url').val(imageURL);
|
||||
this.$el.find('#course-image').attr('src', imageURL);
|
||||
|
||||
return this;
|
||||
@@ -262,9 +262,9 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
|
||||
model: upload,
|
||||
onSuccess: function(response) {
|
||||
var options = {
|
||||
'course_image_name': response.displayname,
|
||||
'course_image_asset_path': response.url
|
||||
}
|
||||
'course_image_name': response.asset.display_name,
|
||||
'course_image_asset_path': response.asset.url
|
||||
};
|
||||
self.model.set(options);
|
||||
self.render();
|
||||
$('#course-image').attr('src', self.model.get('course_image_asset_path'))
|
||||
|
||||
@@ -248,11 +248,11 @@ CMS.Views.EditChapter = Backbone.View.extend({
|
||||
onSuccess: function(response) {
|
||||
var options = {};
|
||||
if(!that.model.get('name')) {
|
||||
options.name = response.displayname;
|
||||
options.name = response.asset.display_name;
|
||||
}
|
||||
options.asset_path = response.url;
|
||||
options.asset_path = response.asset.url;
|
||||
that.model.set(options);
|
||||
},
|
||||
}
|
||||
});
|
||||
$(".wrapper-view").after(view.show().el);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user