Commit 88d95d11 by Andy Armstrong

Rebuild the way modals are constructed to support nesting.

parent b779b8a6
......@@ -350,7 +350,7 @@ def attach_file(filename, sub_path):
def upload_file(filename, sub_path=''):
attach_file(filename, sub_path)
button_css = '.upload-dialog .action-upload'
button_css = '.wrapper-modal-window-assetupload .action-upload'
world.css_click(button_css)
......
......@@ -106,7 +106,15 @@ def click_component_from_menu(category, component_type, is_advanced):
@world.absorb
def edit_component_and_select_settings():
world.edit_component()
world.css_click('.settings-button')
world.ensure_settings_visible()
@world.absorb
def ensure_settings_visible():
# Select the 'settings' tab if there is one (it isn't displayed if it is the only option)
settings_button = world.browser.find_by_css('.settings-button')
if len(settings_button) > 0:
world.css_click('.settings-button')
@world.absorb
......
......@@ -4,13 +4,13 @@ Feature: CMS.Discussion Component Editor
Scenario: User can view discussion component metadata
Given I have created a Discussion Tag
And I edit and select Settings
And I edit the component
Then I see three alphabetized settings and their expected values
# Safari doesn't save the name properly
@skip_safari
Scenario: User can modify display name
Given I have created a Discussion Tag
And I edit and select Settings
And I edit the component
Then I can modify the display name
And my display name change is persisted on save
......@@ -21,3 +21,8 @@ def i_see_only_the_settings_and_values(step):
['Display Name', "Discussion", False],
['Subcategory', "Topic-Level Student-Visible Label", False]
])
@step('I edit the component$')
def i_edit_and_select_settings(_step):
world.edit_component()
......@@ -72,7 +72,7 @@ class RequestHandlerWithSessionId(object):
def success_upload_file(filename):
upload_file(filename, sub_path="uploads/")
world.css_has_text('#upload_confirm', 'Success!')
world.is_css_not_present('.wrapper-dialog-assetupload', wait_time=30)
world.is_css_not_present('.wrapper-modal-window-assetupload', wait_time=30)
def get_translations_container():
......
define ["jquery", "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], ($, ModuleEdit, ModuleModel) ->
define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], ($, edit_helpers, ModuleEdit, ModuleModel) ->
describe "ModuleEdit", ->
beforeEach ->
......@@ -27,6 +27,7 @@ define ["jquery", "coffee/src/views/module_edit", "js/models/module_info", "xmod
</ul>
<div class="edit-xblock-modal"/>
"""
edit_helpers.installEditTemplates(true);
spyOn($, 'ajax').andReturn(@moduleData)
@moduleEdit = new ModuleEdit(
......@@ -103,10 +104,7 @@ define ["jquery", "coffee/src/views/module_edit", "js/models/module_info", "xmod
expect(@moduleEdit.delegateEvents).toHaveBeenCalled()
it "loads the editing view via ajax on demand", ->
editorTemplate = readFixtures('edit-xblock-modal.underscore');
feedbackTemplate = readFixtures('system-feedback.underscore')
appendSetFixtures($("<script>", { id: "edit-xblock-modal-tpl", type: "text/template", html: editorTemplate }));
appendSetFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" , html: feedbackTemplate }));
edit_helpers.installEditTemplates(true);
expect($.ajax).not.toHaveBeenCalledWith(
url: "/xblock/#{@moduleEdit.model.id}/studio_view"
type: "GET"
......
define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_helpers/create_sinon"], (FileUpload, UploadDialog, Chapter, create_sinon) ->
define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers"], (FileUpload, UploadDialog, Chapter, create_sinon, modal_helpers) ->
feedbackTpl = readFixtures('system-feedback.underscore')
......@@ -6,8 +6,8 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
tpl = readFixtures("upload-dialog.underscore")
beforeEach ->
setFixtures($("<script>", {id: "upload-dialog-tpl", type: "text/template"}).text(tpl))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
modal_helpers.installModalTemplates()
appendSetFixtures($("<script>", {id: "upload-dialog-tpl", type: "text/template"}).text(tpl))
CMS.URL.UPLOAD_ASSET = "/upload"
@model = new FileUpload(
......@@ -37,11 +37,10 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
afterEach ->
delete CMS.URL.UPLOAD_ASSET
if (@view && modal_helpers.isShowingModal(@view))
@view.hide()
describe "Basic", ->
it "should be shown by default", ->
expect(@view.options.shown).toBeTruthy()
it "should render without a file selected", ->
@view.render()
expect(@view.$el).toContain("input[type=file]")
......@@ -65,18 +64,6 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
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()
# can't test: this blows up the spec runner
# expect($("body")).toHaveClass("dialog-is-shown")
it "removes body class on hide()", ->
@view.hide()
expect(@view.options.shown).toBeFalsy()
# can't test: this blows up the spec runner
# expect($("body")).not.toHaveClass("dialog-is-shown")
describe "Uploads", ->
beforeEach ->
@clock = sinon.useFakeTimers()
......@@ -87,6 +74,7 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
it "can upload correctly", ->
requests = create_sinon["requests"](this)
@view.render()
@view.upload()
expect(@model.get("uploading")).toBeTruthy()
expect(requests.length).toEqual(1)
......@@ -103,6 +91,7 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
it "can handle upload errors", ->
requests = create_sinon["requests"](this)
@view.render()
@view.upload()
requests[0].respond(500)
expect(@model.get("title")).toMatch(/error/)
......@@ -111,9 +100,10 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec_h
it "removes itself after two seconds on successful upload", ->
requests = create_sinon["requests"](this)
@view.render()
@view.upload()
requests[0].respond(200, {"Content-Type": "application/json"},
'{"response": "dummy_response"}')
expect(@view.remove).not.toHaveBeenCalled()
expect(modal_helpers.isShowingModal(@view)).toBeTruthy();
@clock.tick(2001)
expect(@view.remove).toHaveBeenCalled()
expect(modal_helpers.isShowingModal(@view)).toBeFalsy();
define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec/create_sinon", "jquery"],
define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_helpers/create_sinon", "jquery"],
function (ContentDragger, Notification, create_sinon, $) {
describe("Overview drag and drop functionality", function () {
beforeEach(function () {
......
define(["jquery", "underscore", "js/views/modals/base_modal"],
function ($, _, BaseModal) {
define(["jquery", "underscore", "js/views/modals/base_modal", "js/spec_helpers/modal_helpers"],
function ($, _, BaseModal, modal_helpers) {
describe("BaseModal", function() {
var baseViewPrototype, MockModal, modal;
var MockModal, modal, showMockModal;
MockModal = BaseModal.extend({
initialize: function() {
this.viewHtml = readFixtures('mock/mock-modal.underscore');
},
render: function() {
this.$el.html(this.viewHtml);
getContentHtml: function() {
return readFixtures('mock/mock-modal.underscore');
}
});
showMockModal = function() {
modal = new MockModal({
title: "Mock Modal"
});
modal.show();
};
beforeEach(function () {
modal_helpers.installModalTemplates();
});
afterEach(function() {
if (modal) {
if (modal && modal_helpers.isShowingModal(modal)) {
modal.hide();
}
});
it('is visible after show is called', function () {
modal = new MockModal();
modal.render();
modal.show();
expect($('body')).toHaveClass('modal-window-is-shown');
expect(modal.$('.wrapper-modal-window')).toHaveClass('is-shown');
modal.hide();
describe("Single Modal", function() {
it('is visible after show is called', function () {
showMockModal();
expect(modal_helpers.isShowingModal(modal)).toBeTruthy();
});
it('is removed after hide is called', function () {
showMockModal();
modal.hide();
expect(modal_helpers.isShowingModal(modal)).toBeFalsy();
});
it('is removed after cancel is clicked', function () {
showMockModal();
modal_helpers.cancelModal(modal);
expect(modal_helpers.isShowingModal(modal)).toBeFalsy();
});
});
it('is invisible after hide is called', function () {
modal = new MockModal();
modal.render();
modal.show();
modal.hide();
expect($('body')).not.toHaveClass('modal-window-is-shown');
expect(modal.$('.wrapper-modal-window')).not.toHaveClass('is-shown');
describe("Nested Modal", function() {
var nestedModal, showNestedModal;
showNestedModal = function() {
showMockModal();
nestedModal = new MockModal({
title: "Nested Modal",
parent: modal
});
nestedModal.show();
};
afterEach(function() {
if (nestedModal && modal_helpers.isShowingModal(nestedModal)) {
nestedModal.hide();
}
});
it('is visible after show is called', function () {
showNestedModal();
expect(modal_helpers.isShowingModal(nestedModal)).toBeTruthy();
});
it('is removed after hide is called', function () {
showNestedModal();
nestedModal.hide();
expect(modal_helpers.isShowingModal(nestedModal)).toBeFalsy();
// Verify that the parent modal is still showing
expect(modal_helpers.isShowingModal(modal)).toBeTruthy();
});
it('is removed after cancel is clicked', function () {
showNestedModal();
modal_helpers.cancelModal(nestedModal);
expect(modal_helpers.isShowingModal(nestedModal)).toBeFalsy();
// Verify that the parent modal is still showing
expect(modal_helpers.isShowingModal(modal)).toBeTruthy();
});
});
});
});
......@@ -44,9 +44,9 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
it('can show itself', function() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockXBlockEditorHtml);
expect(modal.$('.wrapper-modal-window')).toHaveClass('is-shown');
expect(edit_helpers.isShowingModal(modal)).toBeTruthy();
edit_helpers.cancelModal(modal);
expect(modal.$('.wrapper-modal-window')).not.toHaveClass('is-shown');
expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
});
it('shows the correct title', function() {
......@@ -79,9 +79,9 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
it('can render itself', function() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockXModuleEditorHtml);
expect(modal.$('.wrapper-modal-window')).toHaveClass('is-shown');
expect(edit_helpers.isShowingModal(modal)).toBeTruthy();
edit_helpers.cancelModal(modal);
expect(modal.$('.wrapper-modal-window')).not.toHaveClass('is-shown');
expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
});
it('shows the correct title', function() {
......@@ -104,6 +104,21 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
expect(settingsButton).not.toHaveClass('is-set');
});
it('can switch tabs', function() {
var requests = create_sinon.requests(this),
editorButton,
settingsButton;
modal = showModal(requests, mockXModuleEditorHtml);
expect(modal.$('.editor-modes a').length).toBe(2);
editorButton = modal.$('.editor-button');
settingsButton = modal.$('.settings-button');
expect(modal.$('.metadata_edit')).toHaveClass('is-inactive');
settingsButton.click();
expect(modal.$('.metadata_edit')).toHaveClass('is-active');
editorButton.click();
expect(modal.$('.metadata_edit')).toHaveClass('is-inactive');
});
describe("Custom Tabs", function() {
var mockCustomTabsHtml;
......@@ -122,5 +137,36 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
});
});
});
describe("XModule Editor (settings only)", function() {
var mockXModuleEditorHtml;
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-settings-only-editor.underscore');
beforeEach(function() {
// Mock the VerticalDescriptor so that the module can be rendered
window.VerticalDescriptor = XModule.Descriptor;
});
afterEach(function () {
window.VerticalDescriptor = null;
});
it('can render itself', function() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockXModuleEditorHtml);
expect(edit_helpers.isShowingModal(modal)).toBeTruthy();
edit_helpers.cancelModal(modal);
expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
});
it('does not show any mode buttons', function() {
var requests = create_sinon.requests(this),
editorButton,
settingsButton;
modal = showModal(requests, mockXModuleEditorHtml);
expect(modal.$('.editor-modes li').length).toBe(0);
});
});
});
});
......@@ -27,14 +27,48 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
create_sinon.respondWithJson(requests, response, requestIndex);
};
describe("Basic display", function() {
var mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore');
it('can render itself', function() {
var requests = create_sinon.requests(this);
containerView.render();
respondWithMockXBlockEditorFragment(requests, {
html: mockContainerXBlockHtml,
"resources": []
});
expect(containerView.$el.select('.xblock-header')).toBeTruthy();
expect(containerView.$('.wrapper-xblock')).not.toHaveClass('is-hidden');
expect(containerView.$('.no-container-content')).toHaveClass('is-hidden');
});
it('shows a loading indicator', function() {
var requests = create_sinon.requests(this);
containerView.render();
expect(containerView.$('.ui-loading')).not.toHaveClass('is-hidden');
respondWithMockXBlockEditorFragment(requests, {
html: mockContainerXBlockHtml,
"resources": []
});
expect(containerView.$('.ui-loading')).toHaveClass('is-hidden');
});
});
describe("Editing an xblock", function() {
var mockContainerXBlockHtml,
mockXBlockEditorHtml;
mockXBlockEditorHtml,
saved;
beforeEach(function () {
saved = false;
window.MockXBlock = function(runtime, element) {
return { };
return {
save: function() {
saved = true;
}
};
};
});
......@@ -48,18 +82,6 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore');
mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore');
it('can render itself', function() {
var requests = create_sinon.requests(this);
containerView.render();
respondWithMockXBlockEditorFragment(requests, {
html: mockContainerXBlockHtml,
"resources": []
});
expect(containerView.$el.select('.xblock-header')).toBeTruthy();
});
it('can show an edit modal for a child xblock', function() {
var requests = create_sinon.requests(this),
editButtons;
......@@ -80,7 +102,50 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
html: mockXBlockEditorHtml,
"resources": []
});
expect($('.wrapper-modal-window')).toHaveClass('is-shown');
expect(edit_helpers.isShowingModal()).toBeTruthy();
});
it('can save changes to settings', function() {
var requests, editButtons, modal;
requests = create_sinon.requests(this);
containerView.render();
respondWithMockXBlockEditorFragment(requests, {
html: mockContainerXBlockHtml,
"resources": []
});
editButtons = containerView.$('.edit-button');
// The container renders four mock xblocks, so there should be four edit buttons
expect(editButtons.length).toBe(4);
editButtons.first().click();
create_sinon.respondWithJson(requests, {
html: mockXBlockEditorHtml,
"resources": []
});
modal = $('.edit-xblock-modal');
// Click on the settings tab
modal.find('.settings-button').click();
// Change the display name's text
modal.find('.setting-input').text("New display name");
// Press the save button
modal.find('.action-save').click();
expect(saved).toBe(true);
});
});
describe("Empty container", function() {
var mockContainerXBlockHtml = readFixtures('mock/mock-empty-container-xblock.underscore');
it('shows the "no children" message', function() {
var requests = create_sinon.requests(this);
containerView.render();
respondWithMockXBlockEditorFragment(requests, {
html: mockContainerXBlockHtml,
"resources": []
});
expect(containerView.$('.no-container-content')).not.toHaveClass('is-hidden');
expect(containerView.$('.wrapper-xblock')).toHaveClass('is-hidden');
});
});
});
......
......@@ -72,5 +72,34 @@ define([ "jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers
expect(editor.getMode()).toEqual('editor');
});
});
describe("Editing an xmodule (settings only)", function() {
var mockXModuleEditorHtml;
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-settings-only-editor.underscore');
beforeEach(function() {
edit_helpers.installEditTemplates();
// Mock the VerticalDescriptor so that the module can be rendered
window.VerticalDescriptor = XModule.Descriptor;
});
afterEach(function () {
window.VerticalDescriptor = null;
});
it('can render itself', function() {
var requests = create_sinon.requests(this);
editor.render();
create_sinon.respondWithJson(requests, {
html: mockXModuleEditorHtml,
"resources": []
});
expect(editor.$el.select('.xblock-header')).toBeTruthy();
expect(editor.getMode()).toEqual('settings');
});
});
});
});
/**
* Provides helper methods for invoking Studio editors in Jasmine tests.
*/
define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock",
define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers", "js/views/modals/edit_xblock",
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function($, create_sinon, EditXBlockModal) {
function($, create_sinon, modal_helpers, EditXBlockModal) {
var editorTemplate = readFixtures('metadata-editor.underscore'),
numberEntryTemplate = readFixtures('metadata-number-entry.underscore'),
......@@ -12,12 +12,10 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock",
editXBlockModalTemplate = readFixtures('edit-xblock-modal.underscore'),
editorModeButtonTemplate = readFixtures('editor-mode-button.underscore'),
installEditTemplates,
showEditModal,
isShowingModal,
cancelModal;
showEditModal;
installEditTemplates = function() {
setFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" }).text(feedbackTemplate));
installEditTemplates = function(append) {
modal_helpers.installModalTemplates(append);
// Add templates needed by the edit XBlock modal
appendSetFixtures($("<script>", { id: "edit-xblock-modal-tpl", type: "text/template" }).text(editXBlockModalTemplate));
......@@ -29,7 +27,6 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock",
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate));
};
showEditModal = function(requests, xblockElement, model, mockHtml) {
var modal = new EditXBlockModal({});
modal.edit(xblockElement, model);
......@@ -40,35 +37,10 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock",
return modal;
};
isShowingModal = function(modal) {
var modalElement;
if (modal) {
modalElement = modal.$el;
} else {
modalElement = $('.wrapper-modal-window');
}
if (modalElement) {
return modalElement.hasClass('is-shown');
}
return false;
};
cancelModal = function(modal) {
var modalElement, cancelButton;
if (modal) {
modalElement = modal.$el;
} else {
modalElement = $('.wrapper-modal-window');
}
cancelButton = modalElement.find('.action-cancel');
expect(cancelButton.length).toBe(1);
cancelButton.click();
};
return {
'installEditTemplates': installEditTemplates,
'showEditModal': showEditModal,
'isShowingModal': isShowingModal,
'cancelModal': cancelModal
'isShowingModal': modal_helpers.isShowingModal,
'cancelModal': modal_helpers.cancelModal
};
});
/**
* Provides helper methods for invoking Studio modal windows in Jasmine tests.
*/
define(["jquery"],
function($) {
var basicModalTemplate = readFixtures('basic-modal.underscore'),
modalButtonTemplate = readFixtures('modal-button.underscore'),
feedbackTemplate = readFixtures('system-feedback.underscore'),
installModalTemplates,
isShowingModal,
cancelModal;
installModalTemplates = function(append) {
if (append) {
appendSetFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" }).text(feedbackTemplate));
} else {
setFixtures($("<script>", { id: "system-feedback-tpl", type: "text/template" }).text(feedbackTemplate));
}
appendSetFixtures($("<script>", { id: "basic-modal-tpl", type: "text/template" }).text(basicModalTemplate));
appendSetFixtures($("<script>", { id: "modal-button-tpl", type: "text/template" }).text(modalButtonTemplate));
};
isShowingModal = function(modal) {
var modalElement;
if (modal) {
modalElement = modal.$('.wrapper-modal-window');
} else {
modalElement = $('.wrapper-modal-window');
}
return modalElement.length > 0;
};
cancelModal = function(modal) {
var modalElement, cancelButton;
if (modal) {
modalElement = modal.$('.wrapper-modal-window');
} else {
modalElement = $('.wrapper-modal-window');
}
cancelButton = modalElement.find('.action-cancel');
expect(cancelButton.length).toBe(1);
cancelButton.click();
};
return {
'installModalTemplates': installModalTemplates,
'isShowingModal': isShowingModal,
'cancelModal': cancelModal
};
});
......@@ -54,7 +54,7 @@ define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gette
var msg = new FileUploadModel({
title: _.template(gettext("Upload a new PDF to “<%= name %>”"),
{name: course.escape('name')}),
message: "Files must be in PDF format.",
message: "Please select a PDF file to upload.",
mimeTypes: ['application/pdf']
});
var that = this;
......@@ -69,7 +69,7 @@ define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gette
that.model.set(options);
}
});
$(".wrapper-view").after(view.show().el);
view.show();
}
});
......
/**
* This is a base modal implementation that provides common utilities.
*/
define(["jquery", "js/views/baseview"],
function($, BaseView) {
define(["jquery", "underscore", "gettext", "js/views/baseview"],
function($, _, gettext, BaseView) {
var BaseModal = BaseView.extend({
events : {
"click .action-cancel": "cancel"
},
options: $.extend({}, BaseView.prototype.options, {
type: "prompt",
closeIcon: false,
icon: false
icon: false,
modalName: 'basic',
modalType: 'generic',
modalSize: 'lg',
title: ""
}),
initialize: function() {
var parent = this.options.parent,
parentElement = this.options.parentElement;
this.modalTemplate = _.template($("#basic-modal-tpl").text());
this.buttonTemplate = _.template($("#modal-button-tpl").text());
if (parent) {
parentElement = parent.$el;
} else if (!parentElement) {
parentElement = this.$el.closest('.modal-window');
if (parentElement.length === 0) {
parentElement = $('body');
}
}
this.parentElement = parentElement;
},
render: function() {
this.$el.html(this.modalTemplate({
name: this.options.modalName,
type: this.options.modalType,
size: this.options.modalSize,
title: this.options.title
}));
this.addActionButtons();
this.renderContents();
this.parentElement.append(this.$el);
},
renderContents: function() {
var contentHtml = this.getContentHtml();
this.$('.modal-content').html(contentHtml);
},
/**
* Returns the content to be shown in the modal.
*/
getContentHtml: function() {
return "";
},
show: function() {
this.render();
this.resize();
this.lastPosition = $(document).scrollTop();
$('body').addClass('modal-window-is-shown');
this.$('.wrapper-modal-window').addClass('is-shown');
$(window).resize(_.bind(this.resize, this));
},
hide: function() {
$('body').removeClass('modal-window-is-shown');
this.$('.wrapper-modal-window').removeClass('is-shown');
$(document).scrollTop(this.lastPosition);
// Completely remove the modal from the DOM
this.undelegateEvents();
this.$el.html('');
},
cancel: function(event) {
event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
this.hide();
},
/**
* Adds the action buttons to the modal.
*/
addActionButtons: function() {
if (this.options.addSaveButton) {
this.addActionButton('save', gettext("Save"), true);
}
this.addActionButton('cancel', gettext("Cancel"));
},
/**
* Adds a new action button to the modal.
* @param type The type of the action.
* @param name The action's name.
* @param isPrimary True if this button is the primary one.
*/
addActionButton: function(type, name, isPrimary) {
var html = this.buttonTemplate({
type: type,
name: name,
isPrimary: isPrimary
});
this.$('.modal-actions ul').append(html);
},
resize: function() {
var top, left, modalWindow, modalWidth, modalHeight,
availableWidth, availableHeight, maxWidth, maxHeight;
modalWindow = this.$('.modal-window');
availableWidth = $(window).width();
availableHeight = $(window).height();
maxWidth = availableWidth * 0.80;
maxHeight = availableHeight * 0.80;
modalWidth = Math.min(modalWindow.outerWidth(), maxWidth);
modalHeight = Math.min(modalWindow.outerHeight(), maxHeight);
left = (availableWidth - modalWidth) / 2;
top = (availableHeight - modalHeight) / 2;
// modalWindow.width(modalWidth);
// modalWindow.height(modalHeight);
modalWindow.css({
top: top + $(window).scrollTop(),
left: left + $(window).scrollLeft()
});
}
});
......
......@@ -9,11 +9,17 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
var EditXBlockModal = BaseModal.extend({
events : {
"click .action-save": "save",
"click .action-cancel": "cancel",
"click .action-modes a": "changeMode"
},
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'edit-xblock',
addSaveButton: true
}),
initialize: function() {
BaseModal.prototype.initialize.call(this);
this.events = _.extend({}, BaseModal.prototype.events, this.events);
this.template = _.template($("#edit-xblock-modal-tpl").text());
this.editorModeButtonTemplate = _.template($("#editor-mode-button-tpl").text());
},
......@@ -27,6 +33,7 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
edit: function(xblockElement, rootXBlockInfo, options) {
this.xblockElement = xblockElement;
this.xblockInfo = this.findXBlockInfo(xblockElement, rootXBlockInfo);
this.options.modalType = this.xblockInfo.get('category');
this.editOptions = options;
this.render();
this.show();
......@@ -35,10 +42,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
this.displayXBlock();
},
render: function() {
this.$el.html(this.template({
getContentHtml: function() {
return this.template({
xblockInfo: this.xblockInfo
}));
});
},
displayXBlock: function() {
......@@ -62,11 +69,12 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
editorView.$('.component-name').text(title);
} else {
this.$('.modal-window-title').text(title);
if (editorView.getMetadataEditor()) {
if (editorView.getDataEditor() && editorView.getMetadataEditor()) {
this.addDefaultModes();
this.selectMode(editorView.mode);
}
}
this.resize();
},
getTitle: function() {
......@@ -82,9 +90,8 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
},
addDefaultModes: function() {
var defaultModes = this.editorView.getDefaultModes(),
i,
mode;
var defaultModes, i, mode;
defaultModes = this.editorView.getDefaultModes();
for (i = 0; i < defaultModes.length; i++) {
mode = defaultModes[i];
this.addModeButton(mode.id, mode.name);
......@@ -109,11 +116,6 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
}
},
cancel: function(event) {
event.preventDefault();
this.hide();
},
save: function(event) {
var self = this,
xblockInfo = this.xblockInfo,
......
......@@ -276,7 +276,7 @@ var DetailsView = ValidatingView.extend({
$('#course-image').attr('src', self.model.get('course_image_asset_path'));
}
});
$('.wrapper-view').after(modal.show().el);
modal.show();
}
});
......
define(["js/views/modals/base_modal", "underscore", "jquery", "jquery.form"],
function(BaseModal, _, $) {
var UploadDialog = BaseModal.extend({
options: {
shown: true,
successMessageTimeout: 2000 // 2 seconds
},
initialize: function() {
this.template = _.template($("#upload-dialog-tpl").text());
this.listenTo(this.model, "change", this.render);
},
render: function() {
var isValid = this.model.isValid();
var selectedFile = this.model.get('selectedFile');
var oldInput = this.$("input[type=file]").get(0);
this.$el.html(this.template({
shown: this.options.shown,
url: this.options.url || CMS.URL.UPLOAD_ASSET,
title: this.model.escape('title'),
message: this.model.escape('message'),
selectedFile: selectedFile,
uploading: this.model.get('uploading'),
uploadedBytes: this.model.get('uploadedBytes'),
totalBytes: this.model.get('totalBytes'),
finished: this.model.get('finished'),
error: this.model.validationError
}));
// Ideally, we'd like to tell the browser to pre-populate the
// <input type="file"> with the selectedFile if we have one -- but
// browser security prohibits that. So instead, we'll swap out the
// new input (that has no file selected) with the old input (that
// already has the selectedFile selected). However, we only want to do
// this if the selected file is valid: if it isn't, we want to render
// a blank input to prompt the user to upload a different (valid) file.
if (selectedFile && isValid) {
$(oldInput).removeClass("error");
this.$('input[type=file]').replaceWith(oldInput);
}
return this;
},
events: {
"change input[type=file]": "selectFile",
"click .action-cancel": "hideAndRemove",
"click .action-upload": "upload"
},
selectFile: function(e) {
this.model.set({
selectedFile: e.target.files[0] || null
});
},
show: function(e) {
if(e && e.preventDefault) { e.preventDefault(); }
this.options.shown = true;
$("body").addClass('modal-window-is-shown');
return this.render();
},
hide: function(e) {
if(e && e.preventDefault) { e.preventDefault(); }
this.options.shown = false;
$("body").removeClass('modal-window-is-shown');
return this.render();
},
hideAndRemove: function(e) {
if(e && e.preventDefault) { e.preventDefault(); }
return this.hide().remove();
},
upload: function(e) {
if(e && e.preventDefault) { e.preventDefault(); }
this.model.set('uploading', true);
this.$("form").ajaxSubmit({
success: _.bind(this.success, this),
error: _.bind(this.error, this),
uploadProgress: _.bind(this.progress, this),
data: {
// don't show the generic error notification; we're in a modal,
// and we're better off modifying it instead.
notifyOnError: false
define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", "jquery.form"],
function($, _, gettext, BaseModal) {
var UploadDialog = BaseModal.extend({
events: {
"change input[type=file]": "selectFile",
"click .action-upload": "upload"
},
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'assetupload',
modalSize: 'med',
successMessageTimeout: 2000 // 2 seconds
}),
initialize: function() {
BaseModal.prototype.initialize.call(this);
this.events = _.extend({}, BaseModal.prototype.events, this.events);
this.template = _.template($("#upload-dialog-tpl").text());
this.listenTo(this.model, "change", this.renderContents);
this.options.title = this.model.get('title');
},
addActionButtons: function() {
this.addActionButton('upload', gettext("Upload"), true);
BaseModal.prototype.addActionButtons.call(this);
},
renderContents: function() {
var isValid = this.model.isValid(),
selectedFile = this.model.get('selectedFile'),
oldInput = this.$("input[type=file]").get(0);
BaseModal.prototype.renderContents.call(this);
// Ideally, we'd like to tell the browser to pre-populate the
// <input type="file"> with the selectedFile if we have one -- but
// browser security prohibits that. So instead, we'll swap out the
// new input (that has no file selected) with the old input (that
// already has the selectedFile selected). However, we only want to do
// this if the selected file is valid: if it isn't, we want to render
// a blank input to prompt the user to upload a different (valid) file.
if (selectedFile && isValid) {
$(oldInput).removeClass("error");
this.$('input[type=file]').replaceWith(oldInput);
this.$('.action-upload').removeClass('disabled');
} else {
this.$('.action-upload').addClass('disabled');
}
return this;
},
getContentHtml: function() {
return this.template({
url: this.options.url || CMS.URL.UPLOAD_ASSET,
message: this.model.escape('message'),
selectedFile: this.model.get('selectedFile'),
uploading: this.model.get('uploading'),
uploadedBytes: this.model.get('uploadedBytes'),
totalBytes: this.model.get('totalBytes'),
finished: this.model.get('finished'),
error: this.model.validationError
});
},
selectFile: function(e) {
this.model.set({
selectedFile: e.target.files[0] || null
});
},
upload: function(e) {
if (e && e.preventDefault) { e.preventDefault(); }
this.model.set('uploading', true);
this.$("form").ajaxSubmit({
success: _.bind(this.success, this),
error: _.bind(this.error, this),
uploadProgress: _.bind(this.progress, this),
data: {
// don't show the generic error notification; we're in a modal,
// and we're better off modifying it instead.
notifyOnError: false
}
});
},
progress: function(event, position, total, percentComplete) {
this.model.set({
"uploadedBytes": position,
"totalBytes": total
});
},
success: function(response, statusText, xhr, form) {
this.model.set({
uploading: false,
finished: true
});
if (this.options.onSuccess) {
this.options.onSuccess(response, statusText, xhr, form);
}
var that = this;
this.removalTimeout = setTimeout(function() {
that.hide();
}, this.options.successMessageTimeout);
},
error: function() {
this.model.set({
"uploading": false,
"uploadedBytes": 0,
"title": gettext("We're sorry, there was an error")
});
}
});
},
progress: function(event, position, total, percentComplete) {
this.model.set({
"uploadedBytes": position,
"totalBytes": total
});
},
success: function(response, statusText, xhr, form) {
this.model.set({
uploading: false,
finished: true
});
if(this.options.onSuccess) {
this.options.onSuccess(response, statusText, xhr, form);
}
var that = this;
this.removalTimeout = setTimeout(function() {
that.hide().remove();
}, this.options.successMessageTimeout);
},
error: function() {
this.model.set({
"uploading": false,
"uploadedBytes": 0,
"title": gettext("We're sorry, there was an error")
});
}
});
return UploadDialog;
}); // end define()
return UploadDialog;
}); // end define()
......@@ -151,6 +151,7 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
view = new VideoUploadDialog({
model: model,
url: self.model.get('urlRoot') + '/' + lang,
parentElement: target.closest('.xblock-editor'),
onSuccess: function (response) {
if (!response['filename']) { return; }
......@@ -161,7 +162,7 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
}
});
$('.wrapper-view').after(view.show().el);
view.show();
},
enableAdd: function() {
......
......@@ -60,9 +60,7 @@ define(["jquery", "underscore", "js/views/baseview", "js/views/xblock", "js/view
target = event.target,
xblockElement = self.findXBlockElement(target);
event.preventDefault();
modal = new EditXBlockModal({
el: $('.edit-xblock-modal')
});
modal = new EditXBlockModal({ });
modal.edit(xblockElement, self.model,
{
refresh: function(xblockInfo) {
......
......@@ -4,193 +4,184 @@
// start with the view/body
[class*="view-"] {
// modal-window backdrop covers the window
.wrapper-modal-window {
@extend %ui-depth4;
@include transition(all $tmg-f2 ease-in-out);
// basic modal content
.modal-window {
@extend %ui-depth3;
@include box-sizing(border-box);
position: absolute;
top: 0;
overflow: scroll;
background: $black-t2;
width: 100%;
height: 100%;
text-align: center;
width: 50%;
box-shadow: 0px 0px 7px $shadow-d1;
border-radius: ($baseline/5);
background-color: $gray-l4;
padding: 7px;
text-align: left;
&:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em; /* Adjusts for spacing */
.modal-content {
box-shadow: 0 0 3px $shadow-d1;
background-color: $white;
padding: 5%;
}
// basic modal content
.modal-window {
@include box-sizing(border-box);
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
position: absolute;
top: 50%;
left: 50%;
width: 50%;
box-shadow: 0px 0px 7px $shadow-d1;
border-radius: ($baseline/5);
background-color: $gray-l4;
padding: 7px;
text-align: left;
.title {
@extend %t-title5;
margin: ($baseline/4) ($baseline/2) ($baseline/2) ($baseline/2);
font-weight: 600;
color: $black;
}
.modal-content {
box-shadow: 0 0 3px $shadow-d1;
.message {
@extend %t-copy-sub1;
margin: 0 0 $baseline 0;
color: $gray;
}
.message-status {
padding: 0;
&.error {
border: 0;
background-color: $white;
padding: 5%;
color: $red;
}
.title {
@extend %t-title5;
margin-bottom: ($baseline/2);
font-weight: 600;
color: $black;
&.success {
border: 0;
background-color: $white;
color: $green-d1;
}
}
.message {
@extend %t-copy-sub1;
margin: $baseline 0;
color: $gray;
.modal-chin,
.modal-actions {
padding: ($baseline*0.75) 2% ($baseline/2) 2%;
&.error {
border: 0;
border-top: 3px solid $red-d2;
background-color: $red;
color: $white;
}
.action-item {
@extend %t-action3;
display: inline-block;
margin-right: ($baseline*0.75);
&.success {
border: 0;
border-top: 3px solid $green-d2;
background-color: $green;
color: $white;
&:last-child {
margin-right: 0;
}
}
.modal-chin,
.modal-actions {
padding: ($baseline*0.75) 2% ($baseline/2) 2%;
.action-item {
@extend %t-action3;
display: inline-block;
margin-right: ($baseline*0.75);
&:last-child {
margin-right: 0;
}
}
.action-primary {
@include blue-button();
@include font-size(14); // needed due to bad button mixins for now
border-color: $blue-d1;
color: $white;
}
.action-primary {
@include blue-button();
@include font-size(14); // needed due to bad button mixins for now
border-color: $blue-d1;
color: $white;
}
a {
color: $blue;
a {
color: $blue;
&:hover {
color: $blue-s2;
}
&:hover {
color: $blue-s2;
}
}
}
}
// small modals - quick editors and dialogs
.modal-sm {
width: 30%;
min-width: ($baseline*15);
// small modals - quick editors and dialogs
.modal-sm {
width: 30%;
min-width: ($baseline*15);
.modal-content {
padding: 5% 4%;
}
.modal-content {
padding: 5% 4%;
}
}
// medium modals - forms and interactives
.modal-med {
width: 40%;
min-width: ($baseline*18);
// medium modals - forms and interactives
.modal-med {
width: 40%;
min-width: ($baseline*18);
.modal-content {
padding: 4%;
}
.modal-content {
padding: 4%;
}
}
// large modals - component editors and interactives
.modal-lg {
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
top: ($baseline*2);
width: 65%;
height: auto;
min-width: ($baseline*30);
// large modals - component editors and interactives
.modal-lg {
width: 75%;
height: auto;
&.modal-editor {
&.modal-editor {
.modal-header {
margin: ($baseline/4) ($baseline/2);
.modal-header {
margin: ($baseline/4) ($baseline/2);
.title {
width: 48%;
display: inline-block;
}
.title {
width: 48%;
display: inline-block;
}
.editor-modes {
width: 49%;
.editor-modes {
width: 49%;
display: inline-block;
text-align: right;
.action-item {
display: inline-block;
text-align: right;
.action-item {
display: inline-block;
margin-left: ($baseline/2);
.editor-button,
.settings-button {
@extend %btn-secondary-gray;
@extend %t-copy-sub1;
border: 0;
padding: ($baseline/4) ($baseline/2);
text-transform: uppercase;
&:hover {
}
&.is-set {
background-color: $gray-d1;
color: $white;
}
margin-left: ($baseline/2);
.editor-button,
.settings-button {
@extend %btn-secondary-gray;
@extend %t-copy-sub1;
border: 0;
padding: ($baseline/4) ($baseline/2);
text-transform: uppercase;
&:hover {
}
&.is-set {
background-color: $gray-d1;
color: $white;
}
}
}
}
}
.modal-content {
padding: 0;
}
.modal-content {
height: 435px;
overflow-y: auto;
overflow-x: hidden;
padding: 0;
}
}
}
// specific modal overrides
// upload modal
.assetupload-modal {
.status-upload {
margin-top: $baseline;
}
}
// component editor
.modal-window {
.CodeMirror {
height: 100%;
}
.wrapper-comp-settings {
.list-input {
&.settings-list {
height: ($baseline*21.25);
height: auto;
max-height: none;
}
}
......@@ -199,10 +190,45 @@
// special overrides for video module editor/hidden tab editors
.wrapper-modal-window .modal-type-video {
.modal-lg.modal-type-video {
.modal-content {
box-shadow: none;
height: auto;
overflow-y: hidden;
// modal within a modal
.wrapper-modal-window-assetupload {
.modal-window {
top: 10% !important;
left: 10% !important;
}
.modal-content {
padding: 2%;
.title {
width: auto;
}
.editor-modes {
display: none;
}
input[type="file"] {
margin: $baseline 0;
}
.message {
margin: 0 0 $baseline 0;
}
.modal-actions {
padding: ($baseline/2) 0;
}
}
}
}
.xmodule_edit.xmodule_VideoDescriptor .editor-with-tabs {
......@@ -259,39 +285,26 @@
}
.tabs-wrapper {
box-shadow: 0 0 3px $shadow-d1;
height: ($baseline*24);
overflow-y: scroll;
border: 1px solid $gray-l2;
.component-tab {
border-top: 1px solid $gray-l3;
border-top: 0;
}
}
}
}
// modal-window set-up
.wrapper-modal-window {
visibility: hidden;
pointer-events: none;
display: none;
.modal-window {
opacity: 0;
}
}
// modal-window showing/hiding
&.modal-window-is-shown {
overflow: hidden;
.wrapper-modal-window.is-shown {
visibility: visible;
pointer-events: auto;
display: block;
.modal-window {
opacity: 1.0;
}
}
.modal-window-overlay {
@extend %ui-depth3;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.5;
filter: alpha(opacity=50);
}
}
......@@ -508,7 +508,56 @@
}
// modal to edit section publish settings
// basic non-backbone modal-window set-up
.wrapper-modal-window {
@extend %ui-depth4;
@include transition(all $tmg-f2 ease-in-out);
visibility: hidden;
pointer-events: none;
display: none;
position: absolute;
top: 0;
overflow: scroll;
background: $black-t2;
width: 100%;
height: 100%;
text-align: center;
&:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em; /* Adjusts for spacing */
}
.modal-window {
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
}
}
// modal-window showing/hiding
&.modal-window-is-shown {
overflow: hidden;
.wrapper-modal-window.is-shown {
visibility: visible;
pointer-events: auto;
display: block;
.modal-window {
opacity: 1.0;
}
}
}
.edit-section-publish-settings {
.picker {
......
......@@ -639,8 +639,8 @@ body.course.unit,.view-unit {
background-color: $white;
padding: $baseline ($baseline/2);
&:last-child {
//margin-bottom: 0;
&:first-child {
border-top: 0;
}
//no required component settings currently
......
......@@ -14,7 +14,7 @@ from django.utils.translation import ugettext as _
<%block name="header_extras">
% for template_name in ["edit-xblock-modal", "editor-mode-button"]:
% for template_name in ["basic-modal", "modal-button", "edit-xblock-modal", "editor-mode-button"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......
......@@ -9,7 +9,7 @@
<%block name="bodyclass">is-signedin course view-static-pages</%block>
<%block name="header_extras">
% for template_name in ["edit-xblock-modal", "editor-mode-button"]:
% for template_name in ["basic-modal", "modal-button", "edit-xblock-modal", "editor-mode-button"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......
<div class="wrapper wrapper-modal-window wrapper-modal-window-<%= name %>"
aria-describedby="modal-window-description"
aria-labelledby="modal-window-title"
aria-hidden=""
role="dialog">
<div class="modal-window-overlay"></div>
<div class="modal-window confirm modal-editor modal-<%= size %> modal-type-<%= type %>">
<div class="<%= name %>-modal" action="#">
<div class="modal-header">
<h2 class="title modal-window-title"><%= title %></h2>
<ul class="editor-modes action-list action-modes">
</ul>
</div>
<div class="modal-content">
</div>
<div class="modal-actions">
<h3 class="sr"><%= gettext("Actions") %></h3>
<ul></ul>
</div>
</div>
</div>
</div>
<div
class="wrapper wrapper-modal-window wrapper-modal-window-edit-xblock"
aria-describedby="modal-window-description"
aria-labelledby="modal-window-title"
aria-hidden=""
role="dialog">
<div class="modal-window confirm modal-editor modal-lg modal-type-<%= xblockInfo.get('category') %>">
<div class="edit-xblock-modal" action="#">
<div class="modal-header">
<h2 class="title modal-window-title"></h2>
<ul class="editor-modes action-list action-modes">
</ul>
</div>
<div class="modal-content">
<div class="xblock-editor" data-locator="<%= xblockInfo.get('id') %>"></div>
</div>
<div class="modal-actions">
<h3 class="sr"><%= gettext("Actions") %></h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-save"><%= gettext("Save") %></a>
</li>
<li class="action-item">
<a href="#" class="button action-secondary action-cancel"><%= gettext("Cancel") %></a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="xblock-editor" data-locator="<%= xblockInfo.get('id') %>"></div>
......@@ -54,11 +54,6 @@
</div>
</div>
<div class="edit-xblock-modal">
</div>
<script type="text/javascript">
require(['js/sock']);
</script>
......
<header class="xblock-header">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse collapse">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span>Empty Vertical Test</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="sr action-item">No Actions</li>
</ul>
</div>
</header>
<article class="xblock-render">
<div class="xblock xblock-student_view xmodule_display xmodule_VerticalModule xblock-initialized" data-runtime-class="PreviewRuntime" data-init="XBlockToXModuleShim" data-runtime-version="1" data-usage-id="i4x:;_;_AndyA;_ABT101;_vertical;_131a499ddaa3474194c1aa2eced34455" data-type="None" data-block-type="vertical">
<div class="vert-mod">
<div class="vert vert-0" data-id="i4x://AndyA/ABT101/vertical/2758bbc495dd40d59050da15b40bd9a5">
</div>
</div>
</div>
</article>
<div class="wrapper wrapper-modal-window wrapper-modal-window-mock">
<div class="modal-window confirm modal-editor modal-lg modal-type-html %>">
<div class="edit-xblock-modal" action="#">
<div class="modal-header">
<h2 class="title modal-window-title">Mock Modal Title</h2>
</div>
<div class="modal-content">
<div class="xblock-editor"></div>
</div>
<div class="modal-actions">
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-save">Save</a>
</li>
<li class="action-item">
<a href="#" class="button action-secondary action-cancel">Cancel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="xblock-editor"></div>
......@@ -2,73 +2,6 @@
<div class="wrapper-comp-editor is-active" id="editor-tab" data-base-asset-url="/c4x/AndyA/ABT101/asset/">
</div>
<section class="sequence-edit">
<section class="filters wip">
<ul>
<li>
<h2>Sort:</h2>
<select>
<option value="">Linear Order</option>
<option value="">Recently Modified</option>
<option value="">Type</option>
<option value="">Alphabetically</option>
</select>
</li>
<li>
<h2>Filter:</h2>
<select>
<option value="">All content</option>
<option value="">Videos</option>
<option value="">Problems</option>
<option value="">Labs</option>
<option value="">Tutorials</option>
<option value="">HTML</option>
</select>
<a href="#" class="more">More</a>
</li>
<li class="search">
<input type="search" name="" id="" value="" placeholder="Search" />
</li>
</ul>
</section>
<div class="content">
<section class="modules">
<ol>
<li>
<ol id="sortable">
<li class="wrapper">
<a href="#" class="module-edit"
data-id="i4x://AndyA/ABT101/poll_question/T1_poll"
data-type="None"
data-preview-type="Poll">Poll Question</a>
<a href="#" class="draggable">handle</a>
</li>
<li class="wrapper">
<a href="#" class="module-edit"
data-id="i4x://AndyA/ABT101/conditional/cond_l1_poll_yes_foo"
data-type="SequenceDescriptor"
data-preview-type="Conditional">Challenge question for yes</a>
<a href="#" class="draggable">handle</a>
</li>
<li class="wrapper">
<a href="#" class="module-edit"
data-id="i4x://AndyA/ABT101/conditional/cond_l1_poll_no_foo"
data-type="SequenceDescriptor"
data-preview-type="Conditional">Challenge question for no</a>
<a href="#" class="draggable">handle</a>
</li>
</ol>
</li>
</ol>
</section>
</div>
<script id="metadata-editor-tpl" type="text/template">
<ul class="list-input settings-list">
<% _.each(_.range(numEntries), function() { %>
......@@ -79,17 +12,6 @@
</script>
<script id="metadata-number-entry" type="text/template">
<div class="wrapper-comp-setting">
\t<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
\t<input class="input setting-input setting-input-number" type="number" id="<%= uniqueId %>" value='<%= model.get("value") %>'/>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<script id="metadata-string-entry" type="text/template">
<div class="wrapper-comp-setting">
\t<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
......@@ -101,70 +23,7 @@
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<script id="metadata-option-entry" type="text/template">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
<select class="input setting-input" id="<%= uniqueId %>" name="<%= model.get('display_name') %>">
<% _.each(model.get('options'), function(option) { %>
<% if (option.display_name !== undefined) { %>
<option value="<%= option['display_name'] %>"><%= option['display_name'] %></option>
<% } else { %>
<option value="<%= option %>"><%= option %></option>
<% } %>
<% }) %>
</select>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<script id="metadata-list-entry" type="text/template">
<div class="wrapper-comp-setting metadata-list-enum">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name')%></label>
<div id="<%= uniqueId %>" class="wrapper-list-settings">
<ol class="list-settings">
</ol>
<a href="#" class="create-action create-setting">
<i class="icon-plus"></i><%= gettext("Add") %> <span class="sr"><%= model.get('display_name')%></span>
</a>
</div>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i>
<span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<script id="metadata-dict-entry" type="text/template">
<div class="wrapper-comp-setting metadata-dict">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name')%></label>
<div id="<%= uniqueId %>" class="wrapper-dict-settings">
<ol class="list-settings"></ol>
<a href="#" class="create-action create-setting">
<i class="icon-plus"></i><%= gettext("Add") %> <span class="sr"><%= model.get('display_name')%></span>
</a>
</div>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i>
<span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='{&#34;display_name&#34;: {&#34;default_value&#34;: null, &#34;explicitly_set&#34;: true, &#34;display_name&#34;: &#34;Display Name&#34;, &#34;help&#34;: &#34;This name appears in the horizontal navigation at the top of the page.&#34;, &#34;type&#34;: &#34;Generic&#34;, &#34;value&#34;: &#34;Poll Question&#34;, &#34;field_name&#34;: &#34;display_name&#34;, &#34;options&#34;: []}, &#34;due&#34;: {&#34;default_value&#34;: null, &#34;explicitly_set&#34;: false, &#34;display_name&#34;: &#34;due&#34;, &#34;help&#34;: &#34;Date that this problem is due by&#34;, &#34;type&#34;: &#34;Generic&#34;, &#34;value&#34;: null, &#34;field_name&#34;: &#34;due&#34;, &#34;options&#34;: []}}'/>
</section>
</div>
<div class="xblock xblock-studio_view xmodule_edit xmodule_WrapperDescriptor" data-runtime-class="StudioRuntime" data-init="XBlockToXModuleShim" data-runtime-version="1" data-usage-id="i4x:;_;_AndyA;_ABT101;_wrapper;_wrapper_l1_poll" data-type="VerticalDescriptor" data-block-type="wrapper" tabindex="0">
<section class="sequence-edit">
<script id="metadata-editor-tpl" type="text/template">
<ul class="list-input settings-list">
<% _.each(_.range(numEntries), function() { %>
<li class="field comp-setting-entry metadata_entry">
</li>
<% }) %>
</ul>
</script>
<script id="metadata-string-entry" type="text/template">
<div class="wrapper-comp-setting">
\t<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
\t<input class="input setting-input" type="text" id="<%= uniqueId %>" value='<%= model.get("value") %>'/>
\t<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
</script>
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='{&#34;display_name&#34;: {&#34;default_value&#34;: null, &#34;explicitly_set&#34;: true, &#34;display_name&#34;: &#34;Display Name&#34;, &#34;help&#34;: &#34;This name appears in the horizontal navigation at the top of the page.&#34;, &#34;type&#34;: &#34;Generic&#34;, &#34;value&#34;: &#34;Poll Question&#34;, &#34;field_name&#34;: &#34;display_name&#34;, &#34;options&#34;: []}, &#34;due&#34;: {&#34;default_value&#34;: null, &#34;explicitly_set&#34;: false, &#34;display_name&#34;: &#34;due&#34;, &#34;help&#34;: &#34;Date that this problem is due by&#34;, &#34;type&#34;: &#34;Generic&#34;, &#34;value&#34;: null, &#34;field_name&#34;: &#34;due&#34;, &#34;options&#34;: []}}'/>
</section>
</div>
<li class="action-item">
<a href="#" class="button <%= isPrimary ? 'action-primary' : '' %> action-<%= type %>"><%= name %></a>
</li>
<div id="dialog-assetupload"
class="wrapper wrapper-modal-window wrapper-modal-window-assetupload <% if(shown) { print('is-shown') } %>"
aria-describedby="dialog-assetupload-description"
aria-labelledby="dialog-assetupload-title"
aria-hidden="<%= !shown %>"
role="dialog">
<div class="modal-window modal-sm confirm">
<form class="upload-dialog" method="POST" action="<%= url %>" enctype="multipart/form-data">
<div class="modal-content">
<h2 class="title"><%= title %></h2>
<% if(error) {%>
<div id="upload_error" class="message message-status error is-shown" name="upload_error">
<p><%= error.message %></p>
</div>
<% } %>
<p id="dialog-assetupload-description" class="message"><%= message %></p>
<input type="file" name="file" <% if(error && error.attributes && error.attributes.selectedFile) {%>class="error"<% } %> />
<div class="status-upload">
<p id="dialog-assetupload-description" class="message"><%= message %></p>
<input type="file" name="file" <% if(error && error.attributes && error.attributes.selectedFile) {%>class="error"<% } %> />
<% if(uploading) { %>
<div class="wrapper-progress">
<% if (uploadedBytes && totalBytes) { %>
<progress value="<%= uploadedBytes %>" max="<%= totalBytes %>"><%= uploadedBytes/totalBytes*100 %>%</progress>
<% } else { %>
<progress></progress>
<% } %>
</div>
<% } %>
<div class="status-upload">
<% if(finished) { %>
<div id="upload_confirm" class="message message-status message-status confirm success is-shown" name="upload_confirm">
<p><%= gettext("Success!") %></p>
</div>
<% } %>
<% if(uploading) { %>
<div class="wrapper-progress">
<% if (uploadedBytes && totalBytes) { %>
<progress value="<%= uploadedBytes %>" max="<%= totalBytes %>"><%= uploadedBytes/totalBytes*100 %>%</progress>
<% } else { %>
<progress></progress>
<% } %>
</div>
<% } %>
</div>
</div>
<% if(error) {%>
<div id="upload_error" class="message message-status error is-shown" name="upload_error">
<p><%= error.message %></p>
</div>
<% } %>
<div class="actions modal-actions">
<h3 class="sr"><%= gettext('Form Actions') %></h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-upload <% if (!selectedFile || error) { %>disabled<% } %>"><%= gettext('Upload') %></a>
</li>
<li class="action-item">
<a href="#" class="button action-secondary action-cancel"><%= gettext('Cancel') %></a>
</li>
</ul>
</div>
</form>
<% if(finished) { %>
<div id="upload_confirm" class="message message-status confirm success is-shown" name="upload_confirm">
<p><%= gettext("File upload succeeded") %></p>
</div>
<% } %>
</div>
</div>
</div>
</form>
......@@ -8,13 +8,17 @@
from xmodule.modulestore.django import loc_mapper
%>
<%block name="header_extras">
% for template_name in ["basic-modal", "modal-button", "upload-dialog"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script type="text/template" id="upload-dialog-tpl">
<%static:include path="js/upload-dialog.underscore" />
</script>
<script type="text/javascript">
window.CMS = window.CMS || {};
CMS.URL = CMS.URL || {};
......
......@@ -7,7 +7,7 @@
<%block name="bodyclass">is-signedin course view-textbooks</%block>
<%block name="header_extras">
% for template_name in ["edit-textbook", "show-textbook", "edit-chapter", "no-textbooks", "upload-dialog"]:
% for template_name in ["edit-textbook", "show-textbook", "edit-chapter", "no-textbooks", "basic-modal", "modal-button", "upload-dialog"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......
......@@ -10,7 +10,7 @@ from xmodule.modulestore.django import loc_mapper
<%block name="bodyclass">is-signedin course unit view-unit feature-upload</%block>
<%block name="header_extras">
% for template_name in ["image-modal", "edit-xblock-modal", "editor-mode-button", "upload-dialog"]:
% for template_name in ["image-modal", "basic-modal", "modal-button", "edit-xblock-modal", "editor-mode-button", "upload-dialog"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......
......@@ -105,18 +105,15 @@ class Component(PageObject):
def edit(self):
self.q(css=self._bounded_selector('.edit-button')).first.click()
EmptyPromise(
lambda: all(
self.q(css=self._bounded_selector('.component-editor'))
.map(lambda el: el.is_displayed())
.results),
"Verify that the editor for component {} has been expanded".format(self.locator)
lambda: self.q(css='.xblock-studio_view').present,
'Wait for the Studio editor to be present'
).fulfill()
return self
@property
def editor_selector(self):
return self._bounded_selector('.xblock-studio_view')
return '.xblock-studio_view'
def go_to_container(self):
"""
......
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