Commit ce363853 by Calen Pennington

Make inline editing save to mongo and then update the preview

parent ff002e35
......@@ -327,7 +327,10 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
module,
"xmodule_edit.html",
{
'location': descriptor.location.url(),
'editor_content': descriptor.get_html(),
'editor_type': descriptor.js_module_name,
'editor_class': descriptor.__class__.__name__,
# TODO (cpennington): Make descriptors know if they have data that can be editng
'editable_data': descriptor.definition.get('data'),
'editable_class': 'editable' if descriptor.definition.get('data') else '',
......@@ -396,7 +399,7 @@ def save_item(request):
export_to_github(course, "CMS Edit", author_string)
descriptor = modulestore().get_item(item_location)
preview_html = get_module_previews(request, descriptor)
preview_html = get_module_previews(request, descriptor)[0]
return HttpResponse(json.dumps(preview_html))
......
......@@ -5,24 +5,12 @@ class CMS.Models.Module extends Backbone.Model
children: ''
metadata: {}
loadModule: (element) ->
elt = $(element).find('.xmodule_edit').first()
@module = XModule.loadModule(elt)
# find the metadata edit region which should be setup server side,
# so that we can wire up posting back those changes
@metadata_elt = $(element).find('.metadata_edit')
editUrl: ->
"/edit_item?#{$.param(id: @get('id'))}"
initialize: (attributes) ->
@module = attributes.module
@unset('module')
delete attributes.module
super(attributes)
save: (args...) ->
@set(data: @module.save()) if @module
# cdodge: package up metadata which is separated into a number of input fields
# there's probably a better way to do this, but at least this lets me continue to move onwards
if @metadata_elt
_metadata = {}
# walk through the set of elments which have the 'xmetadata_name' attribute and
# build up a object to pass back to the server on the subsequent POST
_metadata[$(el).data("metadata-name")]=el.value for el in $('[data-metadata-name]', @metadata_elt)
@set(metadata: _metadata)
super(args...)
class CMS.Views.ModuleEdit extends Backbone.View
tagName: 'section'
className: 'edit-pane'
events:
'click .cancel': 'cancel'
'click .module-edit': 'editSubmodule'
'click .save-update': 'save'
tagName: 'div'
className: 'xmodule_edit'
initialize: ->
@$el.load @model.editUrl(), =>
@model.loadModule(@el)
# Load preview modules
XModule.loadModules('display')
@$children = @$el.find('#sortable')
@enableDrag()
enableDrag: =>
# Enable dragging things in the #sortable div (if there is one)
if @$children.length > 0
@$children.sortable(
placeholder: "ui-state-highlight"
update: (event, ui) =>
@model.set(children: @$children.find('.module-edit').map(
(idx, el) -> $(el).data('id')
).toArray())
)
@$children.disableSelection()
@delegate()
@$component_editor = @$el.find('.component-editor')
@$metadata = @$component_editor.find('.metadata_edit')
delegate: ->
id = @$el.data('id')
events = {}
events["click .component-editor[data-id=#{ id }] .cancel-button"] = 'cancel'
events["click .component-editor[data-id=#{ id }] .save-button"] = 'save'
events["click .component-actions[data-id=#{ id }] .edit-button"] = 'edit'
@delegateEvents(events)
metadata: ->
# cdodge: package up metadata which is separated into a number of input fields
# there's probably a better way to do this, but at least this lets me continue to move onwards
_metadata = {}
if @$metadata
# walk through the set of elments which have the 'xmetadata_name' attribute and
# build up a object to pass back to the server on the subsequent POST
_metadata[$(el).data("metadata-name")] = el.value for el in $('[data-metadata-name]', @$metadata)
_metadata
save: (event) =>
event.preventDefault()
@model.save().done((previews) =>
@model.save(
metadata: @metadata()
).done((preview) =>
alert("Your changes have been saved.")
previews_section = @$el.find('.previews').empty()
$.each(previews, (idx, preview) =>
preview_wrapper = $('<section/>', class: 'preview').append preview
previews_section.append preview_wrapper
)
new_el = $(preview)
@$el.replaceWith(new_el)
@$el = new_el
@delegate()
XModule.loadModules('display')
@model.module = XModule.loadModule(@$el)
XModule.loadModules(@$el)
).fail( ->
alert("There was an error saving your changes. Please try again.")
)
cancel: (event) ->
event.preventDefault()
CMS.popView()
@enableDrag()
@$el.removeClass('editing')
@$component_editor.slideUp(150)
editSubmodule: (event) ->
edit: (event) ->
event.preventDefault()
previewType = $(event.target).data('preview-type')
moduleType = $(event.target).data('type')
CMS.pushView new CMS.Views.ModuleEdit
model: new CMS.Models.Module
id: $(event.target).data('id')
type: if moduleType == 'None' then null else moduleType
previewType: if previewType == 'None' then null else previewType
@enableDrag()
@$el.addClass('editing')
@$component_editor.slideDown(150)
......@@ -6,162 +6,175 @@ var $newComponentStep1;
var $newComponentStep2;
$(document).ready(function() {
$body = $('body');
$modal = $('.history-modal');
$modalCover = $('.modal-cover');
$newComponentItem = $('.new-component-item');
$newComponentStep1 = $('.new-component-step-1');
$newComponentStep2 = $('.new-component-step-2');
$newComponentButton = $('.new-component-button');
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
$body.delegate('.xmodule_edit .edit-button', 'click', editComponent);
$body.delegate('.component-editor .save-button, .component-editor .cancel-button', 'click', closeComponentEditor);
$newComponentButton.bind('click', showNewComponentForm);
$newComponentStep1.find('.new-component-type a').bind('click', showNewComponentProperties);
$newComponentStep2.find('.save-button').bind('click', saveNewComponent);
$newComponentStep2.find('.cancel-button').bind('click', cancelNewComponent);
$('.unit-history ol a').bind('click', showHistoryModal);
$modal.bind('click', hideHistoryModal);
$modalCover.bind('click', hideHistoryModal);
XModule.loadModules('display');
$body = $('body');
$modal = $('.history-modal');
$modalCover = $('.modal-cover');
$newComponentItem = $('.new-component-item');
$newComponentStep1 = $('.new-component-step-1');
$newComponentStep2 = $('.new-component-step-2');
$newComponentButton = $('.new-component-button');
$(document).bind('XModule.loaded', function(e, element) {
if ($(element).hasClass('.xmodule_display')) {
return
}
var previewType = $(element).data('preview-type');
var moduleType = $(element).data('type');
new CMS.Views.ModuleEdit({
el: element,
model: new CMS.Models.Module({
module: $(element).data('module'),
id: $(element).data('id'),
type: moduleType == 'None' ? null : moduleType,
previewType: previewType == 'None' ? null : previewType,
})
});
});
XModule.loadModules()
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
$newComponentButton.bind('click', showNewComponentForm);
$newComponentStep1.find('.new-component-type a').bind('click', showNewComponentProperties);
$newComponentStep2.find('.save-button').bind('click', saveNewComponent);
$newComponentStep2.find('.cancel-button').bind('click', cancelNewComponent);
$('.unit-history ol a').bind('click', showHistoryModal);
$modal.bind('click', hideHistoryModal);
$modalCover.bind('click', hideHistoryModal);
});
function toggleSubmodules(e) {
e.preventDefault();
$(this).toggleClass('expand').toggleClass('collapse');
$(this).closest('.branch, .window').toggleClass('collapsed');
e.preventDefault();
$(this).toggleClass('expand').toggleClass('collapse');
$(this).closest('.branch, .window').toggleClass('collapsed');
}
function setVisibility(e) {
$(this).find('.checked').removeClass('checked');
$(e.target).closest('.option').addClass('checked');
$(this).find('.checked').removeClass('checked');
$(e.target).closest('.option').addClass('checked');
}
function editComponent(e) {
e.preventDefault();
$(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
e.preventDefault();
$(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
}
function closeComponentEditor(e) {
e.preventDefault();
$(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
e.preventDefault();
$(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
}
function showNewComponentForm(e) {
e.preventDefault();
$newComponentItem.addClass('adding');
$(this).slideUp(150);
$newComponentStep1.slideDown(150);
e.preventDefault();
$newComponentItem.addClass('adding');
$(this).slideUp(150);
$newComponentStep1.slideDown(150);
}
function showNewComponentProperties(e) {
e.preventDefault();
var displayName;
var componentSource;
var selectionRange;
var $renderedComponent;
switch($(this).attr('data-type')) {
case 'video':
displayName = 'Video';
componentSource = '<video youtube="1.50:___,1.25:___,1.0:___,0.75:___"/>';
selectionRange = [21, 24];
$renderedComponent = $('<div class="rendered-component"><div class="video-unit"><img src="images/video-module.png"></div></div>');
break;
case 'textbook':
displayName = 'Textbook';
componentSource = '<customtag page="___"><impl>book</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="textbook-icon"></span>More information given in the text.</p></div>');
break;
case 'slide':
displayName = 'Slide';
componentSource = '<customtag page="___"><customtag lecnum="___"><impl>slides</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="slides-icon"></span>Lecture Slides Handout [Clean] [Annotated]</p></div>');
break;
case 'discussion':
displayName = 'Discussion';
componentSource = '<discussion for="___" id="___" discussion_category="___"/>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><div class="discussion-unit"><img src="images/discussion-module.png"></div></div>');
break;
case 'problem':
displayName = 'Problem';
componentSource = '<problem>___</problem>';
selectionRange = [9, 12];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
case 'freeform':
displayName = 'Freeform HTML';
componentSource = '';
selectionRange = [0, 0];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
}
$newComponentItem.prepend($renderedComponent);
$renderedComponent.slideDown(250);
$newComponentStep2.find('h5').html('Edit ' + displayName + ' Component');
$newComponentStep2.find('textarea').html(componentSource);
setTimeout(function() {
$newComponentStep2.find('textarea').focus().get(0).setSelectionRange(selectionRange[0], selectionRange[1]);
}, 10);
$newComponentStep1.slideUp(250);
$newComponentStep2.slideDown(250);
e.preventDefault();
var displayName;
var componentSource;
var selectionRange;
var $renderedComponent;
switch($(this).attr('data-type')) {
case 'video':
displayName = 'Video';
componentSource = '<video youtube="1.50:___,1.25:___,1.0:___,0.75:___"/>';
selectionRange = [21, 24];
$renderedComponent = $('<div class="rendered-component"><div class="video-unit"><img src="images/video-module.png"></div></div>');
break;
case 'textbook':
displayName = 'Textbook';
componentSource = '<customtag page="___"><impl>book</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="textbook-icon"></span>More information given in the text.</p></div>');
break;
case 'slide':
displayName = 'Slide';
componentSource = '<customtag page="___"><customtag lecnum="___"><impl>slides</impl></customtag>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><p><span class="slides-icon"></span>Lecture Slides Handout [Clean] [Annotated]</p></div>');
break;
case 'discussion':
displayName = 'Discussion';
componentSource = '<discussion for="___" id="___" discussion_category="___"/>';
selectionRange = [17, 20];
$renderedComponent = $('<div class="rendered-component"><div class="discussion-unit"><img src="images/discussion-module.png"></div></div>');
break;
case 'problem':
displayName = 'Problem';
componentSource = '<problem>___</problem>';
selectionRange = [9, 12];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
case 'freeform':
displayName = 'Freeform HTML';
componentSource = '';
selectionRange = [0, 0];
$renderedComponent = $('<div class="rendered-component"></div>');
break;
}
$newComponentItem.prepend($renderedComponent);
$renderedComponent.slideDown(250);
$newComponentStep2.find('h5').html('Edit ' + displayName + ' Component');
$newComponentStep2.find('textarea').html(componentSource);
setTimeout(function() {
$newComponentStep2.find('textarea').focus().get(0).setSelectionRange(selectionRange[0], selectionRange[1]);
}, 10);
$newComponentStep1.slideUp(250);
$newComponentStep2.slideDown(250);
}
function cancelNewComponent(e) {
e.preventDefault();
e.preventDefault();
$newComponentStep2.slideUp(250);
$newComponentButton.slideDown(250);
$newComponentItem.removeClass('adding');
$newComponentItem.find('.rendered-component').remove();
$newComponentStep2.slideUp(250);
$newComponentButton.slideDown(250);
$newComponentItem.removeClass('adding');
$newComponentItem.find('.rendered-component').remove();
}
function saveNewComponent(e) {
e.preventDefault();
var $newComponent = $newComponentItem.clone();
$newComponent.removeClass('adding').removeClass('new-component-item');
$newComponent.find('.new-component-step-2').removeClass('new-component-step-2').addClass('component-editor');
setTimeout(function() {
$newComponent.find('.component-editor').slideUp(250);
}, 10);
$newComponent.append('<div class="component-actions"><a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a><a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a> </div><a href="#" class="drag-handle"></a>');
$newComponent.find('.new-component-step-1').remove();
$newComponent.find('.new-component-button').remove();
$newComponentStep2.slideUp(250);
$newComponentButton.slideDown(250);
$newComponentItem.removeClass('adding');
$newComponentItem.find('.rendered-component').remove();
$newComponentItem.before($newComponent);
e.preventDefault();
var $newComponent = $newComponentItem.clone();
$newComponent.removeClass('adding').removeClass('new-component-item');
$newComponent.find('.new-component-step-2').removeClass('new-component-step-2').addClass('component-editor');
setTimeout(function() {
$newComponent.find('.component-editor').slideUp(250);
}, 10);
$newComponent.append('<div class="component-actions"><a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a><a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a> </div><a href="#" class="drag-handle"></a>');
$newComponent.find('.new-component-step-1').remove();
$newComponent.find('.new-component-button').remove();
$newComponentStep2.slideUp(250);
$newComponentButton.slideDown(250);
$newComponentItem.removeClass('adding');
$newComponentItem.find('.rendered-component').remove();
$newComponentItem.before($newComponent);
}
function showHistoryModal(e) {
e.preventDefault();
e.preventDefault();
$modal.show();
$modalCover.show();
$modal.show();
$modalCover.show();
}
function hideHistoryModal(e) {
e.preventDefault();
e.preventDefault();
$modal.hide();
$modalCover.hide();
$modal.hide();
$modalCover.hide();
}
......
......@@ -84,7 +84,7 @@ class @Sequence
@mark_active new_position
@$('#seq_content').html @contents.eq(new_position - 1).text()
XModule.loadModules('display', @$('#seq_content'))
XModule.loadModules(@$('#seq_content'))
MathJax.Hub.Queue(["Typeset", MathJax.Hub, "seq_content"]) # NOTE: Actually redundant. Some other MathJax call also being performed
window.update_schematics() # For embedded circuit simulator exercises in 6.002x
......
@XModule =
###
Load a single module (either an edit module or a display module)
from the supplied element, which should have a data-type attribute
specifying the class to load
###
loadModule: (element) ->
moduleType = $(element).data('type')
if moduleType == 'None'
return
###
Load a single module (either an edit module or a display module)
from the supplied element, which should have a data-type attribute
specifying the class to load
###
loadModule: (element) ->
moduleType = $(element).data('type')
if moduleType == 'None'
return
try
new window[moduleType](element)
catch error
console.error "Unable to load #{moduleType}: #{error.message}" if console
try
$(element).data('module', new window[moduleType](element))
$(document).trigger('XModule.loaded', [element])
catch error
console.error "Unable to load #{moduleType}: #{error.message}" if console
###
Load all modules on the page of the specified type.
If container is provided, only load modules inside that element
Type is one of 'display' or 'edit'
###
loadModules: (type, container) ->
selector = ".xmodule_#{type}"
###
Load all modules on the page of the specified type.
If container is provided, only load modules inside that element
Type is one of 'display' or 'edit'
###
loadModules: (container) ->
selector = ".xmodule_edit, .xmodule_display"
if container?
modules = $(container).find(selector)
else
modules = $(selector)
if container?
modules = $(container).find(selector)
else
modules = $(selector)
modules.each (idx, element) -> XModule.loadModule element
modules.each (idx, element) -> XModule.loadModule element
<div class="xmodule_edit xmodule_${class_} ${editable_class}" data-type="${module_name}">
<div class="xmodule_edit xmodule_${editor_class} ${editable_class}" data-type="${editor_type}" data-id="${location}">
<%include file="xmodule_display.html"/>
% if editable_data:
<div class="component-actions">
<div class="component-actions" data-id="${location}">
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
</div>
<div class="component-editor">
<div class="component-editor" data-id="${location}">
${editor_content}
<a href="#" class="save-button">Save</a>
<a href="#" class="cancel-button">Cancel</a>
......
......@@ -11,7 +11,7 @@ class @Courseware
new Courseware
render: ->
XModule.loadModules('display')
XModule.loadModules()
$('.course-content .histogram').each ->
id = $(this).attr('id').replace(/histogram_/, '')
try
......
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