Commit 59d63178 by Will Daly

Merge pull request #485 from edx/will/js-refactor-assessment-editing

JS editor refactor
parents 64506f31 2c3e8690
{% load i18n %}
{% spaceless %}
<li class="openassessment_criterion is-collapsible">
<div class="openassessment_criterion_header view-outline">
<a class="action expand-collapse collapse"><i class="icon-caret-down ui-toggle-expansion"></i></a>
<h6 class="openassessment_criterion_header_title">{% trans "Criterion" %}</h6>
<div class="openassessment_rubric_remove_button"><h2>{% trans "Remove" %}</h2></div>
</div>
<div class="openassessment_criterion_body wrapper-comp-settings">
<ul class="list-input settings-list openassessment_criterion_basic_editor">
<li class="field comp-setting-entry">
<div class="wrapper-comp-settings">
<label class="openassessment_criterion_name_label setting-label">
{% trans "Criterion Name" %}
<input
class="openassessment_criterion_name input setting-input"
type="text"
value="{{ criterion_name }}"
>
</label>
</div>
</li>
<li class="field comp-setting-entry">
<div class="wrapper-comp-settings">
<label class="openassessment_criterion_prompt_label setting-label">
{% trans "Criterion Prompt" %}
<textarea class="openassessment_criterion_prompt setting-input">{{ criterion_prompt }}</textarea>
</label>
</div>
</li>
</ul>
<ul class="openassessment_criterion_option_list">
{% for option in criterion_options %}
{% include "openassessmentblock/edit/oa_edit_option.html" with option_name=option.name option_points=option.points option_explanation=option.explanation %}
{% endfor %}
</ul>
<div class="openassessment_criterion_add_option openassessment_option_header">
<h2>{% trans "Add Another Option"%}</h2>
</div>
<div class="openassessment_criterion_feedback_wrapper wrapper-comp-settings">
<ul class="list-input settings-list">
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label class="setting-label">
{% trans "Criterion Feedback" %}
<select class="openassessment_criterion_feedback input setting-input">
<option value="disabled">{% trans "Disabled" %}</option>
<option value="optional" {% if criterion_feedback == "optional" %} selected="true" {% endif %}>{% trans "Optional" %}</option>
<option value="required" {% if criterion_feedback == "required" %} selected="true" {% endif %}>{% trans "Required" %}</option>
</select>
</label>
</div>
<p class="setting-help">
{% trans "Select one of the options above. This describes whether or not the student will have to provide criterion feedback." %}
</p>
</li>
</ul>
</div>
</div>
</li>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<li class="openassessment_criterion_option">
<div class="openassessment_option_header">
<span class="openassessment_option_header_title">{% trans "Option" %}</span>
<div class="openassessment_rubric_remove_button">
<h2>{% trans "Remove" %}</h2>
</div>
</div>
<div class="wrapper-comp-settings">
<ul class="list-input settings-list">
<li class="field comp-setting-entry openassessment_criterion_option_name_wrapper">
<div class="wrapper-comp-setting">
<label class="openassessment_criterion_option_name_label setting-label">
{% trans "Option Name"%}
<input
class="openassessment_criterion_option_name input input-label"
type="text"
value="{{ option_name }}"
>
</label>
</div>
</li>
<li class="field comp-setting-entry openassessment_criterion_option_point_wrapper">
<div class="wrapper-comp-setting">
<label class="openassessment_criterion_option_points_label setting-label">
{% trans "Option Points"%}
<input
class="openassessment_criterion_option_points input setting-input"
type="number"
value="{{ option_points }}"
min="0"
>
</label>
</div>
</li>
<li class="field comp-setting-entry openassessment_criterion_option_explanation_wrapper">
<div class="wrapper-comp-setting">
<label class="openassessment_criterion_option_explanation_label setting-label">
{% trans "Option Explanation"%}
<textarea class="openassessment_criterion_option_explanation setting-input">{{ option_explanation }}</textarea>
</label>
</div>
</li>
</ul>
</div>
</li>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="oa_rubric_editor_wrapper" class="oa_editor_content_wrapper">
<div id="openassessment_criterion_template" class="is--hidden">
{% include "openassessmentblock/edit/oa_edit_criterion.html" with criterion_name="" criterion_prompt="" criterion_options=False criterion_feedback="disabled" %}
</div>
<div id="openassessment_option_template" class="is--hidden">
{% include "openassessmentblock/edit/oa_edit_option.html" with option_name="" option_points="" option_explanation="" %}
</div>
<div id="openassessment_rubric_instructions">
<p class = openassessment_description>
......@@ -9,142 +16,20 @@
</div>
<ul id="openassessment_criterion_list" >
{% for criterion in criteria %}
{% with criterion_num=forloop.counter %}
<li class="openassessment_criterion is-collapsible" id="openassessment_criterion_1">
<div class="openassessment_criterion_header view-outline" id="openassessment_criterion_header_{{ criterion_num }}">
<a class="action expand-collapse collapse">
<i class="icon-caret-down ui-toggle-expansion"></i>
</a>
<h6 class="openassessment_criterion_header_title">
{% blocktrans %} Criterion {{ criterion_num }}{% endblocktrans %}
</h6>
<div class="openassessment_rubric_remove_button" id="openassessment_criterion_{{ criterion_num }}_remove">
<h2>{% trans "Remove" %}</h2>
</div>
</div>
<div class="openassessment_criterion_body wrapper-comp-settings" id="openassessment_criterion_body_{{ criterion_num }}">
<ul class="list-input settings-list openassessment_criterion_basic_editor">
<li class="field comp-setting-entry">
<div class="wrapper-comp-settings">
<label for="openassessment_criterion_{{ criterion_num }}_name" class="openassessment_criterion_name_label setting-label">
{% trans "Criterion Name"%}
</label>
<input id="openassessment_criterion_{{ criterion_num }}_name" class="openassessment_criterion_name input setting-input" type="text" value={{ criterion.name }}>
</div>
</li>
<li class="field comp-setting-entry">
<div class="wrapper-comp-settings">
<label for="openassessment_criterion_{{ criterion_num }}_prompt" class="openassessment_criterion_prompt_label setting-label">
{% trans "Criterion Prompt"%}
</label>
<textarea id="openassessment_criterion_{{ criterion_num }}_prompt" class="openassessment_criterion_prompt setting-input">{{ criterion.prompt }}</textarea>
</div>
</li>
</ul>
<ul id="openassessment_criterion_{{ criterion_num }}_options" class="openassessment_criterion_option_list">
{% for option in criterion.options %}
{% with option_num=forloop.counter %}
<li id=openassessment_criterion_{{ criterion_num }}_option_{{ option_num }} class="openassessment_criterion_option">
<div class="openassessment_option_header">
<span class="openassessment_option_header_title">
{% blocktrans %} Option {{ option_num }}{% endblocktrans %}
</span>
<div class="openassessment_rubric_remove_button" id="openassessment_criterion_{{ criterion_num }}_option_1_remove">
<h2>{% trans "Remove" %}</h2>
</div>
</div>
<div class="wrapper-comp-settings">
<ul class="list-input settings-list">
<li class="field comp-setting-entry openassessment_criterion_option_name_wrapper">
<div class="wrapper-comp-setting">
<label for="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_name"
class="openassessment_criterion_option_name_label setting-label">
{% trans "Option Name"%}
</label>
<input id="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_name"
class="openassessment_criterion_option_name input input-label"
type="text"
value="{{ option.name }}">
</div>
</li>
<li class="field comp-setting-entry openassessment_criterion_option_point_wrapper">
<div class="wrapper-comp-setting">
<label for="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_points"
class="openassessment_criterion_option_points_label setting-label">
{% trans "Option Points"%}
</label>
<input id="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_points"
class="openassessment_criterion_option_points input setting-input"
type="number"
value="{{ option.points }}">
</div>
</li>
<li class="field comp-setting-entry openassessment_criterion_option_explanation_wrapper">
<div class="wrapper-comp-setting">
<label for="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_explanation"
class="openassessment_criterion_option_explanation_label setting-label">
{% trans "Option Explanation"%}
</label>
<textarea id="openassessment_criterion_{{ criterion_num }}_option_{{ option_num }}_explanation"
class="openassessment_criterion_option_explanation setting-input">{{ option.explanation }}</textarea>
</div>
</li>
</ul>
</div>
</li>
{% endwith %}
{% endfor %}
</ul>
<div id="openassessment_criterion_{{ criterion_num }}_add_option" class="openassessment_criterion_add_option openassessment_option_header">
<h2>{% trans "Add Another Option"%}</h2>
</div>
<div id="openassessment_criterion_{{ criterion_num }}_feedback_wrapper" class="openassessment_criterion_feedback_wrapper wrapper-comp-settings">
<ul class="list-input settings-list">
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="openassessment_criterion_{{ criterion_num }}_feedback" class="setting-label">{% trans "Criterion Feeedback" %}</label>
<select id="openassessment_criterion_{{ criterion_num }}_feedback" class="input setting-input">
<option value="disabled">{% trans "Disabled" %}</option>
<option value="optional" {% if criterion.feedbackprompt == "optional" %} selected="true" {% endif %}>{% trans "Optional" %}</option>
<option value="required" {% if criterion.feedbackprompt == "required" %} selected="true" {% endif %}>{% trans "Required" %}</option>
</select>
</div>
<p class="setting-help">
{% trans "Select one of the options above. This describes whether or not the student will have to provide criterion feedback." %}
</p>
</li>
</ul>
</div>
</div>
</li>
{% endwith %}
{% for criterion in criteria %}
{% include "openassessmentblock/edit/oa_edit_criterion.html" with criterion_name=criterion.name criterion_prompt=criterion.prompt criterion_options=criterion.options criterion_feedback=criterion.feedback %}
{% endfor %}
</ul>
<div id="openassessment_rubric_add_criterion">
<h6>
{% trans "Add Another Criterion"%}
{% trans "Add Another Criterion" %}
</h6>
</div>
<div id="openassessment_rubric_feedback_wrapper" class="wrapper-comp-settings">
<div id="openassessment_rubric_feedback_header_open">
<span>
{% trans "Rubric Feedback" %}
</span>
<div class="openassessment_rubric_remove_button" id="openassessment_rubric_feedback_remove">
<h2>{% trans "Remove" %}</h2>
</div>
<div id="openassessment_rubric_feedback_header_closed">
<h2>
{% trans "Add Rubric Feedback" %}
</h2>
</div>
<div id="openassessment_rubric_feedback_header">
<span>{% trans "Rubric Feedback" %}</span>
</div>
<ul class="list-input settings-list">
<li class="field comp-setting-entry">
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -385,44 +385,46 @@
"title": "The most important of all questions.",
"submission_due": "2014-10-1T10:00:00",
"criteria": [
{
"name": "Criterion 1",
"prompt": "Prompt 1",
"name": "Criterion with two options",
"prompt": "Prompt for criterion with two options",
"order_num": 0,
"feedback": "optional",
"feedback": "disabled",
"options": [
{
"order_num": 2,
"order_num": 0,
"points": 1,
"name": "Fair",
"explanation": "Fair explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Good"
"name": "Good",
"explanation": "Good explanation"
}
],
"points_possible": 2
},
{
"name": "Criterion 2",
"prompt": "Prompt 2",
"order_num": 1,
"options": [
{
"order_num": 1,
"points": 1,
"name": "Fair"
}
],
"points_possible": 2
"name": "Criterion with no options",
"prompt": "Prompt for criterion with no options",
"order_num": 0,
"options": [],
"feedback": "required",
"points_possible": 0
},
{
"name": "Criterion 3",
"prompt": "Prompt 3",
"name": "Criterion with optional feedback",
"prompt": "Prompt for criterion with optional feedback",
"order_num": 2,
"feedback": "optional",
"options": [
{
"order_num": 2,
"order_num": 0,
"points": 2,
"name": "Good"
"name": "Good",
"explanation": "Good explanation"
}
],
"points_possible": 2
......
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.BaseView=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffInfoView=new OpenAssessment.StaffInfoView(this.element,this.server,this)};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.load();this.loadAssessmentModules();this.staffInfoView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.selfView.load();this.gradeView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,msg){var element=this.element;var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"||type=="peer"||type=="self"||type=="student-training"){container=".step__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",element).html("<p>"+msgHtml+"</p>");$(container,element).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(gettext("Unable to Load"))}};function OpenAssessmentBlock(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server);view.load()}OpenAssessment.StudioView=function(runtime,element,server){this.runtime=runtime;this.server=server;this.liveElement=$(element);var liveElement=this.liveElement;this.fixModalHeight();this.settingsFieldSelectors={promptBox:$("#openassessment_prompt_editor",liveElement),titleField:$("#openassessment_title_editor",liveElement),submissionStartField:$("#openassessment_submission_start_editor",liveElement),submissionDueField:$("#openassessment_submission_due_editor",liveElement),hasPeer:$("#include_peer_assessment",liveElement),hasSelf:$("#include_self_assessment",liveElement),hasAI:$("#include_ai_assessment",liveElement),hasTraining:$("#include_student_training",liveElement),peerMustGrade:$("#peer_assessment_must_grade",liveElement),peerGradedBy:$("#peer_assessment_graded_by",liveElement),peerStart:$("#peer_assessment_start_date",liveElement),peerDue:$("#peer_assessment_due_date",liveElement),selfStart:$("#self_assessment_start_date",liveElement),selfDue:$("#self_assessment_due_date",liveElement)};var criterionHtml=$("#openassessment_criterion_1",liveElement).parent().html();this.criterionHtmlTemplate=criterionHtml.replace(new RegExp("1","g"),"C-C-C");var optionHtml=$("#openassessment_criterion_1_option_1",liveElement).parent().html();var criteriaReplaced=optionHtml.replace(new RegExp("criterion_1","g"),"criterion_C-C-C");this.optionHtmlTemplate=criteriaReplaced.replace(new RegExp("option_1","g"),"option_O-O-O");this.numberOfCriteria=0;this.numberOfOptions=[];this.rubricCriteriaSelectors=[];this.rubricFeedbackPrompt=$("#openassessment_rubric_feedback",liveElement);$("#openassessment_criterion_list",liveElement).empty();this.addNewCriterionToRubric();var view=this;$(".openassessment_save_button",liveElement).click(function(eventData){view.save()});$(".openassessment_cancel_button",liveElement).click(function(eventData){view.cancel()});$(".openassessment_editor_content_and_tabs",liveElement).tabs();view.addSettingsAssessmentCheckboxListener("ai_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("self_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("peer_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("student_training",liveElement);$("#openassessment_rubric_add_criterion",liveElement).click(function(eventData){view.addNewCriterionToRubric(liveElement)})};OpenAssessment.StudioView.prototype={fixModalHeight:function(){var element=this.liveElement;element.toggleClass("openassessment_full_height");element.parentsUntil(".modal-window").toggleClass("openassessment_full_height");$(".modal-window").toggleClass("openassessment_modal_window")},load:function(){var view=this},save:function(){var view=this;this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})},addSettingsAssessmentCheckboxListener:function(name,liveElement){$("#include_"+name,liveElement).change(function(){$("#"+name+"_description_closed",liveElement).toggleClass("is--hidden",this.checked);$("#"+name+"_settings_editor",liveElement).toggleClass("is--hidden",!this.checked)})},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},addNewCriterionToRubric:function(){var view=this;var liveElement=this.liveElement;var newCriterionID=this.numberOfCriteria+1;this.numberOfCriteria+=1;this.numberOfOptions[newCriterionID]=0;var criterionHtml=this.criterionHtmlTemplate.replace(new RegExp("C-C-C","g"),""+newCriterionID);$("#openassessment_criterion_list",liveElement).append(criterionHtml);liveElement=$("#openassessment_criterion_"+newCriterionID);$(".openassessment_criterion_option_list",liveElement).empty();view.rubricCriteriaSelectors[newCriterionID]={criterion:liveElement,name:$(".openassessment_criterion_name",liveElement).first(),prompt:$(".openassessment_criterion_prompt",liveElement).first(),options:[],feedback:"disabled"};$("input:radio[value=disabled]",liveElement).prop("checked",true);view.addNewOptionToCriterion(liveElement,newCriterionID);$("#openassessment_display_criterion_"+newCriterionID,liveElement).change(function(){if(this.checked){$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeIn()}else{$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeOut()}});$("#openassessment_criterion_"+newCriterionID+"_remove",liveElement).click(function(eventData){view.removeCriterionFromRubric(newCriterionID)});$("#openassessment_criterion_"+newCriterionID+"_add_option",liveElement).click(function(eventData){view.addNewOptionToCriterion(liveElement,newCriterionID)});$(".openassessment_feedback_remove_button",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_open",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeIn();$(".openassessment_feedback_remove_button",liveElement).fadeOut();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=false});$(".openassessment_criterion_feedback_header_closed",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_open",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeOut();$(".openassessment_feedback_remove_button",liveElement).fadeIn();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=true});$(".openassessment_criterion_feedback_header_closed",liveElement).hide()},removeCriterionFromRubric:function(criterionToRemove){var view=this;var numCriteria=view.numberOfCriteria;var selectors=view.rubricCriteriaSelectors;for(var i=criterionToRemove;i<numCriteria;i++){selectors[i].name.prop("value",selectors[i+1].name.prop("value"));selectors[i].prompt.prop("value",selectors[i+1].prompt.prop("value"));selectors[i].feedback=selectors[i+1].feedback;$('input:radio[value="disabled"]',selectors[i].criterion).prop("checked",true);while(view.numberOfOptions[i]<view.numberOfOptions[i+1]){view.addNewOptionToCriterion(selectors[i].criteria,i)}while(view.numberOfOptions[i]>view.numberOfOptions[i+1]){view.removeOptionFromCriterion(selectors[i].criteria,i,1)}var options1=selectors[i].options;var options2=selectors[i+1].options;var numOptions2=view.numberOfOptions[i+1];for(var j=1;j<numOptions2;j++){options1[j].points.prop("value",options2[j].points.prop("value"));options1[j].name.prop("value",options2[j].name.prop("value"));options1[j].explanation.prop("value",options2[j].explanation.prop("value"))}}view.rubricCriteriaSelectors[view.rubricCriteriaSelectors.length-1].criterion.remove();view.rubricCriteriaSelectors=view.rubricCriteriaSelectors.slice(0,numCriteria);view.numberOfOptions=view.numberOfOptions.slice(0,numCriteria);view.numberOfCriteria-=1},addNewOptionToCriterion:function(liveElement,criterionID){var view=this;var newOptionID=this.numberOfOptions[criterionID]+1;this.numberOfOptions[criterionID]+=1;var optionHtml=this.optionHtmlTemplate;optionHtml=optionHtml.replace(new RegExp("C-C-C","g"),""+criterionID);optionHtml=optionHtml.replace(new RegExp("O-O-O","g"),""+newOptionID);$("#openassessment_criterion_"+criterionID+"_options",liveElement).append(optionHtml);liveElement=$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID);view.rubricCriteriaSelectors[criterionID].options[newOptionID]={option:liveElement,points:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_points",liveElement),name:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_name",liveElement),explanation:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_explanation",liveElement)};$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_remove",liveElement).click(function(eventData){view.removeOptionFromCriterion(liveElement,criterionID,newOptionID)})},removeOptionFromCriterion:function(liveElement,criterionID,optionToRemove){var view=this;var numberOfOptions=view.numberOfOptions[criterionID];var optionSelectors=view.rubricCriteriaSelectors[criterionID].options;for(var i=optionToRemove;i<numberOfOptions;i++){optionSelectors[i].points.prop("value",optionSelectors[i+1].points.prop("value"));optionSelectors[i].name.prop("value",optionSelectors[i+1].name.prop("value"));optionSelectors[i].explanation.prop("value",optionSelectors[i+1].explanation.prop("value"))}optionSelectors[optionSelectors.length-1].option.remove();view.rubricCriteriaSelectors[criterionID].options=view.rubricCriteriaSelectors[criterionID].options.slice(0,optionSelectors.length-1);view.numberOfOptions[criterionID]-=1},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var rubricCriteria=[];for(var i=1;i<=this.numberOfCriteria;i++){var selectorDict=this.rubricCriteriaSelectors[i];var criterionValueDict={order_num:i-1,name:selectorDict.name.prop("value"),prompt:selectorDict.prompt.prop("value"),feedback:$("#openassessment_criterion_"+i+"_feedback").val()};var optionSelectorList=selectorDict.options;var optionValueList=[];for(var j=1;j<=this.numberOfOptions[i];j++){var optionSelectors=optionSelectorList[j];optionValueList=optionValueList.concat([{order_num:j-1,points:this._getInt(optionSelectors.points),name:optionSelectors.name.val(),explanation:optionSelectors.explanation.val()}])}criterionValueDict.options=optionValueList;rubricCriteria=rubricCriteria.concat([criterionValueDict])}var assessments=[];if(this.settingsFieldSelectors.hasTraining.prop("checked")){assessments.push({name:"student-training",examples:this.studentTrainingExamplesCodeBox.getValue()})}if(this.settingsFieldSelectors.hasPeer.prop("checked")){assessments.push({name:"peer-assessment",must_grade:this._getInt(this.settingsFieldSelectors.peerMustGrade),must_be_graded_by:this._getInt(this.settingsFieldSelectors.peerGradedBy),start:this._getDateTime(this.settingsFieldSelectors.peerStart),due:this._getDateTime(this.settingsFieldSelectors.peerDue)})}if(this.settingsFieldSelectors.hasSelf.prop("checked")){assessments.push({name:"self-assessment",start:this._getDateTime(this.settingsFieldSelectors.selfStart),due:this._getDateTime(this.settingsFieldSelectors.selfDue)})}if(this.settingsFieldSelectors.hasAI.prop("checked")){assessments.push({name:"example-based-assessment",examples:this.aiTrainingExamplesCodeBox.getValue()})}var view=this;this.server.updateEditorContext({title:this.settingsFieldSelectors.titleField.val(),prompt:this.settingsFieldSelectors.promptBox.val(),feedbackPrompt:this.rubricFeedbackPrompt.val(),submissionStart:this._getDateTime(this.settingsFieldSelectors.submissionStartField),submissionDue:this._getDateTime(this.settingsFieldSelectors.submissionDueField),criteria:rubricCriteria,assessments:assessments}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})},_getDateTime:function(selector){var dateStr=selector.val();if(dateStr===""){return null}var timestamp=Date.parse(dateStr);if(isNaN(timestamp)){return null}return new Date(timestamp).toISOString()},_getInt:function(selector){return parseInt(selector.val(),10)}};function OpenAssessmentEditor(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server);view.load()}OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element,index){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(sel,hidden){sel.toggleClass("is--hidden",hidden);sel.attr("aria-hidden",hidden?"true":"false")},isHidden:function(sel){return sel.hasClass("is--hidden")&&sel.attr("aria-hidden")=="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState=="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState=="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState=="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$("#feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html)}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(false)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(true)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")})},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel,$.proxy(view.loadContinuedAssessment,view));var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){view.load();baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.overallFeedback()).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})},overallFeedback:function(overallFeedback){var selector="#assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}}};OpenAssessment.ResponseView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.savedResponse="";this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.installHandlers();view.setAutoSaveEnabled(true)}).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.handleResponseChanged()};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()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},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{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+label+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},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=$.trim(this.response());var savedResponse=$.trim(this.savedResponse);return savedResponse!==currentResponse},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isBlank=$.trim(this.response())!=="";this.submitEnabled(isBlank);if(this.responseChanged()){this.saveEnabled(isBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);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(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;this.confirmSubmission().pipe(function(){var submission=$("#submission__answer__value",view.element).val();baseView.toggleActionError("response",null);return view.server.submit(submission)}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode=="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg="You're about to submit your response for this assignment. "+"After you submit this response, you can't change it or submit a new response.";return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value==optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){$(this.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;var canSubmit=numChecked==numAvailable;callback(canSubmit)})},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);view.selfSubmitEnabled(false);var options=this.rubric.optionsSelected();this.server.selfAssess(options).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},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,[gettext("This section could not be loaded.")])})}).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,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(student_id){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{student_id:student_id}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");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",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");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,[gettext("This response could not be saved.")])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");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,[gettext("This feedback could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback){var url=this.url("peer_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback});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,[gettext("This assessment could not be submitted.")])})}).promise()},selfAssess:function(optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({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,[gettext("This assessment could not be submitted.")])})})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},updateEditorContext:function(kwargs){var url=this.url("update_editor_context");var payload=JSON.stringify({prompt:kwargs.prompt,feedback_prompt:kwargs.feedbackPrompt,title:kwargs.title,submission_start:kwargs.submissionStart,submission_due:kwargs.submissionDue,criteria:kwargs.criteria,assessments:kwargs.assessments});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,[gettext("This problem could not be saved.")])})}).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,[gettext("The server could not be contacted.")])})}).promise()}};if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.StaffInfoView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffInfoView.prototype={load:function(){var view=this;if($("#openassessment__staff-info",view.element).length>0){this.server.render("staff_info").done(function(html){$("#openassessment__staff-info",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("staff_info")})}},loadStudentInfo:function(){var view=this;var sel=$("#openassessment__staff-info",this.element);var student_id=sel.find("#openassessment__student_id").val();this.server.studentInfo(student_id).done(function(html){$("#openassessment__student-info",view.element).replaceWith(html)}).fail(function(errMsg){view.showLoadError("student_info")})},installHandlers:function(){var sel=$("#openassessment__staff-info",this.element);var view=this;if(sel.length<=0){return}this.baseView.setUpCollapseExpand(sel,function(){});sel.find("#openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#submit_student_id").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#schedule_training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});sel.find("#reschedule_unfinished_tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$("#schedule_training_message",this.element).text(msg)}).fail(function(errMsg){$("#schedule_training_message",this.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$("#reschedule_unfinished_tasks_message",this.element).text(msg)}).fail(function(errMsg){$("#reschedule_unfinished_tasks_message",this.element).text(errMsg)})}};OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",this.element);var instructions=$("#openassessment__student-training--instructions",this.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}};
\ No newline at end of file
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.BaseView=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffInfoView=new OpenAssessment.StaffInfoView(this.element,this.server,this)};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.load();this.loadAssessmentModules();this.staffInfoView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.selfView.load();this.gradeView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,msg){var element=this.element;var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"||type=="peer"||type=="self"||type=="student-training"){container=".step__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",element).html("<p>"+msgHtml+"</p>");$(container,element).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(gettext("Unable to Load"))}};function OpenAssessmentBlock(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server);view.load()}OpenAssessment.Container=function(containerItem,kwargs){this.containerItem=containerItem;this.containerElement=kwargs.containerElement;this.templateElement=kwargs.templateElement;this.addButtonElement=kwargs.addButtonElement;this.removeButtonClass=kwargs.removeButtonClass;this.containerItemClass=kwargs.containerItemClass;$(this.addButtonElement).click($.proxy(this.add,this));var container=this;$("."+this.removeButtonClass,this.containerElement).click(function(eventData){container.remove(eventData.target)});$("."+this.containerItemClass,this.containerElement).each(function(index,element){new container.containerItem(element)})};OpenAssessment.Container.prototype={add:function(){$(this.templateElement).clone().removeAttr("id").toggleClass("is--hidden",false).toggleClass(this.containerItemClass,true).appendTo($(this.containerElement));var container=this;var containerItem=$("."+this.containerItemClass,this.containerElement).last();containerItem.find("."+this.removeButtonClass).click(function(eventData){container.remove(eventData.target)});new this.containerItem(containerItem.get(0))},remove:function(element){$(element).closest("."+this.containerItemClass).remove()},getItemValues:function(){var values=[];var container=this;$("."+this.containerItemClass,this.containerElement).each(function(index,element){var containerItem=new container.containerItem(element);var fieldValues=containerItem.getFieldValues();values.push(fieldValues)});return values},getItemElement:function(index){var element=$("."+this.containerItemClass,this.containerElement).get(index);return element!==undefined?element:null}};OpenAssessment.RubricOption=function(element){this.element=element};OpenAssessment.RubricOption.prototype={getFieldValues:function(){return{name:OpenAssessment.Fields.stringField($(".openassessment_criterion_option_name",this.element)),points:OpenAssessment.Fields.intField($(".openassessment_criterion_option_points",this.element)),explanation:OpenAssessment.Fields.stringField($(".openassessment_criterion_option_explanation",this.element))}}};OpenAssessment.RubricCriterion=function(element){this.element=element;this.optionContainer=new OpenAssessment.Container(OpenAssessment.RubricOption,{containerElement:$(".openassessment_criterion_option_list",this.element).get(0),templateElement:$("#openassessment_option_template").get(0),addButtonElement:$(".openassessment_criterion_add_option",this.element).get(0),removeButtonClass:"openassessment_rubric_remove_button",containerItemClass:"openassessment_criterion_option"})};OpenAssessment.RubricCriterion.prototype={getFieldValues:function(){return{name:OpenAssessment.Fields.stringField($(".openassessment_criterion_name",this.element)),prompt:OpenAssessment.Fields.stringField($(".openassessment_criterion_prompt",this.element)),feedback:OpenAssessment.Fields.stringField($(".openassessment_criterion_feedback",this.element)),options:this.optionContainer.getItemValues()}}};OpenAssessment.StudioView=function(runtime,element,server){this.element=element;this.runtime=runtime;this.server=server;this.fixModalHeight();$(".openassessment_editor_content_and_tabs",this.element).tabs();this.promptView=new OpenAssessment.EditPromptView($("#oa_prompt_editor_wrapper",this.element).get(0));this.settingsView=new OpenAssessment.EditSettingsView($("#oa_basic_settings_editor",this.element).get(0),[new OpenAssessment.EditPeerAssessmentView($("#oa_peer_assessment_editor",this.element).get(0)),new OpenAssessment.EditSelfAssessmentView($("#oa_self_assessment_editor",this.element).get(0)),new OpenAssessment.EditStudentTrainingView($("#oa_student_training_editor",this.element).get(0)),new OpenAssessment.EditExampleBasedAssessmentView($("#oa_ai_assessment_editor",this.element).get(0))]);this.rubricView=new OpenAssessment.EditRubricView($("#oa_rubric_editor_wrapper",this.element).get(0));$(".openassessment_save_button",this.element).click($.proxy(this.save,this));$(".openassessment_cancel_button",this.element).click($.proxy(this.cancel,this))};OpenAssessment.StudioView.prototype={fixModalHeight:function(){$(this.element).toggleClass("openassessment_full_height").parentsUntil(".modal-window").toggleClass("openassessment_full_height");$(".modal-window").toggleClass("openassessment_modal_window")},save:function(){var view=this;this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var view=this;this.server.updateEditorContext({prompt:view.promptView.promptText(),feedbackPrompt:view.rubricView.feedbackPrompt(),criteria:view.rubricView.criteriaDefinition(),title:view.settingsView.displayName(),submissionStart:view.settingsView.submissionStart(),submissionDue:view.settingsView.submissionDue(),assessments:view.settingsView.assessmentsDescription()}).done(function(){view.runtime.notify("save",{state:"end"})}).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){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server)}OpenAssessment.ToggleControl=function(element,hiddenSelector,shownSelector){this.element=element;this.hiddenSelector=hiddenSelector;this.shownSelector=shownSelector};OpenAssessment.ToggleControl.prototype={install:function(checkboxSelector){$(checkboxSelector,this.element).change(this,function(event){var control=event.data;if(this.checked){control.show()}else{control.hide()}});return this},show:function(){$(this.hiddenSelector,this.element).fadeOut("fast");$(this.shownSelector,this.element).fadeIn()},hide:function(){$(this.hiddenSelector,this.element).fadeIn();$(this.shownSelector,this.element).fadeOut()}};OpenAssessment.EditPeerAssessmentView=function(element){this.element=element;this.name="peer-assessment";new OpenAssessment.ToggleControl(this.element,"#peer_assessment_description_closed","#peer_assessment_settings_editor").install("#include_peer_assessment")};OpenAssessment.EditPeerAssessmentView.prototype={description:function(){return{must_grade:this.mustGradeNum(),must_be_graded_by:this.mustBeGradedByNum(),start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_peer_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},mustGradeNum:function(num){var sel=$("#peer_assessment_must_grade",this.element);return OpenAssessment.Fields.intField(sel,num)},mustBeGradedByNum:function(num){var sel=$("#peer_assessment_graded_by",this.element);return OpenAssessment.Fields.intField(sel,num)},startDatetime:function(datetime){var sel=$("#peer_assessment_start_date",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)},dueDatetime:function(datetime){var sel=$("#peer_assessment_due_date",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)}};OpenAssessment.EditSelfAssessmentView=function(element){this.element=element;this.name="self-assessment";new OpenAssessment.ToggleControl(this.element,"#self_assessment_description_closed","#self_assessment_settings_editor").install("#include_self_assessment")};OpenAssessment.EditSelfAssessmentView.prototype={description:function(){return{start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_self_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},startDatetime:function(datetime){var sel=$("#self_assessment_start_date",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)},dueDatetime:function(datetime){var sel=$("#self_assessment_due_date",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)}};OpenAssessment.EditStudentTrainingView=function(element){this.element=element;this.name="student-training";new OpenAssessment.ToggleControl(this.element,"#student_training_description_closed","#student_training_settings_editor").install("#include_student_training")};OpenAssessment.EditStudentTrainingView.prototype={description:function(){return{examples:this.exampleDefinitions()}},isEnabled:function(isEnabled){var sel=$("#include_student_training",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},exampleDefinitions:function(xml){var sel=$("#student_training_examples",this.element);return OpenAssessment.Fields.stringField(sel,xml)}};OpenAssessment.EditExampleBasedAssessmentView=function(element){this.element=element;this.name="example-based-assessment";new OpenAssessment.ToggleControl(this.element,"#ai_assessment_description_closed","#ai_assessment_settings_editor").install("#include_ai_assessment")};OpenAssessment.EditExampleBasedAssessmentView.prototype={description:function(){return{examples:this.exampleDefinitions()}},isEnabled:function(isEnabled){var sel=$("#include_ai_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},exampleDefinitions:function(xml){var sel=$("#ai_training_examples",this.element);return OpenAssessment.Fields.stringField(sel,xml)}};OpenAssessment.Fields={stringField:function(sel,value){if(typeof value!=="undefined"){sel.val(value)}return sel.val()},datetimeField:function(sel,value){if(typeof value!=="undefined"){sel.val(value)}var fieldValue=sel.val();return fieldValue!==""?fieldValue:null},intField:function(sel,value){if(typeof value!=="undefined"){sel.val(value)}return parseInt(sel.val(),10)},booleanField:function(sel,value){if(typeof value!=="undefined"){sel.prop("checked",value)}return sel.prop("checked")}};OpenAssessment.EditPromptView=function(element){this.element=element};OpenAssessment.EditPromptView.prototype={promptText:function(text){var sel=$("#openassessment_prompt_editor",this.element);return OpenAssessment.Fields.stringField(sel,text)}};OpenAssessment.EditRubricView=function(element){this.element=element;this.criteriaContainer=new OpenAssessment.Container(OpenAssessment.RubricCriterion,{containerElement:$("#openassessment_criterion_list",this.element).get(0),templateElement:$("#openassessment_criterion_template",this.element).get(0),addButtonElement:$("#openassessment_rubric_add_criterion",this.element).get(0),removeButtonClass:"openassessment_rubric_remove_button",containerItemClass:"openassessment_criterion"})};OpenAssessment.EditRubricView.prototype={criteriaDefinition:function(){var criteria=this.criteriaContainer.getItemValues();for(var criterion_idx=0;criterion_idx<criteria.length;criterion_idx++){var criterion=criteria[criterion_idx];criterion.order_num=criterion_idx;for(var option_idx=0;option_idx<criterion.options.length;option_idx++){var option=criterion.options[option_idx];option.order_num=option_idx}}return criteria},feedbackPrompt:function(text){var sel=$("#openassessment_rubric_feedback",this.element);return OpenAssessment.Fields.stringField(sel,text)}};OpenAssessment.EditSettingsView=function(element,assessmentViews){this.element=element;this.assessmentViews=assessmentViews};OpenAssessment.EditSettingsView.prototype={displayName:function(name){var sel=$("#openassessment_title_editor",this.element);return OpenAssessment.Fields.stringField(sel,name)},submissionStart:function(datetime){var sel=$("#openassessment_submission_start_editor",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)},submissionDue:function(datetime){var sel=$("#openassessment_submission_start_editor",this.element);return OpenAssessment.Fields.datetimeField(sel,datetime)},imageSubmissionEnabled:function(isEnabled){var sel=$("#openassessment_submission_image_editor",this.element);if(typeof isEnabled!=="undefined"){if(isEnabled){sel.val(1)}else{sel.val(0)}}return sel.val()==1},assessmentsDescription:function(){assessmentDescList=[];for(var idx in this.assessmentViews){var asmntView=this.assessmentViews[idx];if(asmntView.isEnabled()){var description=asmntView.description();description["name"]=asmntView.name;assessmentDescList.push(description)}}return assessmentDescList}};OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element,index){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(sel,hidden){sel.toggleClass("is--hidden",hidden);sel.attr("aria-hidden",hidden?"true":"false")},isHidden:function(sel){return sel.hasClass("is--hidden")&&sel.attr("aria-hidden")=="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState=="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState=="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState=="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$("#feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html)}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(false)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(true)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")})},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel,$.proxy(view.loadContinuedAssessment,view));var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){view.load();baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.overallFeedback()).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})},overallFeedback:function(overallFeedback){var selector="#assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}}};OpenAssessment.ResponseView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.savedResponse="";this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.installHandlers();view.setAutoSaveEnabled(true)}).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.handleResponseChanged()};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()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},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{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+label+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},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=$.trim(this.response());var savedResponse=$.trim(this.savedResponse);return savedResponse!==currentResponse},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isBlank=$.trim(this.response())!=="";this.submitEnabled(isBlank);if(this.responseChanged()){this.saveEnabled(isBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);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(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;this.confirmSubmission().pipe(function(){var submission=$("#submission__answer__value",view.element).val();baseView.toggleActionError("response",null);return view.server.submit(submission)}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode=="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg="You're about to submit your response for this assignment. "+"After you submit this response, you can't change it or submit a new response.";return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value==optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){$(this.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;var canSubmit=numChecked==numAvailable;callback(canSubmit)})},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);view.selfSubmitEnabled(false);var options=this.rubric.optionsSelected();this.server.selfAssess(options).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},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,[gettext("This section could not be loaded.")])})}).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,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(student_id){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{student_id:student_id}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");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",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");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,[gettext("This response could not be saved.")])
})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");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,[gettext("This feedback could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback){var url=this.url("peer_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback});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,[gettext("This assessment could not be submitted.")])})}).promise()},selfAssess:function(optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({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,[gettext("This assessment could not be submitted.")])})})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},updateEditorContext:function(kwargs){var url=this.url("update_editor_context");var payload=JSON.stringify({prompt:kwargs.prompt,feedback_prompt:kwargs.feedbackPrompt,title:kwargs.title,submission_start:kwargs.submissionStart,submission_due:kwargs.submissionDue,criteria:kwargs.criteria,assessments:kwargs.assessments});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,[gettext("This problem could not be saved.")])})}).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,[gettext("The server could not be contacted.")])})}).promise()}};if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.StaffInfoView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffInfoView.prototype={load:function(){var view=this;if($("#openassessment__staff-info",view.element).length>0){this.server.render("staff_info").done(function(html){$("#openassessment__staff-info",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("staff_info")})}},loadStudentInfo:function(){var view=this;var sel=$("#openassessment__staff-info",this.element);var student_id=sel.find("#openassessment__student_id").val();this.server.studentInfo(student_id).done(function(html){$("#openassessment__student-info",view.element).replaceWith(html)}).fail(function(errMsg){view.showLoadError("student_info")})},installHandlers:function(){var sel=$("#openassessment__staff-info",this.element);var view=this;if(sel.length<=0){return}this.baseView.setUpCollapseExpand(sel,function(){});sel.find("#openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#submit_student_id").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#schedule_training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});sel.find("#reschedule_unfinished_tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$("#schedule_training_message",this.element).text(msg)}).fail(function(errMsg){$("#schedule_training_message",this.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$("#reschedule_unfinished_tasks_message",this.element).text(msg)}).fail(function(errMsg){$("#reschedule_unfinished_tasks_message",this.element).text(errMsg)})}};OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",this.element);var instructions=$("#openassessment__student-training--instructions",this.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}};
\ No newline at end of file
/**
Tests for the Openassessment Container Object.
**/
describe("OpenAssessment.Container", function () {
var counter = 0;
var StubContainerItem = function(element) {
// Assign an ID to the item if it doesn't already have one.
if ($(element).attr("test_id") === "") {
$(element).attr("test_id", counter);
counter += 1;
}
this.getFieldValues = function() {
var testIdNum = parseInt($(element).attr("test_id"), 10);
return { id: testIdNum };
};
};
var container = null;
var createContainer = function() {
return new OpenAssessment.Container(
StubContainerItem, {
containerElement: $("#container").get(0),
templateElement: $("#template").get(0),
addButtonElement: $("#add_button").get(0),
removeButtonClass: "remove_button",
containerItemClass: "test_item",
}
);
};
beforeEach(function () {
// Reset the counter before each test
counter = 0;
// Install a minimal fixture
// We don't need to use a full ORA2 template for this,
// so we just define the fixture inline.
setFixtures(
'<div id="container" />' +
'<div id="template" test_id="">' +
'<div class="remove_button" />' +
'</div>' +
'<div id="add_button" />'
);
// Create the container and configure it
// to use the stub container item.
container = createContainer();
});
it("adds and removes items", function() {
// Initially, there should be no items
expect(container.getItemValues()).toEqual([]);
// Add an item
container.add();
expect(container.getItemValues()).toEqual([
{ id: 0 }
]);
// Add a second item
container.add();
expect(container.getItemValues()).toEqual([
{ id: 0 },
{ id: 1 }
]);
// Add a third item
container.add();
expect(container.getItemValues()).toEqual([
{ id: 0 },
{ id: 1 },
{ id: 2 }
]);
// Remove the second item
container.remove(container.getItemElement(1));
expect(container.getItemValues()).toEqual([
{ id: 0 },
{ id: 2 },
]);
// Remove the first item
container.remove(container.getItemElement(0));
expect(container.getItemValues()).toEqual([
{ id: 2 },
]);
// Remove the last item
container.remove(container.getItemElement(0));
expect(container.getItemValues()).toEqual([]);
});
it("ignores unrecognized DOM elements", function() {
// Add some items to the container
container.add();
container.add();
expect(container.getItemValues().length).toEqual(2);
// Add an extra element to the container in the DOM
$("<p>Not a container item!</p>").appendTo("#parent_element");
// Expect the count to remain the same
expect(container.getItemValues().length).toEqual(2);
// Add another element
container.add();
expect(container.getItemValues().length).toEqual(3);
// Remove the first element
container.remove(container.getItemElement(0));
expect(container.getItemValues().length).toEqual(2);
});
it("adds an element when the add button is pressed", function() {
// Press the add button
expect(container.getItemValues().length).toEqual(0);
$("#add_button").click();
expect(container.getItemValues().length).toEqual(1);
});
it("removes an element when the remove button is pressed", function() {
// Add some items
container.add();
container.add();
container.add();
expect(container.getItemValues().length).toEqual(3);
// Press the button to delete the second item
$(".remove_button", container.getItemElement(1)).click();
expect(container.getItemValues().length).toEqual(2);
expect(container.getItemValues()).toEqual([
{ id: 0 },
{ id: 2 }
]);
});
it("configures remove buttons for pre-existing items", function() {
// Add an item directly to the container element in the DOM,
// before initializing the container object.
$("#container").append(
'<div class="test_item" test_id="0">' +
'<div class="remove_button" />' +
'<div>'
);
// Initialize the container object
container = createContainer();
// Verify that the container recognizes the pre-existing item
expect(container.getItemValues()).toEqual([{ id: 0 }]);
// Expect that we can click the "remove" button
// to remove the item.
$(".remove_button", container.getItemElement(0)).click();
expect(container.getItemValues().length).toEqual(0);
});
});
\ No newline at end of file
......@@ -57,23 +57,6 @@ describe("OpenAssessment.StudioView", function() {
view = new OpenAssessment.StudioView(runtime, el, server);
});
it("saves the editor context definition", function() {
// Update the context
view.settingsFieldSelectors.titleField.val('THIS IS THE NEW TITLE');
view.settingsFieldSelectors.submissionStartField.val('2014-01-01');
// Save the updated editor definition
view.save();
// Expect the saving notification to start/end
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'start'});
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'end'});
// Expect the server's context to have been updated
expect(server.receivedData.title).toEqual('THIS IS THE NEW TITLE');
expect(server.receivedData.submissionStart).toEqual('2014-01-01T00:00:00.000Z');
});
it("confirms changes for a released problem", function() {
// Simulate an XBlock that has been released
server.isReleased = true;
......
/**
Tests for assessment editing views.
**/
describe("OpenAssessment edit assessment views", function() {
var testEnableAndDisable = function(view) {
view.isEnabled(false);
expect(view.isEnabled()).toBe(false);
view.isEnabled(true);
expect(view.isEnabled()).toBe(true);
};
var testLoadXMLExamples = function(view) {
var xml = "XML DEFINITIONS WOULD BE HERE";
view.exampleDefinitions(xml);
expect(view.description()).toEqual({ examples: xml });
};
beforeEach(function() {
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html');
});
describe("OpenAssessment.EditPeerAssessmentView", function() {
var view = null;
beforeEach(function() {
var element = $("#oa_peer_assessment_editor").get(0);
view = new OpenAssessment.EditPeerAssessmentView(element);
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() {
view.mustGradeNum(1);
view.mustBeGradedByNum(2);
view.startDatetime("2014-01-01T00:00");
view.dueDatetime("2014-03-04T00:00");
expect(view.description()).toEqual({
must_grade: 1,
must_be_graded_by: 2,
start: "2014-01-01T00:00",
due: "2014-03-04T00:00"
});
});
it("Handles default dates", function() {
view.startDatetime("");
view.dueDatetime("");
expect(view.description().start).toBe(null);
expect(view.description().due).toBe(null);
});
});
describe("OpenAssessment.EditSelfAssessmentView", function() {
var view = null;
beforeEach(function() {
var element = $("#oa_self_assessment_editor").get(0);
view = new OpenAssessment.EditSelfAssessmentView(element);
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() {
view.startDatetime("2014-01-01T00:00");
view.dueDatetime("2014-03-04T00:00");
expect(view.description()).toEqual({
start: "2014-01-01T00:00",
due: "2014-03-04T00:00"
});
});
it("Handles default dates", function() {
view.startDatetime("");
view.dueDatetime("");
expect(view.description().start).toBe(null);
expect(view.description().due).toBe(null);
});
});
describe("OpenAssessment.EditStudentTrainingView", function() {
var view = null;
beforeEach(function() {
var element = $("#oa_student_training_editor").get(0);
view = new OpenAssessment.EditStudentTrainingView(element);
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() { testLoadXMLExamples(view); });
});
describe("OpenAssessment.EditExampleBasedAssessmentView", function() {
var view = null;
beforeEach(function() {
var element = $("#oa_ai_assessment_editor").get(0);
view = new OpenAssessment.EditExampleBasedAssessmentView(element);
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() { testLoadXMLExamples(view); });
});
});
\ No newline at end of file
/**
Tests for OpenAssessment prompt editing view.
**/
describe("OpenAssessment.EditPromptView", function() {
var view = null;
beforeEach(function() {
// Load the DOM fixture
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html');
// Create the view
var element = $("#oa_prompt_editor_wrapper").get(0);
view = new OpenAssessment.EditPromptView(element);
});
it("sets and loads prompt text", function() {
view.promptText("");
expect(view.promptText()).toEqual("");
view.promptText("This is a test prompt!");
expect(view.promptText()).toEqual("This is a test prompt!");
});
});
\ No newline at end of file
/**
Tests for the rubric editing view.
**/
describe("OpenAssessment.EditRubricView", function() {
var view = null;
beforeEach(function() {
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html');
var el = $("#oa_rubric_editor_wrapper").get(0);
view = new OpenAssessment.EditRubricView(el);
});
it("reads a criteria definition from the editor", function() {
// This assumes a particular structure of the DOM,
// which is set by the HTML fixture.
var criteria = view.criteriaDefinition();
expect(criteria.length).toEqual(3);
// Criterion with two options, feedback disabled
expect(criteria[0]).toEqual({
name: "Criterion with two options",
prompt: "Prompt for criterion with two options",
order_num: 0,
feedback: "disabled",
options: [
{
order_num: 0,
points: 1,
name: "Fair",
explanation: "Fair explanation"
},
{
order_num: 1,
points: 2,
name: "Good",
explanation: "Good explanation"
}
],
});
// Criterion with no options, feedback required
expect(criteria[1]).toEqual({
name: "Criterion with no options",
prompt: "Prompt for criterion with no options",
order_num: 1,
feedback: "required",
options: []
});
// Criterion with one option, feeback optional
expect(criteria[2]).toEqual({
name: "Criterion with optional feedback",
prompt: "Prompt for criterion with optional feedback",
order_num: 2,
feedback: "optional",
options: [
{
order_num: 0,
points: 2,
name: "Good",
explanation: "Good explanation"
}
]
});
});
it("reads the feedback prompt from the editor", function() {
view.feedbackPrompt("");
expect(view.feedbackPrompt()).toEqual("");
var prompt = "How do you think the student did overall?";
view.feedbackPrompt(prompt);
expect(view.feedbackPrompt()).toEqual(prompt);
});
});
\ No newline at end of file
/**
Tests for the edit settings view.
**/
describe("OpenAssessment.EditSettingsView", function() {
var StubView = function(name, descriptionText) {
this.name = name;
this.description = function() {
return { dummy: descriptionText };
};
var _enabled = true;
this.isEnabled = function(isEnabled) {
if (typeof(isEnabled) !== "undefined") { this._enabled = isEnabled; }
return this._enabled;
};
};
var view = null;
var assessmentViews = null;
beforeEach(function() {
// Load the DOM fixture
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html');
// Create the stub assessment views
assessmentViews = [
new StubView("self-assessment", "Self assessment description"),
new StubView("peer-assessment", "Peer assessment description")
];
// Create the view
var element = $("#oa_basic_settings_editor").get(0);
view = new OpenAssessment.EditSettingsView(element, assessmentViews);
});
it("sets and loads display name", function() {
view.displayName("");
expect(view.displayName()).toEqual("");
view.displayName("This is the name of the problem!");
expect(view.displayName()).toEqual("This is the name of the problem!");
});
it("sets and loads the submission start/due dates", function() {
view.submissionStart("");
expect(view.submissionStart()).toBe(null);
view.submissionStart("2014-04-01T00:00.0000Z");
expect(view.submissionStart()).toEqual("2014-04-01T00:00.0000Z");
view.submissionDue("");
expect(view.submissionDue()).toBe(null);
view.submissionDue("2014-05-02T00:00.0000Z");
expect(view.submissionDue()).toEqual("2014-05-02T00:00.0000Z");
});
it("sets and loads the image enabled state", function() {
view.imageSubmissionEnabled(true);
expect(view.imageSubmissionEnabled()).toBe(true);
view.imageSubmissionEnabled(false);
expect(view.imageSubmissionEnabled()).toBe(false);
});
it("builds a description of enabled assessments", function() {
// Disable all assessments, and expect an empty description
assessmentViews[0].isEnabled(false);
assessmentViews[1].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([]);
// Enable the first assessment only
assessmentViews[0].isEnabled(true);
assessmentViews[1].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([
{
name: "self-assessment",
dummy: "Self assessment description"
}
]);
// Enable the second assessment only
assessmentViews[0].isEnabled(false);
assessmentViews[1].isEnabled(true);
expect(view.assessmentsDescription()).toEqual([
{
name: "peer-assessment",
dummy: "Peer assessment description"
}
]);
// Enable both assessments
assessmentViews[0].isEnabled(true);
assessmentViews[1].isEnabled(true);
expect(view.assessmentsDescription()).toEqual([
{
name: "self-assessment",
dummy: "Self assessment description"
},
{
name: "peer-assessment",
dummy: "Peer assessment description"
}
]);
});
});
/**
Container that handles addition / deletion of arbitrary items.
An item is any object that has a `getFieldValues()` method,
which should return a JSON-serializable representation
of the item.
Containers copy "template" elements to create new items.
For example, to create a container for an item called "test_item",
the DOM should look something like:
<div id="test_container" />
<div id="test_item_template">
<div class="test_item_remove_button">Remove</div>
<p>This is the default value for the item.</p>
</div>
<div id="test_item_add_button">Add</div>
You can then initialize the container:
>>> var container = $("#test_container").get(0);
>>> var template = $("#test_item_template").get(0);
>>> var addButton = $("#test_item_add_button").get(0);
>>>
>>> container = OpenAssessment.Container(
>>> ContainerItem, {
>>> containerElement: container,
>>> templateElement: template,
>>> addButtonElement: addButton,
>>> removeButtonClass: "test_item_remove_button"
>>> containerItemClass: "test_item"
>>> }
>>> );
The container is responsible for initializing the "add" and "remove" buttons,
including for pre-existing items in the container element.
Templates elements are never deleted, so they should be hidden using CSS styles.
Args:
containerItem (object): The container item object used to access
the contents of items in the container.
Kwargs:
containerElement (DOM element): The element representing the container.
templateElement (DOM element): The element containing the template for creating new items.
addButtonElement (DOM element): The element of the button used to add new items to the container.
removeButtonClass (string): The CSS class of the button that removes an item from the container.
There may be one of these for each item in the container.
containerItemClass (string): The CSS class of items in the container.
New items will be assigned this class.
**/
OpenAssessment.Container = function(containerItem, kwargs) {
this.containerItem = containerItem;
this.containerElement = kwargs.containerElement;
this.templateElement = kwargs.templateElement;
this.addButtonElement = kwargs.addButtonElement;
this.removeButtonClass = kwargs.removeButtonClass;
this.containerItemClass = kwargs.containerItemClass;
// Install a click handler for the add button
$(this.addButtonElement).click($.proxy(this.add, this));
// Find items already in the container and install click
// handlers for the delete buttons.
var container = this;
$("." + this.removeButtonClass, this.containerElement).click(
function(eventData) { container.remove(eventData.target); }
);
// Initialize existing items, in case they need to install their
// own event handlers.
$("." + this.containerItemClass, this.containerElement).each(
function(index, element) { new container.containerItem(element); }
);
};
OpenAssessment.Container.prototype = {
/**
Adds a new item to the container.
**/
add: function() {
// Copy the template into the container
// Remove any CSS IDs (since now the element is not unique)
// and add the item class so we can find it later.
$(this.templateElement)
.clone()
.removeAttr('id')
.toggleClass('is--hidden', false)
.toggleClass(this.containerItemClass, true)
.appendTo($(this.containerElement));
// Install a click handler for the delete button
// Since we just added the new element to the container,
// it should be the last one.
var container = this;
var containerItem = $("." + this.containerItemClass, this.containerElement).last()
containerItem.find('.' + this.removeButtonClass)
.click(function(eventData) { container.remove(eventData.target); } );
// Initialize the item, allowing it to install event handlers.
new this.containerItem(containerItem.get(0));
},
/**
Remove the item associated with an element.
If the element is not itself an item, traverse up the
DOM tree until an item is found.
Args:
element (DOM element): An element representing the container item
or an element within the container item.
**/
remove: function(element) {
$(element).closest("." + this.containerItemClass).remove();
},
/**
Retrieves the values that each container defines for itself, in the order in which they are
presented in the DOM.
Returns:
array: The values representing each container item.
**/
getItemValues: function () {
var values = [];
var container = this;
$("." + this.containerItemClass, this.containerElement).each(
function(index, element) {
var containerItem = new container.containerItem(element);
var fieldValues = containerItem.getFieldValues();
values.push(fieldValues);
}
);
return values;
},
/**
Retrieve the element representing an item in this container.
Args:
index (int): The index of the item, starting from 0.
Returns:
DOM element if the item is found, otherwise null.
**/
getItemElement: function(index) {
var element = $("." + this.containerItemClass, this.containerElement).get(index);
return (element !== undefined) ? element : null;
},
};
/**
The RubricOption Class used to construct and maintain references to rubric options from within an options
container object. Constructs a new RubricOption element.
Args:
element (OpenAssessment.Container): The container that the option is a member of.
Returns:
OpenAssessment.RubricOption
**/
OpenAssessment.RubricOption = function(element) {
this.element = element;
};
OpenAssessment.RubricOption.prototype = {
/**
Finds the values currently entered in the Option's fields, and returns them.
Returns:
object literal of the form:
{
'name': 'Real Bad',
'points': 1,
'explanation': 'Essay was primarily composed of emojis.'
}
**/
getFieldValues: function () {
return {
name: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_option_name', this.element)
),
points: OpenAssessment.Fields.intField(
$('.openassessment_criterion_option_points', this.element)
),
explanation: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_option_explanation', this.element)
)
};
}
};
/**
The RubricCriterion Class is used to construct and get information from a rubric element within
the DOM.
Args:
element (JQuery Object): The selection which describes the scope of the criterion.
Returns:
OpenAssessment.RubricCriterion
**/
OpenAssessment.RubricCriterion = function(element) {
this.element = element;
this.optionContainer = new OpenAssessment.Container(
OpenAssessment.RubricOption, {
containerElement: $(".openassessment_criterion_option_list", this.element).get(0),
templateElement: $("#openassessment_option_template").get(0),
addButtonElement: $(".openassessment_criterion_add_option", this.element).get(0),
removeButtonClass: "openassessment_rubric_remove_button",
containerItemClass: "openassessment_criterion_option",
}
);
};
OpenAssessment.RubricCriterion.prototype = {
/**
Finds the values currently entered in the Criterion's fields, and returns them.
Returns:
object literal of the form:
{
'name': 'Emoji Content',
'prompt': 'How expressive was the author with their words, and how much did they rely on emojis?',
'feedback': 'optional',
'options': [
{
'name': 'Real Bad',
'points': 1,
'explanation': 'Essay was primarily composed of emojis.'
}
...
]
}
**/
getFieldValues: function () {
return {
name: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_name', this.element)
),
prompt: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_prompt', this.element)
),
feedback: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_feedback', this.element)
),
options: this.optionContainer.getItemValues()
};
}
};
\ No newline at end of file
......@@ -9,109 +9,75 @@
Returns:
OpenAssessment.StudioView
**/
**/
OpenAssessment.StudioView = function(runtime, element, server) {
this.element = element;
this.runtime = runtime;
this.server = server;
this.liveElement = $(element);
var liveElement = this.liveElement;
// Resize the editing modal
this.fixModalHeight();
// Instantiates JQuery selector variables which will allow manipulation and display controls.
this.settingsFieldSelectors = {
promptBox: $('#openassessment_prompt_editor', liveElement),
titleField: $('#openassessment_title_editor', liveElement),
submissionStartField: $('#openassessment_submission_start_editor', liveElement),
submissionDueField: $('#openassessment_submission_due_editor', liveElement),
hasPeer: $('#include_peer_assessment', liveElement),
hasSelf: $('#include_self_assessment', liveElement),
hasAI: $('#include_ai_assessment', liveElement),
hasTraining: $('#include_student_training', liveElement),
peerMustGrade: $('#peer_assessment_must_grade', liveElement),
peerGradedBy: $('#peer_assessment_graded_by', liveElement),
peerStart: $('#peer_assessment_start_date', liveElement),
peerDue: $('#peer_assessment_due_date', liveElement),
selfStart: $('#self_assessment_start_date', liveElement),
selfDue: $('#self_assessment_due_date', liveElement)
};
// Captures the HTML definition of the original criterion element. This will be the template
// used for all other criterion creations
var criterionHtml = $("#openassessment_criterion_1", liveElement).parent().html();
// Replaces all instances of the original ID (1) with the new fake ID in the string
// representation of the Criterion LI. This is our new template, with a C-C-C marker to replace.
this.criterionHtmlTemplate = criterionHtml.replace(new RegExp("1", "g"), "C-C-C");
// Captures the HTML definition of the original option element. Note that there are TWO template
// tags that need to be replaced "C-C-C" for the Criterion ID, and "O-O-O" for the option ID.
var optionHtml = $("#openassessment_criterion_1_option_1", liveElement).parent().html();
var criteriaReplaced = optionHtml.replace(new RegExp("criterion_1", "g"), "criterion_C-C-C");
this.optionHtmlTemplate = criteriaReplaced.replace(new RegExp("option_1", "g"), "option_O-O-O");
// Start us off with an empty setup, and uses the adding method to add a criteria (which in turn will
// add an option). This design choice was made to ensure consistent practices in adding and removing,
// the logic of which is all maintained in the function calls.
this.numberOfCriteria = 0;
this.numberOfOptions = [];
this.rubricCriteriaSelectors = [];
this.rubricFeedbackPrompt = $('#openassessment_rubric_feedback', liveElement);
$('#openassessment_criterion_list', liveElement).empty();
this.addNewCriterionToRubric();
var view = this;
// Installs the save and cancel buttons
$('.openassessment_save_button', liveElement) .click( function (eventData) {
view.save();
});
$('.openassessment_cancel_button', liveElement) .click( function (eventData) {
view.cancel();
});
// Adds the tabbing functionality
$('.openassessment_editor_content_and_tabs', liveElement) .tabs();
// Installs all of the checkbox listeners in the settings tab
view.addSettingsAssessmentCheckboxListener("ai_assessment", liveElement);
view.addSettingsAssessmentCheckboxListener("self_assessment", liveElement);
view.addSettingsAssessmentCheckboxListener("peer_assessment", liveElement);
view.addSettingsAssessmentCheckboxListener("student_training", liveElement);
$('#openassessment_rubric_add_criterion', liveElement) .click( function (eventData) {
view.addNewCriterionToRubric(liveElement);
});
// Initialize the tabs
$(".openassessment_editor_content_and_tabs", this.element).tabs();
// Initialize the prompt tab view
this.promptView = new OpenAssessment.EditPromptView(
$("#oa_prompt_editor_wrapper", this.element).get(0)
);
// Initialize the settings tab view
this.settingsView = new OpenAssessment.EditSettingsView(
$("#oa_basic_settings_editor", this.element).get(0), [
new OpenAssessment.EditPeerAssessmentView(
$("#oa_peer_assessment_editor", this.element).get(0)
),
new OpenAssessment.EditSelfAssessmentView(
$("#oa_self_assessment_editor", this.element).get(0)
),
new OpenAssessment.EditStudentTrainingView(
$("#oa_student_training_editor", this.element).get(0)
),
new OpenAssessment.EditExampleBasedAssessmentView(
$("#oa_ai_assessment_editor", this.element).get(0)
)
]
);
// Initialize the rubric tab view
this.rubricView = new OpenAssessment.EditRubricView(
$("#oa_rubric_editor_wrapper", this.element).get(0)
);
// Install the save and cancel buttons
$(".openassessment_save_button", this.element).click($.proxy(this.save, this));
$(".openassessment_cancel_button", this.element).click($.proxy(this.cancel, this));
};
OpenAssessment.StudioView.prototype = {
/**
Adjusts the modal's height, position and padding to be larger for OA editing only (Does not impact other modals)
*/
Adjusts the modal's height, position and padding to be larger for OA editing only (Does not impact other modals)
**/
fixModalHeight: function () {
var element = this.liveElement;
element.toggleClass('openassessment_full_height');
element.parentsUntil('.modal-window').toggleClass('openassessment_full_height');
$('.modal-window').toggleClass('openassessment_modal_window');
// Add the full height class to every element from the XBlock
// to the modal window in Studio.
$(this.element)
.toggleClass('openassessment_full_height', true)
.parentsUntil('.modal-window')
.toggleClass('openassessment_full_height', true);
// Add the modal window class to the modal window
$(this.element)
.closest('.modal-window')
.toggleClass('openassessment_modal_window', true);
},
/**
Load the XBlock XML definition from the server and display it in the view.
**/
load: function () {
var view = this;
},
/**
Save the problem's XML definition to the server.
If the problem has been released, make the user confirm the save.
**/
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;
......@@ -127,333 +93,54 @@ OpenAssessment.StudioView.prototype = {
}
}
).fail(function (errMsg) {
view.showError(errMsg);
view.showError(errMsg);
});
},
/**
Construct checkbox listeners for all of our assessment modules
Make the user confirm that he/she wants to update a problem
that has already been released.
Args:
name (string): name of assessment module to install listener on
liveElement (DOM element): the live DOM selector
*/
addSettingsAssessmentCheckboxListener: function (name, liveElement) {
$("#include_" + name , liveElement) .change(function () {
$("#" + name + "_description_closed", liveElement).toggleClass('is--hidden', this.checked);
$("#" + name + "_settings_editor", liveElement).toggleClass('is--hidden', !this.checked);
});
},
/**
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.
**/
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();
}
},
/**
Adds a new criterion to the rubric.
*/
addNewCriterionToRubric: function (){
var view = this;
var liveElement = this.liveElement;
// Always appends the new criterion to the end of the list, and we force linear ordering.
var newCriterionID = this.numberOfCriteria + 1;
this.numberOfCriteria += 1;
this.numberOfOptions[newCriterionID] = 0;
// Fills in the template with the new criterion ID
var criterionHtml = this.criterionHtmlTemplate.replace(new RegExp('C-C-C', 'g'), '' + newCriterionID);
// Adds the new criterion to the DOM
$("#openassessment_criterion_list", liveElement).append(criterionHtml);
// Now that we have altered our LiveElement, we need to "reset" it so that it recognizes the new criterion.
liveElement = $("#openassessment_criterion_" + newCriterionID);
$(".openassessment_criterion_option_list", liveElement).empty();
// Adds our selector elements for easy future access.
view.rubricCriteriaSelectors[newCriterionID] = {
criterion: liveElement,
name: $('.openassessment_criterion_name', liveElement).first(),
prompt: $('.openassessment_criterion_prompt', liveElement).first(),
options: [],
feedback: 'disabled'
};
// Defaults to no feedback
$('input:radio[value=disabled]', liveElement).prop('checked', true);
view.addNewOptionToCriterion(liveElement, newCriterionID);
// Adds a listener that will collapse/expand the criterion on click.
$('#openassessment_display_criterion_' + newCriterionID, liveElement) .change( function () {
if (this.checked){
$('#openassessment_criterion_body_' + newCriterionID, liveElement).fadeIn();
} else {
$('#openassessment_criterion_body_' + newCriterionID, liveElement).fadeOut();
}
});
// Adds a listener which will delete the criterion on a click of the remove button
// The methodology for deletion is to shift all information from previous elements down into
$("#openassessment_criterion_" + newCriterionID + "_remove", liveElement) .click( function (eventData) {
view.removeCriterionFromRubric(newCriterionID);
});
// Adds a listener which will add another option to the Criterion's definition.
$("#openassessment_criterion_" + newCriterionID + "_add_option", liveElement).click( function (eventData) {
view.addNewOptionToCriterion(liveElement, newCriterionID);
});
// Adds a listener which removes criterion feedback
$(".openassessment_feedback_remove_button", liveElement). click( function(eventData){
$(".openassessment_criterion_feedback_direction", liveElement).fadeOut();
$(".openassessment_criterion_feedback_header_open", liveElement).fadeOut();
$(".openassessment_criterion_feedback_header_closed", liveElement).fadeIn();
$(".openassessment_feedback_remove_button", liveElement).fadeOut();
view.rubricCriteriaSelectors[newCriterionID].hasFeedback = false;
});
// Adds a listener which adds criterion feedback if not already displayed.
$(".openassessment_criterion_feedback_header_closed", liveElement).click( function (eventData){
$(".openassessment_criterion_feedback_direction", liveElement).fadeIn();
$(".openassessment_criterion_feedback_header_open", liveElement).fadeIn();
$(".openassessment_criterion_feedback_header_closed", liveElement).fadeOut();
$(".openassessment_feedback_remove_button", liveElement).fadeIn();
view.rubricCriteriaSelectors[newCriterionID].hasFeedback = true;
});
// Hides the criterion header used for adding
$(".openassessment_criterion_feedback_header_closed", liveElement).hide();
},
/**
Removes a specified criterion from the problem's rubric definition.
Changes are made in the DOM, in support/control structures.
*/
removeCriterionFromRubric: function(criterionToRemove){
var view = this;
var numCriteria = view.numberOfCriteria;
var selectors = view.rubricCriteriaSelectors;
// Shifts all data from "higher up" criteria down one in order to allow us to delete the last
// element without deleting information input by the user
for (var i = criterionToRemove; i < numCriteria; i++){
// Shifts all criterion field values
selectors[i].name.prop('value', selectors[i+1].name.prop('value'));
selectors[i].prompt.prop('value', selectors[i+1].prompt.prop('value'));
selectors[i].feedback = selectors[i+1].feedback;
$('input:radio[value="disabled"]', selectors[i].criterion).prop('checked', true);
// Ensures that we won't delete information during the shift by ensuring that the option lists are of the
//same length. Note it doesn't matter what we add or delete, simply that the lengths add up.
while (view.numberOfOptions[i] < view.numberOfOptions[i+1]){
view.addNewOptionToCriterion(selectors[i].criteria, i);
}
while (view.numberOfOptions[i] > view.numberOfOptions[i+1]){
view.removeOptionFromCriterion(selectors[i].criteria, i, 1);
}
// Transfers all data from each option to the next within a criterion.
var options1 = selectors[i].options;
var options2 = selectors[i+1].options;
var numOptions2 = view.numberOfOptions[i+1];
for (var j = 1; j < numOptions2; j++){
options1[j].points.prop('value', options2[j].points.prop('value'));
options1[j].name.prop('value', options2[j].name.prop('value'));
options1[j].explanation.prop('value', options2[j].explanation.prop('value'));
}
}
// Physically removes the rubric criteria from the DOM
view.rubricCriteriaSelectors[view.rubricCriteriaSelectors.length - 1].criterion.remove();
// Deletes the criteria from our three tracking statistics/structures
view.rubricCriteriaSelectors = view.rubricCriteriaSelectors.slice(0,numCriteria);
view.numberOfOptions = view.numberOfOptions.slice(0, numCriteria);
view.numberOfCriteria -= 1;
if (confirm(msg)) { onConfirm(); }
},
/**
Initializes a new option for a given criterion. This code block dictates the methodology for
adding and removing options to a rubric.
Args:
liveElement (DOM element): An element containing the criterion interface in the DOM.
criterionID (string): The specific number of the criterion the option is being added to
*/
addNewOptionToCriterion: function (liveElement, criterionID){
var view = this;
// Finds the ID that will be associated with the option (it will be added to the end)
var newOptionID = this.numberOfOptions[criterionID] + 1;
this.numberOfOptions[criterionID] += 1;
// Replaces the template values with the true criterion and option ID's, and appends it on to the DOM
var optionHtml = this.optionHtmlTemplate;
optionHtml = optionHtml.replace(new RegExp("C-C-C", 'g'), "" + criterionID);
optionHtml = optionHtml.replace(new RegExp("O-O-O", 'g'), "" + newOptionID);
$("#openassessment_criterion_" + criterionID + "_options", liveElement).append(optionHtml);
// Resets the Live Element to be the updated (and newly created) option.
liveElement = $("#openassessment_criterion_" + criterionID + "_option_" + newOptionID);
// Constructs and assigns a dictionary of all of the selectors we store for each option.
view.rubricCriteriaSelectors[criterionID].options[newOptionID] = {
option: liveElement,
points: $("#openassessment_criterion_" + criterionID + "_option_" + newOptionID + "_points", liveElement),
name: $("#openassessment_criterion_" + criterionID + "_option_" + newOptionID + "_name", liveElement),
explanation: $("#openassessment_criterion_" + criterionID + "_option_" + newOptionID + "_explanation", liveElement)
};
// Sets the remove behavior. When deleted, an option will shift all of the data "behind" it on the list
// of options toward itself, and then deletes the last element in the list. This ensures that our options
// are always increasing by one, and that the data doesn't remain tethered to where it was entered.
$("#openassessment_criterion_" + criterionID + "_option_" + newOptionID + "_remove", liveElement).click(
function(eventData){
view.removeOptionFromCriterion(liveElement, criterionID, newOptionID);
}
);
},
/**
Removes a specified element from the DOM and from all tracking data. Note that no action is
taken against the specified element, rather, data is shifted down the chain (to construct the
illusion that the specified element was deleted), and then the last element is actually deleted.
Args:
liveElement (DOM element): An element containing the criterion interface in the DOM.
criterionID (string): The criterion ID that we are deleting from
optionToRemove (string): The option ID that we are "deleting"
*/
removeOptionFromCriterion: function(liveElement, criterionID, optionToRemove) {
var view = this;
var numberOfOptions = view.numberOfOptions[criterionID];
var optionSelectors = view.rubricCriteriaSelectors[criterionID].options;
// Shifts all data down, then deletes the last element, to create the appearance we deleted the given
// elements.
for (var i = optionToRemove; i < numberOfOptions; i++){
// Utilizes stored selectors to perform the swaps.
optionSelectors[i].points.prop('value', optionSelectors[i+1].points.prop('value'));
optionSelectors[i].name.prop('value', optionSelectors[i+1].name.prop('value'));
optionSelectors[i].explanation.prop('value', optionSelectors[i+1].explanation.prop('value'));
}
optionSelectors[optionSelectors.length - 1].option.remove();
view.rubricCriteriaSelectors[criterionID].options =
view.rubricCriteriaSelectors[criterionID].options.slice(0, (optionSelectors.length - 1));
view.numberOfOptions[criterionID] -= 1;
},
/**
Save the updated XML definition to the server.
**/
Save the updated problem definition to the server.
**/
updateEditorContext: 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'});
// Grabs values from all of our fields, and stores them in a format which can be easily validated.
var rubricCriteria = [];
for (var i = 1; i <= this.numberOfCriteria; i++){
var selectorDict = this.rubricCriteriaSelectors[i];
var criterionValueDict = {
order_num: i - 1,
name: selectorDict.name.prop('value'),
prompt: selectorDict.prompt.prop('value'),
feedback: $("#openassessment_criterion_" + i + "_feedback").val()
};
var optionSelectorList = selectorDict.options;
var optionValueList = [];
for (var j = 1; j <= this.numberOfOptions[i]; j++){
var optionSelectors = optionSelectorList[j];
optionValueList = optionValueList.concat([{
order_num: j-1,
points: this._getInt(optionSelectors.points),
name: optionSelectors.name.val(),
explanation: optionSelectors.explanation.val()
}]);
}
criterionValueDict.options = optionValueList;
rubricCriteria = rubricCriteria.concat([criterionValueDict]);
}
var assessments = [];
if (this.settingsFieldSelectors.hasTraining.prop('checked')){
assessments.push({
name: "student-training",
examples: this.studentTrainingExamplesCodeBox.getValue()
});
}
if (this.settingsFieldSelectors.hasPeer.prop('checked')) {
assessments.push({
name: "peer-assessment",
must_grade: this._getInt(this.settingsFieldSelectors.peerMustGrade),
must_be_graded_by: this._getInt(this.settingsFieldSelectors.peerGradedBy),
start: this._getDateTime(this.settingsFieldSelectors.peerStart),
due: this._getDateTime(this.settingsFieldSelectors.peerDue)
});
}
if (this.settingsFieldSelectors.hasSelf.prop('checked')) {
assessments.push({
name: "self-assessment",
start: this._getDateTime(this.settingsFieldSelectors.selfStart),
due: this._getDateTime(this.settingsFieldSelectors.selfDue)
});
}
if (this.settingsFieldSelectors.hasAI.prop('checked')) {
assessments.push({
name: "example-based-assessment",
examples: this.aiTrainingExamplesCodeBox.getValue()
});
}
var view = this;
this.server.updateEditorContext({
title: this.settingsFieldSelectors.titleField.val(),
prompt: this.settingsFieldSelectors.promptBox.val(),
feedbackPrompt: this.rubricFeedbackPrompt.val(),
submissionStart: this._getDateTime(this.settingsFieldSelectors.submissionStartField),
submissionDue: this._getDateTime(this.settingsFieldSelectors.submissionDueField),
criteria: rubricCriteria,
assessments: assessments
prompt: view.promptView.promptText(),
feedbackPrompt: view.rubricView.feedbackPrompt(),
criteria: view.rubricView.criteriaDefinition(),
title: view.settingsView.displayName(),
submissionStart: view.settingsView.submissionStart(),
submissionDue: view.settingsView.submissionDue(),
assessments: view.settingsView.assessmentsDescription()
}).done(
function () {
// Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification.
// Then reload the view.
view.runtime.notify('save', {state: 'end'});
}).fail(function (msg) {
view.showError(msg);
});
// Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification.
// Then reload the view.
function () { view.runtime.notify('save', {state: 'end'}); }
).fail(
function (msg) { view.showError(msg); }
);
},
/**
Cancel editing.
**/
Cancel editing.
**/
cancel: function () {
// Notify the client-side runtime so it will close the editing modal.
this.runtime.notify('cancel', {});
......@@ -468,54 +155,6 @@ OpenAssessment.StudioView.prototype = {
showError: function (errorMsg) {
this.runtime.notify('error', {msg: errorMsg});
},
/**
Retrieve a value from a datetime input.
Args:
selector: The JQuery selector for the datetime input.
Returns:
ISO-formatted datetime string or null
**/
_getDateTime: function(selector) {
var dateStr = selector.val();
// By convention, empty date strings are null,
// meaning choose the default date based on
// other dates set in the problem configuration.
if (dateStr === "") {
return null;
}
// Attempt to parse the date string
// TO DO: currently invalid dates also are set as null,
// which is probably NOT what the user wants!
// We should add proper validation here.
var timestamp = Date.parse(dateStr);
if (isNaN(timestamp)) {
return null;
}
// Send the datetime in ISO format
// This will also convert the timezone to UTC
return new Date(timestamp).toISOString();
},
/**
Retrieve an integer value from an input.
Args:
selector: The JQuery selector for the input.
Returns:
int
**/
_getInt: function(selector) {
return parseInt(selector.val(), 10);
}
};
......@@ -527,5 +166,4 @@ function OpenAssessmentEditor(runtime, element) {
**/
var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.StudioView(runtime, element, server);
view.load();
}
/**
Show and hide elements based on a checkbox.
Args:
element (DOM element): The parent element, used to scope the selectors.
hiddenSelector (string): The CSS selector string for elements
to show when the checkbox is in the "off" state.
shownSelector (string): The CSS selector string for elements
to show when the checkbox is in the "on" state.
**/
OpenAssessment.ToggleControl = function(element, hiddenSelector, shownSelector) {
this.element = element;
this.hiddenSelector = hiddenSelector;
this.shownSelector = shownSelector;
};
OpenAssessment.ToggleControl.prototype = {
/**
Install the event handler for the checkbox,
passing in the toggle control object as the event data.
Args:
checkboxSelector (string): The CSS selector string for the checkbox.
Returns:
OpenAssessment.ToggleControl
**/
install: function(checkboxSelector) {
$(checkboxSelector, this.element).change(
this, function(event) {
var control = event.data;
if (this.checked) { control.show(); }
else { control.hide(); }
}
);
return this;
},
show: function() {
$(this.hiddenSelector, this.element).fadeOut('fast');
$(this.shownSelector, this.element).fadeIn();
},
hide: function() {
$(this.hiddenSelector, this.element).fadeIn();
$(this.shownSelector, this.element).fadeOut();
}
};
/**
Interface for editing peer assessment settings.
Args:
element (DOM element): The DOM element representing this view.
Returns:
OpenAssessment.EditPeerAssessmentView
**/
OpenAssessment.EditPeerAssessmentView = function(element) {
this.element = element;
this.name = "peer-assessment";
new OpenAssessment.ToggleControl(
this.element,
"#peer_assessment_description_closed",
"#peer_assessment_settings_editor"
).install("#include_peer_assessment");
};
OpenAssessment.EditPeerAssessmentView.prototype = {
/**
Return a description of the assessment.
Returns:
object literal
Example usage:
>>> editPeerView.description();
{
must_grade: 5,
must_be_graded_by: 2,
start: null,
due: "2014-04-1T00:00"
}
**/
description: function() {
return {
must_grade: this.mustGradeNum(),
must_be_graded_by: this.mustBeGradedByNum(),
start: this.startDatetime(),
due: this.dueDatetime()
};
},
/**
Get or set whether the assessment is enabled.
Args:
isEnabled (boolean, optional): If provided, set the enabled state of the assessment.
Returns:
boolean
***/
isEnabled: function(isEnabled) {
var sel = $("#include_peer_assessment", this.element);
return OpenAssessment.Fields.booleanField(sel, isEnabled);
},
/**
Get or set the required number of submissions a student must peer-assess.
Args:
num (int, optional): If provided, set the required number of assessments.
Returns:
int
**/
mustGradeNum: function(num) {
var sel = $("#peer_assessment_must_grade", this.element);
return OpenAssessment.Fields.intField(sel, num);
},
/**
Get or set the required number of peer-assessments a student must receive.
Args:
num (int, optional): If provided, set the required number of assessments.
Returns:
int
**/
mustBeGradedByNum: function(num) {
var sel = $("#peer_assessment_graded_by", this.element);
return OpenAssessment.Fields.intField(sel, num);
},
/**
Get or set the start date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
Returns:
string (ISO-formatted UTC datetime)
**/
startDatetime: function(datetime) {
var sel = $("#peer_assessment_start_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
/**
Get or set the due date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
Returns:
string (ISO-formatted UTC datetime)
**/
dueDatetime: function(datetime) {
var sel = $("#peer_assessment_due_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
};
/**
Interface for editing self assessment settings.
Args:
element (DOM element): The DOM element representing this view.
Returns:
OpenAssessment.EditSelfAssessmentView
**/
OpenAssessment.EditSelfAssessmentView = function(element) {
this.element = element;
this.name = "self-assessment";
new OpenAssessment.ToggleControl(
this.element,
"#self_assessment_description_closed",
"#self_assessment_settings_editor"
).install("#include_self_assessment");
};
OpenAssessment.EditSelfAssessmentView.prototype = {
/**
Return a description of the assessment.
Returns:
object literal
Example usage:
>>> editSelfView.description();
{
start: null,
due: "2014-04-1T00:00"
}
**/
description: function() {
return {
start: this.startDatetime(),
due: this.dueDatetime()
};
},
/**
Get or set whether the assessment is enabled.
Args:
isEnabled (boolean, optional): If provided, set the enabled state of the assessment.
Returns:
boolean
***/
isEnabled: function(isEnabled) {
var sel = $("#include_self_assessment", this.element);
return OpenAssessment.Fields.booleanField(sel, isEnabled);
},
/**
Get or set the start date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
Returns:
string (ISO-formatted UTC datetime)
**/
startDatetime: function(datetime) {
var sel = $("#self_assessment_start_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
/**
Get or set the due date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
Returns:
string (ISO-formatted UTC datetime)
**/
dueDatetime: function(datetime) {
var sel = $("#self_assessment_due_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
};
/**
Interface for editing self assessment settings.
Args:
element (DOM element): The DOM element representing this view.
Returns:
OpenAssessment.EditStudentTrainingView
**/
OpenAssessment.EditStudentTrainingView = function(element) {
this.element = element;
this.name = "student-training";
new OpenAssessment.ToggleControl(
this.element,
"#student_training_description_closed",
"#student_training_settings_editor"
).install("#include_student_training");
};
OpenAssessment.EditStudentTrainingView.prototype = {
/**
Return a description of the assessment.
Returns:
object literal
Example usage:
>>> editTrainingView.description();
{
examples: "XML DEFINITION HERE"
}
**/
description: function() {
return {
examples: this.exampleDefinitions()
};
},
/**
Get or set whether the assessment is enabled.
Args:
isEnabled (boolean, optional): If provided, set the enabled state of the assessment.
Returns:
boolean
***/
isEnabled: function(isEnabled) {
var sel = $("#include_student_training", this.element);
return OpenAssessment.Fields.booleanField(sel, isEnabled);
},
/**
Get or set the XML defining the training examples.
Args:
xml (string, optional): The XML of the training example definitions.
Returns:
string
**/
exampleDefinitions: function(xml) {
var sel = $("#student_training_examples", this.element);
return OpenAssessment.Fields.stringField(sel, xml);
},
};
/**
Interface for editing example-based assessment settings.
Args:
element (DOM element): The DOM element representing this view.
Returns:
OpenAssessment.EditExampleBasedAssessmentView
**/
OpenAssessment.EditExampleBasedAssessmentView = function(element) {
this.element = element;
this.name = "example-based-assessment";
new OpenAssessment.ToggleControl(
this.element,
"#ai_assessment_description_closed",
"#ai_assessment_settings_editor"
).install("#include_ai_assessment");
};
OpenAssessment.EditExampleBasedAssessmentView.prototype = {
/**
Return a description of the assessment.
Returns:
object literal
Example usage:
>>> editTrainingView.description();
{
examples: "XML DEFINITION HERE"
}
**/
description: function() {
return {
examples: this.exampleDefinitions()
};
},
/**
Get or set whether the assessment is enabled.
Args:
isEnabled (boolean, optional): If provided, set the enabled state of the assessment.
Returns:
boolean
***/
isEnabled: function(isEnabled) {
var sel = $("#include_ai_assessment", this.element);
return OpenAssessment.Fields.booleanField(sel, isEnabled);
},
/**
Get or set the XML defining the training examples.
Args:
xml (string, optional): The XML of the training example definitions.
Returns:
string
**/
exampleDefinitions: function(xml) {
var sel = $("#ai_training_examples", this.element);
return OpenAssessment.Fields.stringField(sel, xml);
},
};
\ No newline at end of file
/**
Utilities for reading / writing fields.
**/
OpenAssessment.Fields = {
stringField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.val(value); }
return sel.val();
},
datetimeField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.val(value); }
var fieldValue = sel.val();
return (fieldValue !== "") ? fieldValue : null;
},
intField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.val(value); }
return parseInt(sel.val(), 10);
},
booleanField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.prop("checked", value); }
return sel.prop("checked");
},
};
/**
Editing interface for the rubric prompt.
Args:
element (DOM element): The DOM element representing this view.
Returns:
OpenAssessment.EditPromptView
**/
OpenAssessment.EditPromptView = function(element) {
this.element = element;
};
OpenAssessment.EditPromptView.prototype = {
/**
Get or set the text of the prompt.
Args:
text (string, optional): If provided, set the text of the prompt.
Returns:
string
**/
promptText: function(text) {
var sel = $('#openassessment_prompt_editor', this.element);
return OpenAssessment.Fields.stringField(sel, text);
},
};
\ No newline at end of file
/**
Interface for editing rubric definitions.
**/
OpenAssessment.EditRubricView = function(element) {
this.element = element;
this.criteriaContainer = new OpenAssessment.Container(
OpenAssessment.RubricCriterion, {
containerElement: $("#openassessment_criterion_list", this.element).get(0),
templateElement: $("#openassessment_criterion_template", this.element).get(0),
addButtonElement: $("#openassessment_rubric_add_criterion", this.element).get(0),
removeButtonClass: "openassessment_rubric_remove_button",
containerItemClass: "openassessment_criterion",
}
);
};
OpenAssessment.EditRubricView.prototype = {
/**
Construct a list of criteria definitions from the editor UI.
Returns:
list of criteria objects
Example usage:
>>> editRubricView.criteriaDefinition();
[
{
name: "Criterion",
prompt: "Prompt",
order_num: 0,
feedback: "disabled",
options: [
{
order_num: 0,
points: 1,
name: "Good",
explanation: "Explanation"
},
...
]
},
...
]
**/
criteriaDefinition: function() {
var criteria = this.criteriaContainer.getItemValues();
// Add order_num fields for criteria and options
for (var criterion_idx = 0; criterion_idx < criteria.length; criterion_idx++) {
var criterion = criteria[criterion_idx];
criterion.order_num = criterion_idx;
for (var option_idx = 0; option_idx < criterion.options.length; option_idx++) {
var option = criterion.options[option_idx];
option.order_num = option_idx;
}
}
return criteria;
},
/**
Get or set the feedback prompt in the editor.
This is the prompt shown to students when giving "overall" feedback
on a submission.
Args:
text (string, optional): If provided, set the feedback prompt to this value.
Returns:
string
**/
feedbackPrompt: function(text) {
var sel = $("#openassessment_rubric_feedback", this.element);
return OpenAssessment.Fields.stringField(sel, text);
}
};
\ No newline at end of file
/**
Editing interface for OpenAssessment settings (including assessments).
Args:
element (DOM element): The DOM element representing this view.
assessmentViews (array): List of assessment view objects.
Returns:
OpenAssessment.EditSettingsView
**/
OpenAssessment.EditSettingsView = function(element, assessmentViews) {
this.element = element;
this.assessmentViews = assessmentViews;
};
OpenAssessment.EditSettingsView.prototype = {
/**
Get or set the display name of the problem.
Args:
name (string, optional): If provided, set the display name.
Returns:
string
**/
displayName: function(name) {
var sel = $("#openassessment_title_editor", this.element);
return OpenAssessment.Fields.stringField(sel, name);
},
/**
Get or set the submission start date.
Args:
datetime (string, optional): If provided, set the datetime.
Returns:
string (ISO-format UTC datetime)
**/
submissionStart: function(datetime) {
var sel = $("#openassessment_submission_start_editor", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
/**
Get or set the submission end date.
Args:
datetime (string, optional): If provided, set the datetime.
Returns:
string (ISO-format UTC datetime)
**/
submissionDue: function(datetime) {
var sel = $("#openassessment_submission_start_editor", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
},
/**
Enable / disable image submission.
Args:
isEnabled (boolean, optional): If provided, enable/disable image submission.
Returns:
boolean
**/
imageSubmissionEnabled: function(isEnabled) {
var sel = $("#openassessment_submission_image_editor", this.element);
if (typeof(isEnabled) !== "undefined") {
if (isEnabled) { sel.val(1); }
else { sel.val(0); }
}
return (sel.val() == 1);
},
/**
Construct a list of enabled assessments and their properties.
Returns:
list of object literals representing the assessments.
Example usage:
>>> editSettingsView.assessmentsDescription()
[
{
name: "peer-assessment",
start: "2014-04-01T00:00",
due: null
must_grade: 5,
must_be_graded_by: 2,
},
{
name: "self-assessment",
start: null,
due: null
}
]
**/
assessmentsDescription: function() {
assessmentDescList = [];
for (var idx in this.assessmentViews) {
var asmntView = this.assessmentViews[idx];
if (asmntView.isEnabled()) {
var description = asmntView.description();
description["name"] = asmntView.name;
assessmentDescList.push(description);
}
}
return assessmentDescList;
},
};
\ No newline at end of file
......@@ -20,6 +20,9 @@ logger = logging.getLogger(__name__)
class StudioMixin(object):
"""
Studio editing view for OpenAssessment XBlock.
"""
DEFAULT_CRITERIA = [
{
......@@ -30,10 +33,6 @@ class StudioMixin(object):
}
]
"""
Studio editing view for OpenAssessment XBlock.
"""
def studio_view(self, context=None):
"""
Render the OpenAssessment XBlock for editing in Studio.
......
#!/usr/bin/env bash
cd `dirname $BASH_SOURCE` && cd ../openassessment/xblock/static
sass --update sass:css --force
sass --update sass:css --force --style compressed
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