Commit 9896d39f by Will Daly

Merge pull request #203 from edx/will/saved-but-not-submitted

TIM-304
parents 94a82c6b 6a28f277
...@@ -71,7 +71,8 @@ ...@@ -71,7 +71,8 @@
<ul class="list list--actions"> <ul class="list list--actions">
<li class="list--actions__item"> <li class="list--actions__item">
<a aria-role="button" href="#" id="step--response__submit" class="action action--submit step--response__submit is--disabled"> <a aria-role="button" href="#" id="step--response__submit"
class="action action--submit step--response__submit {{ submit_enabled|yesno:",is--disabled" }}">
<span class="copy">Submit your response and move to the next step</span> <span class="copy">Submit your response and move to the next step</span>
<i class="ico icon-caret-right"></i> <i class="ico icon-caret-right"></i>
</a> </a>
......
<div id="openassessment-base">
<ol>
<li id="openassessment__response" class="openassessment__steps__step step--response ui-toggle-visibility">
<div class="ui-toggle-visibility__content">
<div class="wrapper--step__content">
<div class="step__content">
<form id="response__submission" class="response__submission">
<ol class="list list--fields response__submission__content">
<li class="field field--textarea submission__answer" id="submission__answer">
<label class="sr" for="submission__answer__value">Provide your response to the question.</label>
<textarea id="submission__answer__value" placeholder=""></textarea>
<span class="tip">You may continue to work on your response until you submit it.</span>
</li>
</ol>
<div class="response__submission__actions">
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">We could not save your progress</h3>
</div>
<ul class="list list--actions">
<li class="list--actions__item">
<button type="submit" id="submission__save" class="action action--save submission__save is--disabled">Save Your Progress</button>
<div id="response__save_status" class="response__submission__status">
<h3 class="response__submission__status__title">
<span class="sr">Your Working Submission Status:</span>
Unsaved draft
</h3>
</div>
</li>
</ul>
</div>
</form>
</div>
<div class="step__actions">
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">We could not submit your response</h3>
</div>
<ul class="list list--actions">
<li class="list--actions__item">
<a aria-role="button" href="#" id="step--response__submit" class="action action--submit step--response__submit is--disabled">
<span class="copy">Submit your response and move to the next step</span>
<i class="ico icon-caret-right"></i>
</a>
</li>
</ul>
</div>
</div>
</div>
</li>
</ol>
</div>
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.BaseUI=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server};OpenAssessment.BaseUI.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps"),800,{offset:-50})}},setUpCollapseExpand:function(parentSel,onExpand){parentSel.find(".ui-toggle-visibility__control").click(function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");if(sel.hasClass("is--collapsed")&&onExpand!==undefined){onExpand()}sel.toggleClass("is--collapsed")})},load:function(){this.renderSubmissionStep();this.renderPeerAssessmentStep();this.renderSelfAssessmentStep();this.renderGradeStep()},renderSubmissionStep:function(){var ui=this;this.server.render("submission").done(function(html){$("#openassessment__response",ui.element).replaceWith(html);var sel=$("#openassessment__response",ui.element);ui.setUpCollapseExpand(sel);ui.responseChanged();sel.find("#submission__answer__value").keyup(function(eventData){ui.responseChanged()});sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();ui.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();ui.save()})}).fail(function(errMsg){ui.showLoadError("response")})},responseChanged:function(){var blankSubmission=$("#submission__answer__value",this.element).val()==="";$("#step--response__submit",this.element).toggleClass("is--disabled",blankSubmission);$("#submission__save",this.element).toggleClass("is--disabled",blankSubmission)},renderPeerAssessmentStep:function(){var ui=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",ui.element).replaceWith(html);var sel=$("#openassessment__peer-assessment",ui.element);ui.setUpCollapseExpand(sel,$.proxy(ui.renderContinuedPeerAssessmentStep,ui));sel.find("#peer-assessment--001__assessment").change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#peer-assessment--001__assessment__submit",ui.element).toggleClass("is--disabled",numChecked!=numAvailable)});sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();ui.peerAssess()})}).fail(function(errMsg){ui.showLoadError("peer-assessment")})},renderContinuedPeerAssessmentStep:function(){var ui=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",ui.element).replaceWith(html);var sel=$("#openassessment__peer-assessment",ui.element);ui.setUpCollapseExpand(sel);sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();ui.continuedPeerAssess()});sel.find("#peer-assessment--001__assessment").change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#peer-assessment--001__assessment__submit",ui.element).toggleClass("is--disabled",numChecked!=numAvailable)})}).fail(function(errMsg){ui.showLoadError("peer-assessment")})},renderSelfAssessmentStep:function(){var ui=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",ui.element).replaceWith(html);var sel=$("#openassessment__self-assessment",ui.element);ui.setUpCollapseExpand(sel);$("#self-assessment--001__assessment",ui.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#self-assessment--001__assessment__submit",ui.element).toggleClass("is--disabled",numChecked!=numAvailable)});sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();ui.selfAssess()})}).fail(function(errMsg){ui.showLoadError("self-assessment")})},renderGradeStep:function(){var ui=this;this.server.render("grade").done(function(html){$("#openassessment__grade",ui.element).replaceWith(html);var sel=$("#openassessment__grade",ui.element);ui.setUpCollapseExpand(sel);sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();ui.submitFeedbackOnAssessment()})}).fail(function(errMsg){ui.showLoadError("grade",errMsg)})},save:function(){var ui=this;var submission=$("#submission__answer__value",ui.element).val();ui.setSaveStatus("Saving...");ui.toggleActionError("save",null);ui.server.save(submission).done(function(){ui.setSaveStatus("Saved but not submitted")}).fail(function(errMsg){ui.setSaveStatus("Error");ui.toggleActionError("save",errMsg)})},setSaveStatus:function(msg){$("#response__save_status h3",this.element).html(msg)},submit:function(){var ui=this;var submission=$("#submission__answer__value",ui.element).val();ui.toggleActionError("response",null);ui.server.submit(submission).done(function(studentId,attemptNum){ui.renderSubmissionStep();ui.renderPeerAssessmentStep()}).fail(function(errCode,errMsg){ui.toggleActionError("submit",errMsg)})},submitFeedbackOnAssessment:function(){var ui=this;var text=$("#feedback__remarks__value",ui.element).val();var options=$.map($(".feedback__overall__value:checked",ui.element),function(element,index){return $(element).val()});ui.server.submitFeedbackOnAssessment(text,options).done(function(){}).fail(function(errMsg){ui.toggleActionError("feedback_assess",errMsg)})},peerAssess:function(){var ui=this;ui.peerAssessRequest(function(){ui.renderPeerAssessmentStep();ui.renderSelfAssessmentStep();ui.renderGradeStep();ui.scrollToTop()})},continuedPeerAssess:function(){var ui=this;ui.peerAssessRequest(function(){ui.renderContinuedPeerAssessmentStep();ui.renderGradeStep()})},peerAssessRequest:function(successFunction){var submissionId=$("span#peer_submission_uuid",this.element)[0].innerHTML.trim();var optionsSelected={};$("#peer-assessment--001__assessment input[type=radio]:checked",this.element).each(function(index,sel){optionsSelected[sel.name]=sel.value});var feedback=$("#assessment__rubric__question--feedback__value",this.element).val();var ui=this;this.toggleActionError("peer",null);this.server.peerAssess(submissionId,optionsSelected,feedback).done(successFunction).fail(function(errMsg){ui.toggleActionError("peer",errMsg)})},selfAssess:function(){var submissionId=$("span#self_submission_uuid",this.element)[0].innerHTML.trim();var optionsSelected={};$("#self-assessment--001__assessment input[type=radio]:checked",this.element).each(function(index,sel){optionsSelected[sel.name]=sel.value});var ui=this;this.toggleActionError("self",null);this.server.selfAssess(submissionId,optionsSelected).done(function(){ui.renderPeerAssessmentStep();ui.renderSelfAssessmentStep();ui.renderGradeStep();ui.scrollToTop()}).fail(function(errMsg){ui.toggleActionError("self",errMsg)})},toggleActionError:function(type,msg){var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"){container=".step__actions"}else if(type=="peer"){container=".step__actions"}else if(type=="self"){container=".self-assessment__actions"}else if(type=="feedback_assess"){container=".submission__feedback__actions"}if(container===null){if(msg!==null){console.log(msg)}}else{var msgHtml=msg===null?"":msg;$(container+" .message__content").html("<p>"+msgHtml+"</p>");$(container).toggleClass("has--error",msg!==null)}},showLoadError:function(step){var container="#openassessment__"+step;$(container).toggleClass("has--error",true);$(container+" .step__status__value i").removeClass().addClass("ico icon-warning-sign");$(container+" .step__status__value .copy").html("Unable to Load")}};function OpenAssessmentBlock(runtime,element){$(function($){var server=new OpenAssessment.Server(runtime,element);var ui=new OpenAssessment.BaseUI(runtime,element,server);ui.load()})}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.StudioUI=function(runtime,element,server){this.runtime=runtime;this.server=server;this.codeBox=CodeMirror.fromTextArea($(element).find(".openassessment-editor").first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});var ui=this;$(element).find(".openassessment-save-button").click(function(eventData){ui.save()});$(element).find(".openassessment-cancel-button").click(function(eventData){ui.cancel()})};OpenAssessment.StudioUI.prototype={load:function(){var ui=this;this.server.loadXml().done(function(xml){ui.codeBox.setValue(xml)}).fail(function(msg){ui.showError(msg)})},save:function(){var ui=this;this.server.checkReleased().done(function(isReleased){if(isReleased){ui.confirmPostReleaseUpdate($.proxy(ui.updateXml,ui))}else{ui.updateXml()}}).fail(function(errMsg){ui.showError(msg)})},confirmPostReleaseUpdate:function(onConfirm){var msg="This problem has already been released. Any changes will apply only to future assessments.";if(confirm(msg)){onConfirm()}},updateXml:function(){this.runtime.notify("save",{state:"start"});var xml=this.codeBox.getValue();var ui=this;this.server.updateXml(xml).done(function(){ui.runtime.notify("save",{state:"end"});ui.load()}).fail(function(msg){ui.showError(msg)})},cancel:function(){this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})}};function OpenAssessmentEditor(runtime,element){$(function($){var server=new OpenAssessment.Server(runtime,element);var ui=new OpenAssessment.StudioUI(runtime,element,server);ui.load()})}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},maxInputSize:1024*64,render:function(component){var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},renderContinuedPeer:function(){var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},submit:function(submission){var url=this.url("submit");if(submission.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["submit","Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(data){defer.rejectWith(this,["AJAX","Could not contact server."])})}).promise()},save:function(submission){var url=this.url("save_submission");if(submission.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");if(text.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},peerAssess:function(submissionId,optionsSelected,feedback){var url=this.url("peer_assess");if(feedback.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}var payload=JSON.stringify({submission_uuid:submissionId,options_selected:optionsSelected,feedback:feedback});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},selfAssess:function(submissionId,optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({submission_uuid:submissionId,options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})})},loadXml:function(){var url=this.url("xml");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.xml])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},updateXml:function(xml){var url=this.url("update_xml");var payload=JSON.stringify({xml:xml});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()}}; if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.BaseView=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server};OpenAssessment.BaseView.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps"),800,{offset:-50})}},setUpCollapseExpand:function(parentSel,onExpand){parentSel.find(".ui-toggle-visibility__control").click(function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");if(sel.hasClass("is--collapsed")&&onExpand!==undefined){onExpand()}sel.toggleClass("is--collapsed")})},load:function(){this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this);this.responseView.load();this.renderPeerAssessmentStep();this.renderSelfAssessmentStep();this.renderGradeStep()},renderPeerAssessmentStep:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);var sel=$("#openassessment__peer-assessment",view.element);view.setUpCollapseExpand(sel,$.proxy(view.renderContinuedPeerAssessmentStep,view));sel.find("#peer-assessment--001__assessment").change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#peer-assessment--001__assessment__submit",view.element).toggleClass("is--disabled",numChecked!=numAvailable)});sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.peerAssess()})}).fail(function(errMsg){view.showLoadError("peer-assessment")})},renderContinuedPeerAssessmentStep:function(){var view=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);var sel=$("#openassessment__peer-assessment",view.element);view.setUpCollapseExpand(sel);sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.continuedPeerAssess()});sel.find("#peer-assessment--001__assessment").change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#peer-assessment--001__assessment__submit",view.element).toggleClass("is--disabled",numChecked!=numAvailable)})}).fail(function(errMsg){view.showLoadError("peer-assessment")})},renderSelfAssessmentStep:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);var sel=$("#openassessment__self-assessment",view.element);view.setUpCollapseExpand(sel);$("#self-assessment--001__assessment",view.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;$("#self-assessment--001__assessment__submit",view.element).toggleClass("is--disabled",numChecked!=numAvailable)});sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})}).fail(function(errMsg){view.showLoadError("self-assessment")})},renderGradeStep:function(){var view=this;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);var sel=$("#openassessment__grade",view.element);view.setUpCollapseExpand(sel);sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})}).fail(function(errMsg){view.showLoadError("grade",errMsg)})},submitFeedbackOnAssessment:function(){var view=this;var text=$("#feedback__remarks__value",view.element).val();var options=$.map($(".feedback__overall__value:checked",view.element),function(element,index){return $(element).val()});view.server.submitFeedbackOnAssessment(text,options).done(function(){}).fail(function(errMsg){view.toggleActionError("feedback_assess",errMsg)})},peerAssess:function(){var view=this;this.peerAssessRequest(function(){view.renderPeerAssessmentStep();view.renderSelfAssessmentStep();view.renderGradeStep();view.scrollToTop()})},continuedPeerAssess:function(){var view=this;view.peerAssessRequest(function(){view.renderContinuedPeerAssessmentStep();view.renderGradeStep()})},peerAssessRequest:function(successFunction){var submissionId=$("span#peer_submission_uuid",this.element)[0].innerHTML.trim();var optionsSelected={};$("#peer-assessment--001__assessment input[type=radio]:checked",this.element).each(function(index,sel){optionsSelected[sel.name]=sel.value});var feedback=$("#assessment__rubric__question--feedback__value",this.element).val();var view=this;this.toggleActionError("peer",null);this.server.peerAssess(submissionId,optionsSelected,feedback).done(successFunction).fail(function(errMsg){view.toggleActionError("peer",errMsg)})},selfAssess:function(){var submissionId=$("span#self_submission_uuid",this.element)[0].innerHTML.trim();var optionsSelected={};$("#self-assessment--001__assessment input[type=radio]:checked",this.element).each(function(index,sel){optionsSelected[sel.name]=sel.value});var view=this;this.toggleActionError("self",null);this.server.selfAssess(submissionId,optionsSelected).done(function(){view.renderPeerAssessmentStep();view.renderSelfAssessmentStep();view.renderGradeStep();view.scrollToTop()}).fail(function(errMsg){view.toggleActionError("self",errMsg)})},toggleActionError:function(type,msg){var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"){container=".step__actions"}else if(type=="peer"){container=".step__actions"}else if(type=="self"){container=".self-assessment__actions"}else if(type=="feedback_assess"){container=".submission__feedback__actions"}if(container===null){if(msg!==null){console.log(msg)}}else{var msgHtml=msg===null?"":msg;$(container+" .message__content").html("<p>"+msgHtml+"</p>");$(container).toggleClass("has--error",msg!==null)}},showLoadError:function(step){var container="#openassessment__"+step;$(container).toggleClass("has--error",true);$(container+" .step__status__value i").removeClass().addClass("ico icon-warning-sign");$(container+" .step__status__value .copy").html("Unable to Load")}};function OpenAssessmentBlock(runtime,element){$(function($){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server);view.load()})}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.StudioView=function(runtime,element,server){this.runtime=runtime;this.server=server;this.codeBox=CodeMirror.fromTextArea($(element).find(".openassessment-editor").first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});var view=this;$(element).find(".openassessment-save-button").click(function(eventData){view.save()});$(element).find(".openassessment-cancel-button").click(function(eventData){view.cancel()})};OpenAssessment.StudioView.prototype={load:function(){var view=this;this.server.loadXml().done(function(xml){view.codeBox.setValue(xml)}).fail(function(msg){view.showError(msg)})},save:function(){var view=this;this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateXml,view))}else{view.updateXml()}}).fail(function(errMsg){view.showError(msg)})},confirmPostReleaseUpdate:function(onConfirm){var msg="This problem has already been released. Any changes will apply only to future assessments.";if(confirm(msg)){onConfirm()}},updateXml:function(){this.runtime.notify("save",{state:"start"});var xml=this.codeBox.getValue();var view=this;this.server.updateXml(xml).done(function(){view.runtime.notify("save",{state:"end"});view.load()}).fail(function(msg){view.showError(msg)})},cancel:function(){this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})}};function OpenAssessmentEditor(runtime,element){$(function($){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server);view.load()})}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.ResponseView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.savedResponse=""};OpenAssessment.ResponseView.prototype={load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("response")})},installHandlers:function(){var sel=$("#openassessment__response",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);this.savedResponse=this.response();var handleChange=function(eventData){view.responseChanged()};sel.find("#submission__answer__value").on("change keyup drop paste",handleChange);sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();view.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();view.save()})},submitEnabled:function(enabled){var sel=$("#step--response__submit",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveEnabled:function(enabled){var sel=$("#submission__save",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveStatus:function(msg){var sel=$("#response__save_status h3",this.element);if(typeof msg==="undefined"){return sel.text()}else{sel.html('<span class="sr">Your Working Submission Status:</span>\n'+msg)}},response:function(text){var sel=$("#submission__answer__value",this.element);if(typeof text==="undefined"){return sel.val()}else{sel.val(text)}},responseChanged:function(){var currentResponse=this.response();var isBlank=currentResponse!=="";this.submitEnabled(isBlank);if(this.savedResponse!==currentResponse){this.saveEnabled(isBlank);this.saveStatus("Unsaved draft")}},save:function(){this.saveStatus("Saving...");this.baseView.toggleActionError("save",null);var view=this;var savedResponse=this.response();this.server.save(savedResponse).done(function(){view.savedResponse=savedResponse;var currentResponse=view.response();view.submitEnabled(currentResponse!=="");if(currentResponse==savedResponse){view.saveEnabled(false);view.saveStatus("Saved but not submitted")}}).fail(function(errMsg){view.saveStatus("Error");view.baseView.toggleActionError("save",errMsg)})},submit:function(){var submission=$("#submission__answer__value",this.element).val();this.baseView.toggleActionError("response",null);var view=this;var baseView=this.baseView;this.server.submit(submission).done(function(studentId,attemptNum){view.load();baseView.renderPeerAssessmentStep()}).fail(function(errCode,errMsg){baseView.toggleActionError("submit",errMsg)})}};if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},maxInputSize:1024*64,render:function(component){var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},renderContinuedPeer:function(){var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},submit:function(submission){var url=this.url("submit");if(submission.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["submit","Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(data){defer.rejectWith(this,["AJAX","Could not contact server."])})}).promise()},save:function(submission){var url=this.url("save_submission");if(submission.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");if(text.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},peerAssess:function(submissionId,optionsSelected,feedback){var url=this.url("peer_assess");if(feedback.length>this.maxInputSize){return $.Deferred(function(defer){defer.rejectWith(this,["Response text is too large. Please reduce the size of your response and try to submit again."])}).promise()}var payload=JSON.stringify({submission_uuid:submissionId,options_selected:optionsSelected,feedback:feedback});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},selfAssess:function(submissionId,optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({submission_uuid:submissionId,options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})})},loadXml:function(){var url=this.url("xml");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.xml])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},updateXml:function(xml){var url=this.url("update_xml");var payload=JSON.stringify({xml:xml});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,["Could not contact server."])})}).promise()}};
\ No newline at end of file \ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Tests for OA student-facing views. Tests for OA student-facing views.
**/ **/
describe("OpenAssessment.BaseUI", function() { describe("OpenAssessment.BaseView", function() {
// Stub server that returns dummy data // Stub server that returns dummy data
var StubServer = function() { var StubServer = function() {
...@@ -15,12 +15,6 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -15,12 +15,6 @@ describe("OpenAssessment.BaseUI", function() {
grade: "Test fragment" grade: "Test fragment"
}; };
this.submit = function(submission) {
return $.Deferred(function(defer) {
defer.resolveWith(this, ['student', 0]);
}).promise();
};
this.peerAssess = function(submissionId, optionsSelected, feedback) { this.peerAssess = function(submissionId, optionsSelected, feedback) {
return $.Deferred(function(defer) { defer.resolve(); }).promise(); return $.Deferred(function(defer) { defer.resolve(); }).promise();
}; };
...@@ -42,7 +36,7 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -42,7 +36,7 @@ describe("OpenAssessment.BaseUI", function() {
this.feedbackOptions = options; this.feedbackOptions = options;
// Return a promise that always resolves successfully // Return a promise that always resolves successfully
return $.Deferred(function(defer) { defer.resolve() }).promise(); return $.Deferred(function(defer) { defer.resolve(); }).promise();
}; };
}; };
...@@ -50,7 +44,7 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -50,7 +44,7 @@ describe("OpenAssessment.BaseUI", function() {
var runtime = {}; var runtime = {};
var server = null; var server = null;
var ui = null; var view = null;
/** /**
Wait for subviews to load before executing callback. Wait for subviews to load before executing callback.
...@@ -60,7 +54,7 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -60,7 +54,7 @@ describe("OpenAssessment.BaseUI", function() {
**/ **/
var loadSubviews = function(callback) { var loadSubviews = function(callback) {
runs(function() { runs(function() {
ui.load(); view.load();
}); });
waitsFor(function() { waitsFor(function() {
...@@ -85,21 +79,13 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -85,21 +79,13 @@ describe("OpenAssessment.BaseUI", function() {
// Create the object under test // Create the object under test
var el = $("#openassessment-base").get(0); var el = $("#openassessment-base").get(0);
ui = new OpenAssessment.BaseUI(runtime, el, server); view = new OpenAssessment.BaseView(runtime, el, server);
});
it("Sends a submission to the server", function() {
loadSubviews(function() {
spyOn(server, 'submit').andCallThrough();
ui.submit();
expect(server.submit).toHaveBeenCalled();
});
}); });
it("Sends a peer assessment to the server", function() { it("Sends a peer assessment to the server", function() {
loadSubviews(function() { loadSubviews(function() {
spyOn(server, 'peerAssess').andCallThrough(); spyOn(server, 'peerAssess').andCallThrough();
ui.peerAssess(); view.peerAssess();
expect(server.peerAssess).toHaveBeenCalled(); expect(server.peerAssess).toHaveBeenCalled();
}); });
}); });
...@@ -107,7 +93,7 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -107,7 +93,7 @@ describe("OpenAssessment.BaseUI", function() {
it("Sends a self assessment to the server", function() { it("Sends a self assessment to the server", function() {
loadSubviews(function() { loadSubviews(function() {
spyOn(server, 'selfAssess').andCallThrough(); spyOn(server, 'selfAssess').andCallThrough();
ui.selfAssess(); view.selfAssess();
expect(server.selfAssess).toHaveBeenCalled(); expect(server.selfAssess).toHaveBeenCalled();
}); });
}); });
...@@ -126,10 +112,10 @@ describe("OpenAssessment.BaseUI", function() { ...@@ -126,10 +112,10 @@ describe("OpenAssessment.BaseUI", function() {
// Create the object under test // Create the object under test
var el = $("#openassessment-base").get(0); var el = $("#openassessment-base").get(0);
ui = new OpenAssessment.BaseUI(runtime, el, server); view = new OpenAssessment.BaseView(runtime, el, server);
// Submit feedback on an assessment // Submit feedback on an assessment
ui.submitFeedbackOnAssessment(); view.submitFeedbackOnAssessment();
// Expect that the feedback was retrieved from the DOM and sent to the server // Expect that the feedback was retrieved from the DOM and sent to the server
expect(server.feedbackText).toEqual('I disliked the feedback I received.'); expect(server.feedbackText).toEqual('I disliked the feedback I received.');
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Tests for OA XBlock editing. Tests for OA XBlock editing.
**/ **/
describe("OpenAssessment.StudioUI", function() { describe("OpenAssessment.StudioView", function() {
var runtime = { var runtime = {
notify: function(type, data) {} notify: function(type, data) {}
...@@ -52,7 +52,7 @@ describe("OpenAssessment.StudioUI", function() { ...@@ -52,7 +52,7 @@ describe("OpenAssessment.StudioUI", function() {
}; };
var server = null; var server = null;
var ui = null; var view = null;
beforeEach(function() { beforeEach(function() {
...@@ -68,24 +68,24 @@ describe("OpenAssessment.StudioUI", function() { ...@@ -68,24 +68,24 @@ describe("OpenAssessment.StudioUI", function() {
// Create the object under test // Create the object under test
var el = $('#openassessment-edit').get(0); var el = $('#openassessment-edit').get(0);
ui = new OpenAssessment.StudioUI(runtime, el, server); view = new OpenAssessment.StudioView(runtime, el, server);
}); });
it("loads the XML definition", function() { it("loads the XML definition", function() {
// Initialize the UI // Initialize the view
ui.load(); view.load();
// Expect that the XML definition was loaded // Expect that the XML definition was loaded
var contents = ui.codeBox.getValue(); var contents = view.codeBox.getValue();
expect(contents).toEqual('<openassessment></openassessment>'); expect(contents).toEqual('<openassessment></openassessment>');
}); });
it("saves the XML definition", function() { it("saves the XML definition", function() {
// Update the XML // Update the XML
ui.codeBox.setValue('<openassessment>test!</openassessment>'); view.codeBox.setValue('<openassessment>test!</openassessment>');
// Save the updated XML // Save the updated XML
ui.save(); view.save();
// Expect the saving notification to start/end // Expect the saving notification to start/end
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'start'}); expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'start'});
...@@ -100,31 +100,31 @@ describe("OpenAssessment.StudioUI", function() { ...@@ -100,31 +100,31 @@ describe("OpenAssessment.StudioUI", function() {
server.isReleased = true; server.isReleased = true;
// Stub the confirmation step (avoid showing the dialog) // Stub the confirmation step (avoid showing the dialog)
spyOn(ui, 'confirmPostReleaseUpdate').andCallFake( spyOn(view, 'confirmPostReleaseUpdate').andCallFake(
function(onConfirm) { onConfirm(); } function(onConfirm) { onConfirm(); }
); );
// Save the updated XML // Save the updated XML
ui.save(); view.save();
// Verify that the user was asked to confirm the changes // Verify that the user was asked to confirm the changes
expect(ui.confirmPostReleaseUpdate).toHaveBeenCalled(); expect(view.confirmPostReleaseUpdate).toHaveBeenCalled();
}); });
it("cancels editing", function() { it("cancels editing", function() {
ui.cancel(); view.cancel();
expect(runtime.notify).toHaveBeenCalledWith('cancel', {}); expect(runtime.notify).toHaveBeenCalledWith('cancel', {});
}); });
it("displays an error when server reports a load XML error", function() { it("displays an error when server reports a load XML error", function() {
server.loadError = true; server.loadError = true;
ui.load(); view.load();
expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'}); expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'});
}); });
it("displays an error when server reports an update XML error", function() { it("displays an error when server reports an update XML error", function() {
server.updateError = true; server.updateError = true;
ui.save('<openassessment>test!</openassessment>'); view.save('<openassessment>test!</openassessment>');
expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'}); expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'});
}); });
......
/**
Tests for OpenAssessment response (submission) step.
**/
describe("OpenAssessment.ResponseView", function() {
// Stub server
var StubServer = function() {
var successPromise = $.Deferred(
function(defer) {
defer.resolve();
}
).promise();
this.save = function(submission) {
return successPromise;
};
this.submit = function(submission) {
return successPromise;
};
this.render = function(step) {
return successPromise;
};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.renderPeerAssessmentStep = function() {};
};
// Stubs
var baseView = null;
var server = null;
// View under test
var view = null;
beforeEach(function() {
// Load the DOM fixture
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_response.html');
// Create the stub server
server = new StubServer();
// Create the stub base view
baseView = new StubBaseView();
// Create and install the view
var el = $('#openassessment-base').get(0);
view = new OpenAssessment.ResponseView(el, server, baseView);
view.installHandlers();
});
it("updates submit/save buttons and save status when response text changes", function() {
// Response is blank --> save/submit buttons disabled
view.response('');
view.responseChanged();
expect(view.submitEnabled()).toBe(false);
expect(view.saveEnabled()).toBe(false);
expect(view.saveStatus()).toContain('Unsaved draft');
// Response is not blank --> submit button enabled
view.response('Test response');
view.responseChanged();
expect(view.submitEnabled()).toBe(true);
expect(view.saveEnabled()).toBe(true);
expect(view.saveStatus()).toContain('Unsaved draft');
});
it("updates submit/save buttons and save status when the user saves a response", function() {
// Response is blank --> save/submit button is disabled
view.response('');
view.save();
expect(view.submitEnabled()).toBe(false);
expect(view.saveEnabled()).toBe(false);
expect(view.saveStatus()).toContain('Saved but not submitted');
// Response is not blank --> submit button enabled
view.response('Test response');
view.save();
expect(view.submitEnabled()).toBe(true);
expect(view.saveEnabled()).toBe(false);
expect(view.saveStatus()).toContain('Saved but not submitted');
});
it("shows unsaved draft only when response text has changed", function() {
// Save the initial response
view.response('Lorem ipsum');
view.save();
expect(view.saveEnabled()).toBe(false);
expect(view.saveStatus()).toContain('Saved but not submitted');
// Keep the text the same, but trigger an update
// Should still be saved
view.response('Lorem ipsum');
view.responseChanged();
expect(view.saveEnabled()).toBe(false);
expect(view.saveStatus()).toContain('Saved but not submitted');
// Change the text
// This should cause it to change to unsaved draft
view.response('changed ');
view.responseChanged();
expect(view.saveEnabled()).toBe(true);
expect(view.saveStatus()).toContain('Unsaved draft');
});
it("sends the saved submission to the server", function() {
spyOn(server, 'save').andCallThrough();
view.response('Test response');
view.save();
expect(server.save).toHaveBeenCalledWith('Test response');
});
it("submits a response to the server", function() {
spyOn(server, 'submit').andCallThrough();
view.response('Test response');
view.submit();
expect(server.submit).toHaveBeenCalledWith('Test response');
});
});
...@@ -15,16 +15,16 @@ Args: ...@@ -15,16 +15,16 @@ Args:
server (OpenAssessment.Server): The interface to the XBlock server. server (OpenAssessment.Server): The interface to the XBlock server.
Returns: Returns:
OpenAssessment.BaseUI OpenAssessment.BaseView
**/ **/
OpenAssessment.BaseUI = function(runtime, element, server) { OpenAssessment.BaseView = function(runtime, element, server) {
this.runtime = runtime; this.runtime = runtime;
this.element = element; this.element = element;
this.server = server; this.server = server;
}; };
OpenAssessment.BaseUI.prototype = { OpenAssessment.BaseView.prototype = {
/** /**
* Checks to see if the scrollTo function is available, then scrolls to the * Checks to see if the scrollTo function is available, then scrolls to the
...@@ -65,91 +65,35 @@ OpenAssessment.BaseUI.prototype = { ...@@ -65,91 +65,35 @@ OpenAssessment.BaseUI.prototype = {
* Asynchronously load each sub-view into the DOM. * Asynchronously load each sub-view into the DOM.
*/ */
load: function() { load: function() {
this.renderSubmissionStep(); this.responseView = new OpenAssessment.ResponseView(this.element, this.server, this);
this.responseView.load();
this.renderPeerAssessmentStep(); this.renderPeerAssessmentStep();
this.renderSelfAssessmentStep(); this.renderSelfAssessmentStep();
this.renderGradeStep(); this.renderGradeStep();
}, },
/** /**
Render the submission step.
**/
renderSubmissionStep: function() {
var ui = this;
this.server.render('submission').done(
function(html) {
// Load the HTML
$('#openassessment__response', ui.element).replaceWith(html);
var sel = $('#openassessment__response', ui.element);
// Install a click handler for collapse/expand
ui.setUpCollapseExpand(sel);
// If we have a saved submission, enable the submit button
ui.responseChanged();
// Install change handler for textarea (to enable submission button)
sel.find('#submission__answer__value').keyup(
function(eventData) { ui.responseChanged(); }
);
// Install a click handler for submission
sel.find('#step--response__submit').click(
function(eventObject) {
// Override default form submission
eventObject.preventDefault();
ui.submit();
}
);
// Install a click handler for the save button
sel.find('#submission__save').click(
function(eventObject) {
// Override default form submission
eventObject.preventDefault();
ui.save();
}
);
}
).fail(function(errMsg) {
ui.showLoadError('response');
});
},
/**
Enable/disable the submission and save buttons based on whether
the user has entered a response.
**/
responseChanged: function() {
var blankSubmission = ($('#submission__answer__value', this.element).val() === '');
$('#step--response__submit', this.element).toggleClass('is--disabled', blankSubmission);
$('#submission__save', this.element).toggleClass('is--disabled', blankSubmission);
},
/**
Render the peer-assessment step. Render the peer-assessment step.
**/ **/
renderPeerAssessmentStep: function() { renderPeerAssessmentStep: function() {
var ui = this; var view = this;
this.server.render('peer_assessment').done( this.server.render('peer_assessment').done(
function(html) { function(html) {
// Load the HTML // Load the HTML
$('#openassessment__peer-assessment', ui.element).replaceWith(html); $('#openassessment__peer-assessment', view.element).replaceWith(html);
var sel = $('#openassessment__peer-assessment', ui.element); var sel = $('#openassessment__peer-assessment', view.element);
// Install a click handler for collapse/expand // Install a click handler for collapse/expand
ui.setUpCollapseExpand(sel, $.proxy(ui.renderContinuedPeerAssessmentStep, ui)); view.setUpCollapseExpand(sel, $.proxy(view.renderContinuedPeerAssessmentStep, view));
// Install a change handler for rubric options to enable/disable the submit button // Install a change handler for rubric options to enable/disable the submit button
sel.find("#peer-assessment--001__assessment").change( sel.find("#peer-assessment--001__assessment").change(
function() { function() {
var numChecked = $('input[type=radio]:checked', this).length; var numChecked = $('input[type=radio]:checked', this).length;
var numAvailable = $('.field--radio.assessment__rubric__question', this).length; var numAvailable = $('.field--radio.assessment__rubric__question', this).length;
$("#peer-assessment--001__assessment__submit", ui.element).toggleClass( $("#peer-assessment--001__assessment__submit", view.element).toggleClass(
'is--disabled', numChecked != numAvailable 'is--disabled', numChecked != numAvailable
); );
} }
...@@ -162,13 +106,13 @@ OpenAssessment.BaseUI.prototype = { ...@@ -162,13 +106,13 @@ OpenAssessment.BaseUI.prototype = {
eventObject.preventDefault(); eventObject.preventDefault();
// Handle the click // Handle the click
ui.peerAssess(); view.peerAssess();
} }
); );
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.showLoadError('peer-assessment'); view.showLoadError('peer-assessment');
}); });
}, },
...@@ -178,16 +122,16 @@ OpenAssessment.BaseUI.prototype = { ...@@ -178,16 +122,16 @@ OpenAssessment.BaseUI.prototype = {
* peer grading process. * peer grading process.
*/ */
renderContinuedPeerAssessmentStep: function() { renderContinuedPeerAssessmentStep: function() {
var ui = this; var view = this;
this.server.renderContinuedPeer().done( this.server.renderContinuedPeer().done(
function(html) { function(html) {
// Load the HTML // Load the HTML
$('#openassessment__peer-assessment', ui.element).replaceWith(html); $('#openassessment__peer-assessment', view.element).replaceWith(html);
var sel = $('#openassessment__peer-assessment', ui.element); var sel = $('#openassessment__peer-assessment', view.element);
// Install a click handler for collapse/expand // Install a click handler for collapse/expand
ui.setUpCollapseExpand(sel); view.setUpCollapseExpand(sel);
// Install a click handler for assessment // Install a click handler for assessment
sel.find('#peer-assessment--001__assessment__submit').click( sel.find('#peer-assessment--001__assessment__submit').click(
...@@ -196,7 +140,7 @@ OpenAssessment.BaseUI.prototype = { ...@@ -196,7 +140,7 @@ OpenAssessment.BaseUI.prototype = {
eventObject.preventDefault(); eventObject.preventDefault();
// Handle the click // Handle the click
ui.continuedPeerAssess(); view.continuedPeerAssess();
} }
); );
...@@ -205,14 +149,14 @@ OpenAssessment.BaseUI.prototype = { ...@@ -205,14 +149,14 @@ OpenAssessment.BaseUI.prototype = {
function() { function() {
var numChecked = $('input[type=radio]:checked', this).length; var numChecked = $('input[type=radio]:checked', this).length;
var numAvailable = $('.field--radio.assessment__rubric__question', this).length; var numAvailable = $('.field--radio.assessment__rubric__question', this).length;
$("#peer-assessment--001__assessment__submit", ui.element).toggleClass( $("#peer-assessment--001__assessment__submit", view.element).toggleClass(
'is--disabled', numChecked != numAvailable 'is--disabled', numChecked != numAvailable
); );
} }
); );
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.showLoadError('peer-assessment'); view.showLoadError('peer-assessment');
}); });
}, },
...@@ -220,23 +164,23 @@ OpenAssessment.BaseUI.prototype = { ...@@ -220,23 +164,23 @@ OpenAssessment.BaseUI.prototype = {
Render the self-assessment step. Render the self-assessment step.
**/ **/
renderSelfAssessmentStep: function() { renderSelfAssessmentStep: function() {
var ui = this; var view = this;
this.server.render('self_assessment').done( this.server.render('self_assessment').done(
function(html) { function(html) {
// Load the HTML // Load the HTML
$('#openassessment__self-assessment', ui.element).replaceWith(html); $('#openassessment__self-assessment', view.element).replaceWith(html);
var sel = $('#openassessment__self-assessment', ui.element); var sel = $('#openassessment__self-assessment', view.element);
// Install a click handler for collapse/expand // Install a click handler for collapse/expand
ui.setUpCollapseExpand(sel); view.setUpCollapseExpand(sel);
// Install a change handler for rubric options to enable/disable the submit button // Install a change handler for rubric options to enable/disable the submit button
$("#self-assessment--001__assessment", ui.element).change( $("#self-assessment--001__assessment", view.element).change(
function() { function() {
var numChecked = $('input[type=radio]:checked', this).length; var numChecked = $('input[type=radio]:checked', this).length;
var numAvailable = $('.field--radio.assessment__rubric__question', this).length; var numAvailable = $('.field--radio.assessment__rubric__question', this).length;
$("#self-assessment--001__assessment__submit", ui.element).toggleClass( $("#self-assessment--001__assessment__submit", view.element).toggleClass(
'is--disabled', numChecked != numAvailable 'is--disabled', numChecked != numAvailable
); );
} }
...@@ -249,12 +193,12 @@ OpenAssessment.BaseUI.prototype = { ...@@ -249,12 +193,12 @@ OpenAssessment.BaseUI.prototype = {
eventObject.preventDefault(); eventObject.preventDefault();
// Handle the click // Handle the click
ui.selfAssess(); view.selfAssess();
} }
); );
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.showLoadError('self-assessment'); view.showLoadError('self-assessment');
}); });
}, },
...@@ -262,116 +206,71 @@ OpenAssessment.BaseUI.prototype = { ...@@ -262,116 +206,71 @@ OpenAssessment.BaseUI.prototype = {
Render the grade step. Render the grade step.
**/ **/
renderGradeStep: function() { renderGradeStep: function() {
var ui = this; var view = this;
this.server.render('grade').done( this.server.render('grade').done(
function(html) { function(html) {
// Load the HTML // Load the HTML
$('#openassessment__grade', ui.element).replaceWith(html); $('#openassessment__grade', view.element).replaceWith(html);
// Install a click handler for collapse/expand // Install a click handler for collapse/expand
var sel = $('#openassessment__grade', ui.element); var sel = $('#openassessment__grade', view.element);
ui.setUpCollapseExpand(sel); view.setUpCollapseExpand(sel);
// Install a click handler for assessment feedback // Install a click handler for assessment feedback
sel.find('#feedback__submit').click(function(eventObject) { sel.find('#feedback__submit').click(function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
ui.submitFeedbackOnAssessment(); view.submitFeedbackOnAssessment();
}); });
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.showLoadError('grade', errMsg); view.showLoadError('grade', errMsg);
}); });
}, },
/** /**
Save a response without submitting it. Send assessment feedback to the server and update the view.
**/
save: function() {
// Retrieve the student's response from the DOM
var ui = this;
var submission = $('#submission__answer__value', ui.element).val();
ui.setSaveStatus('Saving...');
ui.toggleActionError('save', null);
ui.server.save(submission).done(function() {
ui.setSaveStatus("Saved but not submitted");
}).fail(function(errMsg) {
ui.setSaveStatus('Error');
ui.toggleActionError('save', errMsg);
});
},
/**
Display a save status message.
Args:
msg (str): The message to display.
**/
setSaveStatus: function(msg) {
$('#response__save_status h3', this.element).html(msg);
},
/**
Send a submission to the server and update the UI.
**/
submit: function() {
// Send the submission to the server
var ui = this;
var submission = $('#submission__answer__value', ui.element).val();
ui.toggleActionError('response', null);
ui.server.submit(submission).done(
// When we have successfully sent the submission, expand the next step
function(studentId, attemptNum) {
ui.renderSubmissionStep();
ui.renderPeerAssessmentStep();
}
).fail(function(errCode, errMsg) {
ui.toggleActionError('submit', errMsg);
});
},
/**
Send assessment feedback to the server and update the UI.
**/ **/
submitFeedbackOnAssessment: function() { submitFeedbackOnAssessment: function() {
// Send the submission to the server // Send the submission to the server
var ui = this; var view = this;
var text = $('#feedback__remarks__value', ui.element).val(); var text = $('#feedback__remarks__value', view.element).val();
var options = $.map( var options = $.map(
$('.feedback__overall__value:checked', ui.element), $('.feedback__overall__value:checked', view.element),
function(element, index) { return $(element).val(); } function(element, index) { return $(element).val(); }
); );
ui.server.submitFeedbackOnAssessment(text, options).done(function() { view.server.submitFeedbackOnAssessment(text, options).done(function() {
// When we have successfully sent the submission, textarea no longer editable
// TODO // TODO
// When we have successfully sent the submission, textarea no longer editable // When we have successfully sent the submission, textarea no longer editable
// console.log("Feedback to the assessments submitted, thanks!"); // console.log("Feedback to the assessments submitted, thanks!");
}).fail(function(errMsg) { }).fail(function(errMsg) {
ui.toggleActionError('feedback_assess', errMsg); view.toggleActionError('feedback_assess', errMsg);
}); });
}, },
/** /**
Send an assessment to the server and update the UI. Send an assessment to the server and update the view.
**/ **/
peerAssess: function() { peerAssess: function() {
var ui = this; var view = this;
ui.peerAssessRequest(function() { this.peerAssessRequest(function() {
ui.renderPeerAssessmentStep(); view.renderPeerAssessmentStep();
ui.renderSelfAssessmentStep(); view.renderSelfAssessmentStep();
ui.renderGradeStep(); view.renderGradeStep();
ui.scrollToTop(); view.scrollToTop();
}); });
}, },
/** /**
* Send an assessment to the server and update the UI, with the assumption * Send an assessment to the server and update the view, with the assumption
* that we are continuing peer assessments beyond the required amount. * that we are continuing peer assessments beyond the required amount.
*/ */
continuedPeerAssess: function() { continuedPeerAssess: function() {
var ui = this; var view = this;
ui.peerAssessRequest(function() { view.peerAssessRequest(function() {
ui.renderContinuedPeerAssessmentStep(); view.renderContinuedPeerAssessmentStep();
ui.renderGradeStep(); view.renderGradeStep();
}); });
}, },
...@@ -396,17 +295,17 @@ OpenAssessment.BaseUI.prototype = { ...@@ -396,17 +295,17 @@ OpenAssessment.BaseUI.prototype = {
var feedback = $('#assessment__rubric__question--feedback__value', this.element).val(); var feedback = $('#assessment__rubric__question--feedback__value', this.element).val();
// Send the assessment to the server // Send the assessment to the server
var ui = this; var view = this;
this.toggleActionError('peer', null); this.toggleActionError('peer', null);
this.server.peerAssess(submissionId, optionsSelected, feedback).done( this.server.peerAssess(submissionId, optionsSelected, feedback).done(
successFunction successFunction
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.toggleActionError('peer', errMsg); view.toggleActionError('peer', errMsg);
}); });
}, },
/** /**
Send a self-assessment to the server and update the UI. Send a self-assessment to the server and update the view.
**/ **/
selfAssess: function() { selfAssess: function() {
// Retrieve self-assessment info from the DOM // Retrieve self-assessment info from the DOM
...@@ -419,17 +318,17 @@ OpenAssessment.BaseUI.prototype = { ...@@ -419,17 +318,17 @@ OpenAssessment.BaseUI.prototype = {
); );
// Send the assessment to the server // Send the assessment to the server
var ui = this; var view = this;
this.toggleActionError('self', null); this.toggleActionError('self', null);
this.server.selfAssess(submissionId, optionsSelected).done( this.server.selfAssess(submissionId, optionsSelected).done(
function() { function() {
ui.renderPeerAssessmentStep(); view.renderPeerAssessmentStep();
ui.renderSelfAssessmentStep(); view.renderSelfAssessmentStep();
ui.renderGradeStep(); view.renderGradeStep();
ui.scrollToTop(); view.scrollToTop();
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.toggleActionError('self', errMsg); view.toggleActionError('self', errMsg);
}); });
}, },
...@@ -486,7 +385,7 @@ function OpenAssessmentBlock(runtime, element) { ...@@ -486,7 +385,7 @@ function OpenAssessmentBlock(runtime, element) {
**/ **/
$(function($) { $(function($) {
var server = new OpenAssessment.Server(runtime, element); var server = new OpenAssessment.Server(runtime, element);
var ui = new OpenAssessment.BaseUI(runtime, element, server); var view = new OpenAssessment.BaseView(runtime, element, server);
ui.load(); view.load();
}); });
} }
...@@ -8,7 +8,7 @@ if (typeof OpenAssessment == "undefined" || !OpenAssessment) { ...@@ -8,7 +8,7 @@ if (typeof OpenAssessment == "undefined" || !OpenAssessment) {
/** /**
Interface for editing UI in Studio. Interface for editing view in Studio.
The constructor initializes the DOM for editing. The constructor initializes the DOM for editing.
Args: Args:
...@@ -17,9 +17,9 @@ Args: ...@@ -17,9 +17,9 @@ Args:
server (OpenAssessment.Server): The interface to the XBlock server. server (OpenAssessment.Server): The interface to the XBlock server.
Returns: Returns:
OpenAssessment.StudioUI OpenAssessment.StudioView
**/ **/
OpenAssessment.StudioUI = function(runtime, element, server) { OpenAssessment.StudioView = function(runtime, element, server) {
this.runtime = runtime; this.runtime = runtime;
this.server = server; this.server = server;
...@@ -30,31 +30,31 @@ OpenAssessment.StudioUI = function(runtime, element, server) { ...@@ -30,31 +30,31 @@ OpenAssessment.StudioUI = function(runtime, element, server) {
); );
// Install click handlers // Install click handlers
var ui = this; var view = this;
$(element).find('.openassessment-save-button').click( $(element).find('.openassessment-save-button').click(
function(eventData) { function(eventData) {
ui.save(); view.save();
}); });
$(element).find('.openassessment-cancel-button').click( $(element).find('.openassessment-cancel-button').click(
function(eventData) { function(eventData) {
ui.cancel(); view.cancel();
}); });
}; };
OpenAssessment.StudioUI.prototype = { OpenAssessment.StudioView.prototype = {
/** /**
Load the XBlock XML definition from the server and display it in the UI. Load the XBlock XML definition from the server and display it in the view.
**/ **/
load: function() { load: function() {
var ui = this; var view = this;
this.server.loadXml().done( this.server.loadXml().done(
function(xml) { function(xml) {
ui.codeBox.setValue(xml); view.codeBox.setValue(xml);
}).fail(function(msg) { }).fail(function(msg) {
ui.showError(msg); view.showError(msg);
} }
); );
}, },
...@@ -64,17 +64,17 @@ OpenAssessment.StudioUI.prototype = { ...@@ -64,17 +64,17 @@ OpenAssessment.StudioUI.prototype = {
If the problem has been released, make the user confirm the save. If the problem has been released, make the user confirm the save.
**/ **/
save: function() { save: function() {
var ui = this; var view = this;
// Check whether the problem has been released; if not, // Check whether the problem has been released; if not,
// warn the user and allow them to cancel. // warn the user and allow them to cancel.
this.server.checkReleased().done( this.server.checkReleased().done(
function(isReleased) { function(isReleased) {
if (isReleased) { ui.confirmPostReleaseUpdate($.proxy(ui.updateXml, ui)); } if (isReleased) { view.confirmPostReleaseUpdate($.proxy(view.updateXml, view)); }
else { ui.updateXml(); } else { view.updateXml(); }
} }
).fail(function(errMsg) { ).fail(function(errMsg) {
ui.showError(msg); view.showError(msg);
}); });
}, },
...@@ -102,16 +102,16 @@ OpenAssessment.StudioUI.prototype = { ...@@ -102,16 +102,16 @@ OpenAssessment.StudioUI.prototype = {
// Send the updated XML to the server // Send the updated XML to the server
var xml = this.codeBox.getValue(); var xml = this.codeBox.getValue();
var ui = this; var view = this;
this.server.updateXml(xml).done(function() { this.server.updateXml(xml).done(function() {
// Notify the client-side runtime that we finished saving // Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification. // so it can hide the "Saving..." notification.
ui.runtime.notify('save', {state: 'end'}); view.runtime.notify('save', {state: 'end'});
// Reload the XML definition in the editor // Reload the XML definition in the editor
ui.load(); view.load();
}).fail(function(msg) { }).fail(function(msg) {
ui.showError(msg); view.showError(msg);
}); });
}, },
...@@ -143,7 +143,7 @@ function OpenAssessmentEditor(runtime, element) { ...@@ -143,7 +143,7 @@ function OpenAssessmentEditor(runtime, element) {
**/ **/
$(function($) { $(function($) {
var server = new OpenAssessment.Server(runtime, element); var server = new OpenAssessment.Server(runtime, element);
var ui = new OpenAssessment.StudioUI(runtime, element, server); var view = new OpenAssessment.StudioView(runtime, element, server);
ui.load(); view.load();
}); });
} }
/* JavaScript for response (submission) view */
/* Namespace for open assessment */
if (typeof OpenAssessment == "undefined" || !OpenAssessment) {
OpenAssessment = {};
}
/**
Interface for response (submission) view.
Args:
element (DOM element): The DOM element representing the XBlock.
server (OpenAssessment.Server): The interface to the XBlock server.
baseView (OpenAssessment.BaseView): Container view.
Returns:
OpenAssessment.ResponseView
**/
OpenAssessment.ResponseView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
this.savedResponse = "";
};
OpenAssessment.ResponseView.prototype = {
/**
Load the response (submission) view.
**/
load: function() {
var view = this;
this.server.render('submission').done(
function(html) {
// Load the HTML and install event handlers
$('#openassessment__response', view.element).replaceWith(html);
view.installHandlers();
}
).fail(function(errMsg) {
view.baseView.showLoadError('response');
});
},
/**
Install event handlers for the view.
**/
installHandlers: function() {
var sel = $('#openassessment__response', this.element);
var view = this;
// Install a click handler for collapse/expand
this.baseView.setUpCollapseExpand(sel);
// Install change handler for textarea (to enable submission button)
this.savedResponse = this.response();
var handleChange = function(eventData) { view.responseChanged(); };
sel.find('#submission__answer__value').on('change keyup drop paste', handleChange);
// Install a click handler for submission
sel.find('#step--response__submit').click(
function(eventObject) {
// Override default form submission
eventObject.preventDefault();
view.submit();
}
);
// Install a click handler for the save button
sel.find('#submission__save').click(
function(eventObject) {
// Override default form submission
eventObject.preventDefault();
view.save();
}
);
},
/**
Enable/disable the submit button.
Check that whether the submit button is enabled.
Args:
enabled (bool): If specified, set the state of the button.
Returns:
bool: Whether the button is enabled.
Examples:
>> view.submitEnabled(true); // enable the button
>> view.submitEnabled(); // check whether the button is enabled
>> true
**/
submitEnabled: function(enabled) {
var sel = $('#step--response__submit', this.element);
if (typeof enabled === 'undefined') {
return !sel.hasClass('is--disabled');
} else {
sel.toggleClass('is--disabled', !enabled)
}
},
/**
Enable/disable the save button.
Check that whether the save button is enabled.
Args:
enabled (bool): If specified, set the state of the button.
Returns:
bool: Whether the button is enabled.
Examples:
>> view.submitEnabled(true); // enable the button
>> view.submitEnabled(); // check whether the button is enabled
>> true
**/
saveEnabled: function(enabled) {
var sel = $('#submission__save', this.element);
if (typeof enabled === 'undefined') {
return !sel.hasClass('is--disabled');
} else {
sel.toggleClass('is--disabled', !enabled);
}
},
/**
Set the save status message.
Retrieve the save status message.
Args:
msg (string): If specified, the message to display.
Returns:
string: The current status message.
**/
saveStatus: function(msg) {
var sel = $('#response__save_status h3', this.element);
if (typeof msg === 'undefined') {
return sel.text();
} else {
// Setting the HTML will overwrite the screen reader tag,
// so prepend it to the message.
sel.html('<span class="sr">Your Working Submission Status:</span>\n' + msg);
}
},
/**
Set the response text.
Retrieve the response text.
Args:
text (string): If specified, the text to set for the response.
Returns:
string: The current response text.
**/
response: function(text) {
var sel = $('#submission__answer__value', this.element);
if (typeof text === 'undefined') {
return sel.val();
} else {
sel.val(text);
}
},
/**
Enable/disable the submission and save buttons based on whether
the user has entered a response.
**/
responseChanged: function() {
// Enable the save/submit button only for non-blank responses
var currentResponse = this.response();
var isBlank = (currentResponse !== '');
this.submitEnabled(isBlank);
// Update the save button and status only if the response has changed
if (this.savedResponse !== currentResponse) {
this.saveEnabled(isBlank);
this.saveStatus('Unsaved draft');
}
},
/**
Save a response without submitting it.
**/
save: function() {
// Update the save status and error notifications
this.saveStatus('Saving...');
this.baseView.toggleActionError('save', null);
var view = this;
var savedResponse = this.response();
this.server.save(savedResponse).done(function() {
// Remember which response we saved, once the server confirms that it's been saved...
view.savedResponse = savedResponse;
// ... but update the UI based on what the user may have entered
// since hitting the save button.
var currentResponse = view.response();
view.submitEnabled(currentResponse !== '');
if (currentResponse == savedResponse) {
view.saveEnabled(false);
view.saveStatus("Saved but not submitted");
}
}).fail(function(errMsg) {
view.saveStatus('Error');
view.baseView.toggleActionError('save', errMsg);
});
},
/**
Send a response submission to the server and update the view.
**/
submit: function() {
// Send the submission to the server
var submission = $('#submission__answer__value', this.element).val();
this.baseView.toggleActionError('response', null);
var view = this;
var baseView = this.baseView;
this.server.submit(submission).done(
// When we have successfully sent the submission, move on to the next step
function(studentId, attemptNum) {
view.load();
baseView.renderPeerAssessmentStep();
}
).fail(function(errCode, errMsg) {
baseView.toggleActionError('submit', errMsg);
});
}
};
...@@ -155,7 +155,7 @@ class SubmissionMixin(object): ...@@ -155,7 +155,7 @@ class SubmissionMixin(object):
Returns: Returns:
unicode unicode
""" """
return _(u'Saved but not submitted') if self.has_saved else _(u'Not saved') return _(u'Saved but not submitted') if self.has_saved else _(u'Unsaved draft')
@XBlock.handler @XBlock.handler
def render_submission(self, data, suffix=''): def render_submission(self, data, suffix=''):
...@@ -185,6 +185,7 @@ class SubmissionMixin(object): ...@@ -185,6 +185,7 @@ class SubmissionMixin(object):
context = { context = {
"saved_response": self.saved_response, "saved_response": self.saved_response,
"save_status": self.save_status, "save_status": self.save_status,
"submit_enabled": self.saved_response != '',
"submission_due": sub_due, "submission_due": sub_due,
} }
......
...@@ -14,7 +14,7 @@ class SaveResponseTest(XBlockHandlerTestCase): ...@@ -14,7 +14,7 @@ class SaveResponseTest(XBlockHandlerTestCase):
def test_default_saved_response_blank(self, xblock): def test_default_saved_response_blank(self, xblock):
resp = self.request(xblock, 'render_submission', json.dumps({})) resp = self.request(xblock, 'render_submission', json.dumps({}))
self.assertIn('<textarea id="submission__answer__value" placeholder=""></textarea>', resp) self.assertIn('<textarea id="submission__answer__value" placeholder=""></textarea>', resp)
self.assertIn('Not saved', resp) self.assertIn('Unsaved draft', resp)
@ddt.file_data('data/save_responses.json') @ddt.file_data('data/save_responses.json')
@scenario('data/save_scenario.xml', user_id="Perleman") @scenario('data/save_scenario.xml', user_id="Perleman")
...@@ -57,4 +57,4 @@ class SaveResponseTest(XBlockHandlerTestCase): ...@@ -57,4 +57,4 @@ class SaveResponseTest(XBlockHandlerTestCase):
def test_missing_submission_key(self, xblock): def test_missing_submission_key(self, xblock):
resp = self.request(xblock, 'save_submission', json.dumps({}), response_format="json") resp = self.request(xblock, 'save_submission', json.dumps({}), response_format="json")
self.assertFalse(resp['success']) self.assertFalse(resp['success'])
self.assertIn('submission', resp['msg']) self.assertIn('submission', resp['msg'])
\ 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