BLD-1000: Download handout.
This commit is contained in:
@@ -47,7 +47,8 @@ var FileUpload = Backbone.Model.extend({
|
||||
return RegExp(('(?:.+)\\.(' + formats.join('|') + ')$'), 'i');
|
||||
};
|
||||
|
||||
return _.contains(attrs.mimeTypes, file.type) ||
|
||||
return (attrs.mimeTypes.length === 0 && attrs.fileFormats.length === 0) ||
|
||||
_.contains(attrs.mimeTypes, file.type) ||
|
||||
getRegExp(attrs.fileFormats).test(file.name);
|
||||
},
|
||||
// Return strings for the valid file types and extensions this
|
||||
|
||||
176
cms/static/js/spec/video/file_uploader_editor_spec.js
Normal file
176
cms/static/js/spec/video/file_uploader_editor_spec.js
Normal file
@@ -0,0 +1,176 @@
|
||||
define(
|
||||
[
|
||||
'jquery', 'underscore', 'js/spec_helpers/create_sinon', 'squire'
|
||||
],
|
||||
function ($, _, create_sinon, Squire) {
|
||||
'use strict';
|
||||
describe('FileUploader', function () {
|
||||
var FileUploaderTemplate = readFixtures(
|
||||
'metadata-file-uploader-entry.underscore'
|
||||
),
|
||||
FileUploaderItemTemplate = readFixtures(
|
||||
'metadata-file-uploader-item.underscore'
|
||||
),
|
||||
locator = 'locator',
|
||||
feedbackTpl = readFixtures('system-feedback.underscore'),
|
||||
modelStub = {
|
||||
default_value: 'http://example.org/test_1',
|
||||
display_name: 'File Upload',
|
||||
explicitly_set: false,
|
||||
field_name: 'file_upload',
|
||||
help: 'Specifies the name for this component.',
|
||||
type: 'FileUploader',
|
||||
value: 'http://example.org/test_1'
|
||||
},
|
||||
self, injector;
|
||||
|
||||
var setValue = function (view, value) {
|
||||
view.setValueInEditor(value);
|
||||
view.updateModel();
|
||||
};
|
||||
|
||||
var createPromptSpy = function (name) {
|
||||
var spy = jasmine.createSpyObj(name, ['constructor', 'show', 'hide']);
|
||||
spy.constructor.andReturn(spy);
|
||||
spy.show.andReturn(spy);
|
||||
spy.extend = jasmine.createSpy().andReturn(spy.constructor);
|
||||
|
||||
return spy;
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
self = this;
|
||||
|
||||
this.addMatchers({
|
||||
assertValueInView: function(expected) {
|
||||
var value = this.actual.getValueFromEditor();
|
||||
return this.env.equals_(value, expected);
|
||||
},
|
||||
assertCanUpdateView: function (expected) {
|
||||
var view = this.actual,
|
||||
value;
|
||||
|
||||
view.setValueInEditor(expected);
|
||||
value = view.getValueFromEditor();
|
||||
|
||||
return this.env.equals_(value, expected);
|
||||
},
|
||||
assertClear: function (modelValue) {
|
||||
var env = this.env,
|
||||
view = this.actual,
|
||||
model = view.model;
|
||||
|
||||
return model.getValue() === null &&
|
||||
env.equals_(model.getDisplayValue(), modelValue) &&
|
||||
env.equals_(view.getValueFromEditor(), modelValue);
|
||||
},
|
||||
assertUpdateModel: function (originalValue, newValue) {
|
||||
var env = this.env,
|
||||
view = this.actual,
|
||||
model = view.model,
|
||||
expectOriginal;
|
||||
|
||||
view.setValueInEditor(newValue);
|
||||
expectOriginal = env.equals_(model.getValue(), originalValue);
|
||||
view.updateModel();
|
||||
|
||||
return expectOriginal &&
|
||||
env.equals_(model.getValue(), newValue);
|
||||
},
|
||||
verifyButtons: function (upload, download, index) {
|
||||
var view = this.actual,
|
||||
uploadBtn = view.$('.upload-setting'),
|
||||
downloadBtn = view.$('.download-setting');
|
||||
|
||||
upload = upload ? uploadBtn.length : !uploadBtn.length;
|
||||
download = download ? downloadBtn.length : !downloadBtn.length;
|
||||
|
||||
return upload && download;
|
||||
}
|
||||
});
|
||||
|
||||
appendSetFixtures($('<script>', {
|
||||
id: 'metadata-file-uploader-entry',
|
||||
type: 'text/template'
|
||||
}).text(FileUploaderTemplate));
|
||||
|
||||
appendSetFixtures($('<script>', {
|
||||
id: 'metadata-file-uploader-item',
|
||||
type: 'text/template'
|
||||
}).text(FileUploaderItemTemplate));
|
||||
|
||||
this.uploadSpies = createPromptSpy('UploadDialog');
|
||||
|
||||
injector = new Squire();
|
||||
injector.mock('js/views/uploads', function () {
|
||||
return self.uploadSpies.constructor;
|
||||
});
|
||||
injector.mock('js/views/video/transcripts/metadata_videolist');
|
||||
injector.mock('js/views/video/translations_editor');
|
||||
|
||||
runs(function() {
|
||||
injector.require([
|
||||
'js/models/metadata', 'js/views/metadata'
|
||||
],
|
||||
function(MetadataModel, MetadataView) {
|
||||
var model = new MetadataModel($.extend(true, {}, modelStub));
|
||||
self.view = new MetadataView.FileUploader({
|
||||
model: model,
|
||||
locator: locator
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
waitsFor(function() {
|
||||
return self.view;
|
||||
}, 'FileUploader was not created', 2000);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
injector.clean();
|
||||
injector.remove();
|
||||
});
|
||||
|
||||
it('returns the initial value upon initialization', function () {
|
||||
expect(this.view).assertValueInView('http://example.org/test_1');
|
||||
expect(this.view).verifyButtons(true, true);
|
||||
});
|
||||
|
||||
it('updates its value correctly', function () {
|
||||
expect(this.view).assertCanUpdateView('http://example.org/test_2');
|
||||
});
|
||||
|
||||
it('upload works correctly', function () {
|
||||
var options;
|
||||
|
||||
setValue(this.view, '');
|
||||
expect(this.view).verifyButtons(true, false);
|
||||
|
||||
this.view.$el.find('.upload-setting').click();
|
||||
|
||||
expect(this.uploadSpies.constructor).toHaveBeenCalled();
|
||||
expect(this.uploadSpies.show).toHaveBeenCalled();
|
||||
|
||||
options = this.uploadSpies.constructor.mostRecentCall.args[0];
|
||||
options.onSuccess({
|
||||
'asset': {
|
||||
'url': 'http://example.org/test_3'
|
||||
}
|
||||
});
|
||||
|
||||
expect(this.view).verifyButtons(true, true);
|
||||
expect(this.view.getValueFromEditor()).toEqual('http://example.org/test_3');
|
||||
});
|
||||
|
||||
it('has a clear method to revert to the model default', function () {
|
||||
setValue(this.view, 'http://example.org/test_5');
|
||||
|
||||
this.view.clear();
|
||||
expect(this.view).assertClear('http://example.org/test_1');
|
||||
});
|
||||
|
||||
it('has an update model method', function () {
|
||||
expect(this.view).assertUpdateModel(null, 'http://example.org/test_6');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -212,43 +212,22 @@ function ($, _, create_sinon, Squire) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('has a clear method to revert to the model default', function () {
|
||||
it('w/ popup, if values were changed', function (){
|
||||
var requests = create_sinon.requests(this),
|
||||
options;
|
||||
|
||||
setValue(this.view, {
|
||||
'fr': 'fr.srt',
|
||||
'uk': 'uk.srt'
|
||||
});
|
||||
|
||||
this.view.$el.find('.create-setting').click();
|
||||
this.view.clear();
|
||||
|
||||
expect(this.view).assertClear({
|
||||
'en': 'en.srt',
|
||||
'ru': 'ru.srt'
|
||||
});
|
||||
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
it('has a clear method to revert to the model default', function () {
|
||||
setValue(this.view, {
|
||||
'fr': 'en.srt',
|
||||
'uk': 'ru.srt'
|
||||
});
|
||||
|
||||
it('w/o popup, if just keys were changed', function (){
|
||||
setValue(this.view, {
|
||||
'fr': 'en.srt',
|
||||
'uk': 'ru.srt'
|
||||
});
|
||||
this.view.$el.find('.create-setting').click();
|
||||
|
||||
this.view.$el.find('.create-setting').click();
|
||||
this.view.clear();
|
||||
|
||||
this.view.clear();
|
||||
|
||||
expect(this.view).assertClear({
|
||||
'en': 'en.srt',
|
||||
'ru': 'ru.srt'
|
||||
});
|
||||
|
||||
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
expect(this.view).assertClear({
|
||||
'en': 'en.srt',
|
||||
'ru': 'ru.srt'
|
||||
});
|
||||
|
||||
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
});
|
||||
|
||||
it('has an update model method', function () {
|
||||
@@ -261,26 +240,15 @@ function ($, _, create_sinon, Squire) {
|
||||
expect(this.view.$el.find('select').length).toEqual(5);
|
||||
});
|
||||
|
||||
describe('can remove an entry', function () {
|
||||
it('w/ popup, if values were changed', function (){
|
||||
var requests = create_sinon.requests(this),
|
||||
options;
|
||||
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(4);
|
||||
this.view.$el.find('.remove-setting').last().click();
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
|
||||
});
|
||||
|
||||
it('w/o popup, if just keys were changed', function (){
|
||||
setValue(this.view, {
|
||||
'en': 'en.srt',
|
||||
'ru': 'ru.srt',
|
||||
'fr': ''
|
||||
});
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
|
||||
this.view.$el.find('.remove-setting').last().click();
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(2);
|
||||
it('can remove an entry', function () {
|
||||
setValue(this.view, {
|
||||
'en': 'en.srt',
|
||||
'ru': 'ru.srt',
|
||||
'fr': ''
|
||||
});
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
|
||||
this.view.$el.find('.remove-setting').last().click();
|
||||
expect(_.keys(this.view.model.get('value')).length).toEqual(2);
|
||||
});
|
||||
|
||||
it('only allows one blank entry at a time', function () {
|
||||
|
||||
@@ -8,12 +8,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
|
||||
var templateName = _.result(this, 'templateName');
|
||||
// Backbone model cid is only unique within the collection.
|
||||
this.uniqueId = _.uniqueId(templateName + "_");
|
||||
|
||||
var tpl = document.getElementById(templateName).text;
|
||||
if(!tpl) {
|
||||
console.error("Couldn't load template: " + templateName);
|
||||
}
|
||||
this.template = _.template(tpl);
|
||||
this.template = this.loadTemplate(templateName);
|
||||
this.$el.html(this.template({model: this.model, uniqueId: this.uniqueId}));
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.render();
|
||||
@@ -85,6 +80,20 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the named template from the page, or logs an error if it fails.
|
||||
* @param name The name of the template.
|
||||
* @returns The loaded template.
|
||||
*/
|
||||
loadTemplate: function(name) {
|
||||
var templateSelector = "#" + name,
|
||||
templateText = $(templateSelector).text();
|
||||
if (!templateText) {
|
||||
console.error("Failed to load " + name + " template");
|
||||
}
|
||||
return _.template(templateText);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
define(
|
||||
[
|
||||
"js/views/baseview", "underscore", "js/models/metadata", "js/views/abstract_editor",
|
||||
"js/models/uploads", "js/views/uploads",
|
||||
"js/views/video/transcripts/metadata_videolist",
|
||||
"js/views/video/translations_editor"
|
||||
],
|
||||
function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslations) {
|
||||
function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog, VideoList, VideoTranslations) {
|
||||
var Metadata = {};
|
||||
|
||||
Metadata.Editor = BaseView.extend({
|
||||
|
||||
// Model is CMS.Models.MetadataCollection,
|
||||
initialize : function() {
|
||||
var self = this,
|
||||
counter = 0,
|
||||
locator = self.$el.closest('[data-locator]').data('locator');
|
||||
|
||||
this.template = this.loadTemplate('metadata-editor');
|
||||
|
||||
this.$el.html(this.template({numEntries: this.collection.length}));
|
||||
var counter = 0;
|
||||
|
||||
var self = this;
|
||||
this.collection.each(
|
||||
function (model) {
|
||||
var data = {
|
||||
el: self.$el.find('.metadata_entry')[counter++],
|
||||
locator: locator,
|
||||
model: model
|
||||
},
|
||||
conversions = {
|
||||
@@ -85,7 +88,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
|
||||
|
||||
events : {
|
||||
"change input" : "updateModel",
|
||||
"keypress .setting-input" : "showClearButton" ,
|
||||
"keypress .setting-input" : "showClearButton",
|
||||
"click .setting-clear" : "clear"
|
||||
},
|
||||
|
||||
@@ -487,5 +490,62 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Provides convenient way to upload/download files in component edit.
|
||||
* The editor uploads files directly to course assets and stores link
|
||||
* to uploaded file.
|
||||
*/
|
||||
Metadata.FileUploader = AbstractEditor.extend({
|
||||
|
||||
events : {
|
||||
"click .upload-setting" : "upload",
|
||||
"click .setting-clear" : "clear"
|
||||
},
|
||||
|
||||
templateName: "metadata-file-uploader-entry",
|
||||
templateButtonsName: "metadata-file-uploader-item",
|
||||
|
||||
initialize: function () {
|
||||
this.buttonTemplate = this.loadTemplate(this.templateButtonsName);
|
||||
AbstractEditor.prototype.initialize.apply(this);
|
||||
},
|
||||
|
||||
getValueFromEditor: function () {
|
||||
return this.$('#' + this.uniqueId).val();
|
||||
},
|
||||
|
||||
setValueInEditor: function (value) {
|
||||
var html = this.buttonTemplate({
|
||||
model: this.model,
|
||||
uniqueId: this.uniqueId
|
||||
});
|
||||
|
||||
this.$('#' + this.uniqueId).val(value);
|
||||
this.$('.wrapper-uploader-actions').html(html);
|
||||
},
|
||||
|
||||
upload: function (event) {
|
||||
var self = this,
|
||||
target = $(event.currentTarget),
|
||||
url = /assets/ + this.options.locator,
|
||||
model = new FileUpload({
|
||||
title: gettext('Upload File'),
|
||||
}),
|
||||
view = new UploadDialog({
|
||||
model: model,
|
||||
url: url,
|
||||
parentElement: target.closest('.xblock-editor'),
|
||||
onSuccess: function (response) {
|
||||
if (response['asset'] && response['asset']['url']) {
|
||||
self.model.setValue(response['asset']['url']);
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
return Metadata;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user