Commit c3161007 by gradyward

Added UI functionality to allow Three Tab OA Creation

parent 9355cc55
......@@ -7,3 +7,4 @@ Brian Talbot <btalbot@edx.org>
Mark Hoeber <hoeber@edx.org>
Sylvia Pearce <spearce@edx.org>
Ned Batchelder <ned@nedbatchelder.com>
Grady Ward <gward@brandeis.edu>
\ No newline at end of file
{% load i18n %}
<div id="openassessment-edit" class="editor-with-buttons">
<textarea class="openassessment-editor"></textarea>
<div id="openassessment-editor" class="editor-with-buttons editor-with-tabs">
<div class="openassessment-editor-content-and-tabs">
<div class="openassessment-editor-header">
<h6 id="oa-editor-window-title" class="title modal-window-title" >Editing: Open Assessment</h6>
<ul class="editor-modes action-list action-modes editor-tabs">
<li class="view-button oa-editor-tab"><a href="#oa-settings-editor-wrapper">{% trans "Settings" %}</a></li>
<li class="view-button oa-editor-tab"><a href="#oa-rubric-editor-wrapper">{% trans "Rubric" %}</a></li>
<li class="view-button oa-editor-tab"><a href="#oa-prompt-editor-wrapper">{% trans "Prompt" %}</a></li>
</ul>
</div>
<div id = "oa-prompt-editor-wrapper" class="oa-editor-content-wrapper">
<h2>Prompt Editor</h2>
<textarea class="openassessment-prompt-editor"></textarea>
</div>
<div id="oa-rubric-editor-wrapper" class="oa-editor-content-wrapper">
<h2>Rubric Editor</h2>
<textarea class="openassessment-rubric-editor"></textarea>
</div>
<div id="oa-settings-editor-wrapper" class="oa-editor-content-wrapper">
<div id="oa-settings-editor-text-fields">
<h2>Settings Editor</h2>
<h2>Title:</h2>
<input type="text" name="title" class="openassessment-title-editor">
<h2>Submission Start Date: </h2>
<input type="text" name="start_date" class="openassessment-submission-start-editor">
<h2>Submission Due Date:</h2>
<input type="text" name="due_date" class="openassessment-submission-due-editor">
</div>
<div id="oa-settings-assessments">
XML For Assessments:
<textarea class="openassessment-assessments-editor"></textarea>
</div>
</div>
</div>
<div class="xblock-actions">
<h3 class="sr">Actions</h3>
<ul>
......
......@@ -22,7 +22,7 @@ from openassessment.xblock.lms_mixin import LmsCompatibilityMixin
from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin
from openassessment.xblock.submission_mixin import SubmissionMixin
from openassessment.xblock.studio_mixin import StudioMixin
from openassessment.xblock.xml import update_from_xml, serialize_content_to_xml
from openassessment.xblock.xml import serialize_content_to_xml
from openassessment.xblock.staff_info_mixin import StaffInfoMixin
from openassessment.xblock.workflow_mixin import WorkflowMixin
from openassessment.workflow import api as workflow_api
......
......@@ -2133,6 +2133,48 @@ hr.divider,
.step--student-training .message--incorrect.is--hidden .step__header {
border-bottom: none; }
#openassessment-editor .openassessment-editor-content-and-tabs {
width: 100%;
height: 370px; }
#openassessment-editor .openassessment-editor-header {
background-color: #e5e5e5;
width: 100%;
top: 0; }
#openassessment-editor #oa-editor-window-title {
float: left; }
#openassessment-editor .oa-editor-tab {
float: right;
padding: 2.5px 5px;
margin: 2.5px 5px; }
#openassessment-editor .oa-editor-content-wrapper {
height: 100%;
width: 100%;
padding: 5px 10px; }
#openassessment-editor .openassessment-prompt-editor {
width: 100%;
height: 100%;
resize: none; }
#openassessment-editor .openassessment-rubric-editor {
width: 100%;
height: 100%; }
#openassessment-editor .openassessment-assessments-editor {
width: 100%; }
#openassessment-editor #oa-settings-editor-text-fields {
float: left;
width: 30%; }
#openassessment-editor #oa-settings-assessments {
float: right;
width: 70%;
height: 100%; }
#openassessment-editor .xblock-actions {
background-color: #e5e5e5;
position: absolute;
width: 100%;
bottom: 0; }
.modal-content {
height: 500px !important; }
.openassessment .self-assessment__display__header, .openassessment .peer-assessment__display__header, .openassessment .step__header {
margin-bottom: 0 !important;
border-radius: 0 !important;
......
/**
Interface for editing view in Studio.
The constructor initializes the DOM for editing.
Interface for editing view in Studio.
The constructor initializes the DOM for editing.
Args:
runtime (Runtime): an XBlock runtime instance.
element (DOM element): The DOM element representing this XBlock.
server (OpenAssessment.Server): The interface to the XBlock server.
Args:
runtime (Runtime): an XBlock runtime instance.
element (DOM element): The DOM element representing this XBlock.
server (OpenAssessment.Server): The interface to the XBlock server.
Returns:
OpenAssessment.StudioView
**/
Returns:
OpenAssessment.StudioView
**/
OpenAssessment.StudioView = function(runtime, element, server) {
this.runtime = runtime;
this.server = server;
// Initialize the code box
this.codeBox = CodeMirror.fromTextArea(
$(element).find('.openassessment-editor').first().get(0),
this.promptBox = $('.openassessment-prompt-editor').first().get(0);
this.rubricXmlBox = CodeMirror.fromTextArea(
$(element).find('.openassessment-rubric-editor').first().get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true}
);
this.titleField = $(element).find('.openassessment-title-editor');
this.submissionStartField = $(element).find('.openassessment-submission-start-editor').first().get(0);
this.submissionDueField = $(element).find('.openassessment-submission-due-editor').first().get(0);
this.assessmentsXmlBox = CodeMirror.fromTextArea(
$(element).find('.openassessment-assessments-editor').first().get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true}
);
// Install click handlers
var view = this;
$(element).find('.openassessment-save-button').click(
function(eventData) {
function (eventData) {
view.save();
});
});
$(element).find('.openassessment-cancel-button').click(
function(eventData) {
function (eventData) {
view.cancel();
});
};
});
$('.openassessment-editor-content-and-tabs').tabs();
};
OpenAssessment.StudioView.prototype = {
/**
Load the XBlock XML definition from the server and display it in the view.
**/
load: function() {
Load the XBlock XML definition from the server and display it in the view.
**/
load: function () {
var view = this;
this.server.loadXml().done(
function(xml) {
view.codeBox.setValue(xml);
}).fail(function(msg) {
function (prompt, rubricXml, settings) {
view.rubricXmlBox.setValue(rubricXml);
view.assessmentsXmlBox.setValue(settings.assessments);
view.submissionStartField.value = settings.submission_start;
view.submissionDueField.value = settings.submission_due;
view.promptBox.value = prompt;
view.titleField.value = settings.title;
}).fail(function (msg) {
view.showError(msg);
}
);
},
/**
Save the problem's XML definition to the server.
If the problem has been released, make the user confirm the save.
**/
save: function() {
Save the problem's XML definition to the server.
If the problem has been released, make the user confirm the save.
**/
save: function () {
var view = this;
// Check whether the problem has been released; if not,
// warn the user and allow them to cancel.
this.server.checkReleased().done(
function(isReleased) {
if (isReleased) { view.confirmPostReleaseUpdate($.proxy(view.updateXml, view)); }
else { view.updateXml(); }
function (isReleased) {
if (isReleased) {
view.confirmPostReleaseUpdate($.proxy(view.updateXml, view));
}
else {
view.updateXml();
}
}
).fail(function(errMsg) {
view.showError(msg);
});
).fail(function (errMsg) {
view.showError(msg);
});
},
/**
Make the user confirm that he/she wants to update a problem
that has already been released.
Args:
onConfirm (function): A function that accepts no arguments,
executed if the user confirms the update.
**/
confirmPostReleaseUpdate: function(onConfirm) {
Make the user confirm that he/she wants to update a problem
that has already been released.
Args:
onConfirm (function): A function that accepts no arguments,
executed if the user confirms the update.
**/
confirmPostReleaseUpdate: function (onConfirm) {
var msg = gettext("This problem has already been released. Any changes will apply only to future assessments.");
// TODO: classier confirm dialog
if (confirm(msg)) { onConfirm(); }
if (confirm(msg)) {
onConfirm();
}
},
/**
Save the updated XML definition to the server.
**/
updateXml: function() {
Save the updated XML definition to the server.
**/
updateXml: function () {
// Notify the client-side runtime that we are starting
// to save so it can show the "Saving..." notification
this.runtime.notify('save', {state: 'start'});
// Send the updated XML to the server
var xml = this.codeBox.getValue();
var prompt = this.promptBox.value;
var rubricXml = this.rubricXmlBox.getValue();
var title = this.titleField.value;
var sub_start = this.submissionStartField.value;
var sub_due = this.submissionDueField.value;
var assessmentsXml = this.assessmentsXmlBox.getValue();
var view = this;
this.server.updateXml(xml).done(function() {
this.server.updateXml(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml).done(function () {
// Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification.
view.runtime.notify('save', {state: 'end'});
// Reload the XML definition in the editor
view.load();
}).fail(function(msg) {
}).fail(function (msg) {
view.showError(msg);
});
},
/**
Cancel editing.
**/
cancel: function() {
Cancel editing.
**/
cancel: function () {
// Notify the client-side runtime so it will close the editing modal.
this.runtime.notify('cancel', {});
},
/**
Display an error message to the user.
Display an error message to the user.
Args:
errorMsg (string): The error message to display.
**/
showError: function(errorMsg) {
Args:
errorMsg (string): The error message to display.
**/
showError: function (errorMsg) {
this.runtime.notify('error', {msg: errorMsg});
}
};
......@@ -130,11 +163,11 @@ OpenAssessment.StudioView.prototype = {
function OpenAssessmentEditor(runtime, element) {
/**
Initialize the editing interface on page load.
**/
$(function($) {
Initialize the editing interface on page load.
**/
$(function ($) {
var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.StudioView(runtime, element, server);
view.load();
});
}
};
\ No newline at end of file
......@@ -356,7 +356,7 @@ OpenAssessment.Server.prototype = {
$.ajax({
type: "POST", url: url, data: "\"\""
}).done(function(data) {
if (data.success) { defer.resolveWith(this, [data.xml]); }
if (data.success) { defer.resolveWith(this, [data.prompt, data.rubric, data.settings]); }
else { defer.rejectWith(this, [data.msg]); }
}).fail(function(data) {
defer.rejectWith(this, [gettext('This problem could not be loaded.')]);
......@@ -378,9 +378,15 @@ OpenAssessment.Server.prototype = {
function(err) { console.log(err); }
);
**/
updateXml: function(xml) {
updateXml: function(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml) {
var url = this.url('update_xml');
var payload = JSON.stringify({xml: xml});
var settings = {
'title': title,
'submission_start': sub_start,
'submission_due': sub_due,
'assessments': assessmentsXml
};
var payload = JSON.stringify({'prompt': prompt, 'rubric': rubricXml, 'settings': settings});
return $.Deferred(function(defer) {
$.ajax({
type: "POST", url: url, data: payload
......
......@@ -57,8 +57,8 @@
}
.openassessment__student-info_list {
list-style-type: none;
}
list-style-type: none;
}
.value {
width: $max-width/2;
......@@ -165,6 +165,74 @@
}
}
}
}
// --------------------
// Developer Styles for Studio Editing of OA problems
// --------------------
#openassessment-editor {
.openassessment-editor-content-and-tabs {
width: 100%;
height: 370px;
}
.openassessment-editor-header{
background-color: #e5e5e5;
width: 100%;
top: 0;
}
#oa-editor-window-title{
float: left;
}
.oa-editor-tab{
float: right;
padding: ($baseline-v/8) ($baseline-h/8);
margin: ($baseline-v/8) ($baseline-h/8);
}
.oa-editor-content-wrapper {
height: 100%;
width: 100%;
padding: ($baseline-v/4) ($baseline-h/4);
}
.openassessment-prompt-editor {
width: 100%;
height: 100%;
resize: none;
}
.openassessment-rubric-editor {
width: 100%;
height: 100%;
}
.openassessment-assessments-editor {
width: 100%;
}
#oa-settings-editor-text-fields {
float: left;
width: 30%;
}
#oa-settings-assessments{
float: right;
width: 70%;
height: 100%;
}
.xblock-actions {
background-color: #e5e5e5;
position: absolute;
width: 100%;
bottom: 0;
}
}
.modal-content {
height: 500px !important;
}
......@@ -8,7 +8,11 @@ from django.template.loader import get_template
from django.utils.translation import ugettext as _
from xblock.core import XBlock
from xblock.fragment import Fragment
from openassessment.xblock.xml import serialize_content, update_from_xml_str, ValidationError, UpdateFromXmlError
from openassessment.xblock.xml import(
serialize_content, parse_rubric_xml, parse_assessments_xml, UpdateFromXmlError, serialize_rubric_to_xml_str,
serialize_assessments_to_xml_str
)
from openassessment.xblock.validation import validator
......@@ -51,21 +55,52 @@ class StudioMixin(object):
Returns:
dict with keys 'success' (bool) and 'msg' (str)
"""
if 'xml' in data:
try:
update_from_xml_str(self, data['xml'], validator=validator(self))
if 'rubric' not in data:
return {'success': False, 'msg': _('Must specify "rubric" in request JSON dict.')}
except ValidationError as ex:
return {'success': False, 'msg': _('Validation error: {error}').format(error=ex)}
if 'settings' not in data:
return {'success': False, 'msg': _('Must specify "settings" in request JSON dict.')}
except UpdateFromXmlError as ex:
return {'success': False, 'msg': _('An error occurred while saving: {error}').format(error=ex)}
if 'prompt' not in data:
return {'success': False, 'msg': _('Must specify "prompt" in request JSON dict.')}
else:
return {'success': True, 'msg': _('Successfully updated OpenAssessment XBlock')}
settings = data['settings']
try:
rubric = parse_rubric_xml(data['rubric'])
assessments = parse_assessments_xml(settings['assessments'])
submission_due = settings["submission_due"]
except UpdateFromXmlError as ex:
return {'success': False, 'msg': _('An error occurred while saving: {error}').format(error=ex)}
xblock_validator = validator(self)
success, msg = xblock_validator(rubric, {'due': submission_due}, assessments)
if not success:
return {'success': False, 'msg': _('Validation error: {error}').format(error=msg)}
self.update(
rubric,
assessments,
settings["submission_due"],
settings["submission_start"],
settings["title"],
data['prompt']
)
return {'success': True, 'msg': _('Successfully updated OpenAssessment XBlock')}
def update(self, rubric, assessments, submission_due, submission_start, title, prompt):
"""
Given a dictionary of properties, update the XBlock
else:
return {'success': False, 'msg': _('Must specify "xml" in request JSON dict.')}
"""
# If we've gotten this far, then we've successfully parsed the XML
# and validated the contents. At long last, we can safely update the XBlock.
self.title = title
self.prompt = prompt
self.rubric_criteria = rubric['criteria']
self.rubric_assessments = assessments
self.rubric_feedback_prompt = rubric['feedback_prompt']
self.submission_start = submission_start
self.submission_due = submission_due
@XBlock.json_handler
def xml(self, data, suffix=''):
......@@ -82,8 +117,14 @@ class StudioMixin(object):
dict with keys 'success' (bool), 'message' (unicode), and 'xml' (unicode)
"""
try:
xml = serialize_content(self)
rubric = serialize_rubric_to_xml_str(self)
prompt = self.prompt
settings = {
'title': self.title,
'submission_start': self.submission_start,
'submission_due': self.submission_due,
'assessments': serialize_assessments_to_xml_str(self)
}
# We do not expect `serialize_content` to raise an exception,
# but if it does, handle it gracefully.
except Exception as ex:
......@@ -91,7 +132,7 @@ class StudioMixin(object):
logger.error(msg)
return {'success': False, 'msg': msg, 'xml': u''}
else:
return {'success': True, 'msg': '', 'xml': xml}
return {'success': True, 'msg': '', 'prompt': prompt, 'rubric': rubric, 'settings': settings}
@XBlock.json_handler
def check_released(self, data, suffix=''):
......@@ -112,4 +153,4 @@ class StudioMixin(object):
return {
'success': True, 'msg': u'',
'is_released': self.is_released()
}
}
\ No newline at end of file
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