Commit a5921b62 by Will Daly

Add field validation for datetime fields and option points.

Remove support in Javascript for "default" (empty) dates and update the template rendering script to support this in the JS tests.
parent cb932343
......@@ -31,7 +31,7 @@
class="openassessment_criterion_option_points input setting-input"
type="number"
value="{{ option_points }}"
min="0"
min="0" max="999"
>
</label>
</div>
......
......@@ -388,7 +388,8 @@
"context": {
"prompt": "How much do you like waffles?",
"title": "The most important of all questions.",
"submission_due": "2014-10-1T10:00:00",
"submission_start": "2014-01-02T12:15",
"submission_due": "2014-10-01T04:53",
"criteria": [
{
"name": "criterion_1",
......@@ -443,13 +444,14 @@
],
"assessments": {
"peer_assessment": {
"start": "",
"due": "",
"start": "2014-01-02T00:00",
"due": "2014-01-03T00:00",
"must_grade": 5,
"must_be_graded_by": 3
},
"self_assessment": {
"due": ""
"start": "2014-01-04T00:00",
"due": "2014-01-05T00:00"
}
},
"editor_assessments_order": [
......@@ -466,6 +468,7 @@
"context": {
"prompt": "Test prompt",
"title": "Test title",
"submission_start": "2014-01-1T10:00:00",
"submission_due": "2014-10-1T10:00:00",
"criteria": [
{
......@@ -588,7 +591,7 @@
}
},
"peer_assessment": {
"start": "",
"start": "2014-01-02T00:00",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
......
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.Logger==="undefined"){window.Logger={log:function(event_type,data,kwargs){}}}if(typeof OpenAssessment.Server=="undefined"||!OpenAssessment.Server){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,criterionFeedback,overallFeedback){var url=this.url("self_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.")])})})},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,editor_assessments_order:kwargs.editorAssessmentsOrder,allow_file_upload:kwargs.imageSubmissionEnabled});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()},getUploadUrl:function(contentType){var url=this.url("upload_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({contentType:contentType})}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("Could not retrieve upload url.")])})}).promise()},getDownloadUrl:function(){var url=this.url("download_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({})}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("Could not retrieve download url.")])})}).promise()}}}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.Logger==="undefined"){window.Logger={log:function(event_type,data,kwargs){}}}OpenAssessment.Container=function(containerItem,kwargs){this.containerElement=kwargs.containerElement;this.templateElement=kwargs.templateElement;this.addButtonElement=kwargs.addButtonElement;this.removeButtonClass=kwargs.removeButtonClass;this.containerItemClass=kwargs.containerItemClass;this.notifier=kwargs.notifier;var container=this;this.createContainerItem=function(element){return new containerItem(element,container.notifier)};$(this.addButtonElement).click($.proxy(this.add,this));$("."+this.removeButtonClass,this.containerElement).click(function(eventData){var item=container.createContainerItem(eventData.target);container.remove(item)});$("."+this.containerItemClass,this.containerElement).each(function(index,element){container.createContainerItem(element)})};OpenAssessment.Container.prototype={add:function(){$(this.templateElement).children().first().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){var containerItem=container.createContainerItem(eventData.target);container.remove(containerItem)});var handlerItem=container.createContainerItem(containerItem);handlerItem.addHandler()},remove:function(item){var itemElement=$(item.element).closest("."+this.containerItemClass);var containerItem=this.createContainerItem(itemElement);containerItem.removeHandler();itemElement.remove()},getItemValues:function(){var values=[];var container=this;$("."+this.containerItemClass,this.containerElement).each(function(index,element){var containerItem=container.createContainerItem(element);var fieldValues=containerItem.getFieldValues();values.push(fieldValues)});return values},getItem:function(index){var element=$("."+this.containerItemClass,this.containerElement).get(index);return element!==undefined?this.createContainerItem(element):null},getAllItems:function(){var container=this;return $("."+this.containerItemClass,this.containerElement).map(function(){return container.createContainerItem(this)})}};OpenAssessment.ItemUtilities={createUniqueName:function(selector,nameAttribute){var index=0;while(index<=selector.length){if(selector.parent().find("*["+nameAttribute+"='"+index+"']").length===0){return index.toString()}index++}return index.toString()}};OpenAssessment.RubricOption=function(element,notifier){this.element=element;this.notifier=notifier;$(this.element).focusout($.proxy(this.updateHandler,this))};OpenAssessment.RubricOption.prototype={getFieldValues:function(){var fields={label:OpenAssessment.Fields.stringField($(".openassessment_criterion_option_label",this.element)),points:OpenAssessment.Fields.intField($(".openassessment_criterion_option_points",this.element)),explanation:OpenAssessment.Fields.stringField($(".openassessment_criterion_option_explanation",this.element))};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_option_name",this.element));if(nameString!==""){fields.name=nameString}return fields},addHandler:function(){var criterionElement=$(this.element).closest(".openassessment_criterion");var criterionName=$(criterionElement).data("criterion");var criterionLabel=$(".openassessment_criterion_label",criterionElement).val();var options=$(".openassessment_criterion_option",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(options,"data-option");$(this.element).attr("data-criterion",criterionName).attr("data-option",name);$(".openassessment_criterion_option_name",this.element).attr("value",name);var fields=this.getFieldValues();this.notifier.notificationFired("optionAdd",{criterionName:criterionName,criterionLabel:criterionLabel,name:name,label:fields.label,points:fields.points})},removeHandler:function(){var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");this.notifier.notificationFired("optionRemove",{criterionName:criterionName,name:optionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");var optionLabel=fields.label;var optionPoints=fields.points;this.notifier.notificationFired("optionUpdated",{criterionName:criterionName,name:optionName,label:optionLabel,points:optionPoints})}};OpenAssessment.RubricCriterion=function(element,notifier){this.element=element;this.notifier=notifier;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_criterion_option_remove_button",containerItemClass:"openassessment_criterion_option",notifier:this.notifier});$(this.element).focusout($.proxy(this.updateHandler,this))};OpenAssessment.RubricCriterion.prototype={getFieldValues:function(){var fields={label:OpenAssessment.Fields.stringField($(".openassessment_criterion_label",this.element)),prompt:OpenAssessment.Fields.stringField($(".openassessment_criterion_prompt",this.element)),feedback:OpenAssessment.Fields.stringField($(".openassessment_criterion_feedback",this.element)),options:this.optionContainer.getItemValues()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_name",this.element));if(nameString!==""){fields.name=nameString}return fields},addOption:function(){this.optionContainer.add()},addHandler:function(){var criteria=$(".openassessment_criterion",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(criteria,"data-criterion");$(this.element).attr("data-criterion",name);$(".openassessment_criterion_name",this.element).attr("value",name)},removeHandler:function(){var criterionName=$(this.element).data("criterion");this.notifier.notificationFired("criterionRemove",{criterionName:criterionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=fields.name;var criterionLabel=fields.label;this.notifier.notificationFired("criterionUpdated",{criterionName:criterionName,criterionLabel:criterionLabel})}};OpenAssessment.TrainingExample=function(element){this.element=element};OpenAssessment.TrainingExample.prototype={getFieldValues:function(){var optionsSelected=[];$(".openassessment_training_example_criterion_option",this.element).each(function(){optionsSelected.push({criterion:$(this).data("criterion"),option:$(this).prop("value")})});return{answer:$(".openassessment_training_example_essay",this.element).first().prop("value"),options_selected:optionsSelected}},addHandler:function(){},removeHandler:function(){},updateHandler:function(){}};OpenAssessment.StudioView=function(runtime,element,server){this.element=element;this.runtime=runtime;this.server=server;this.fixModalHeight();this.initializeTabs();this.promptView=new OpenAssessment.EditPromptView($("#oa_prompt_editor_wrapper",this.element).get(0));var studentTrainingView=new OpenAssessment.EditStudentTrainingView($("#oa_student_training_editor",this.element).get(0));var peerAssessmentView=new OpenAssessment.EditPeerAssessmentView($("#oa_peer_assessment_editor",this.element).get(0));var selfAssessmentView=new OpenAssessment.EditSelfAssessmentView($("#oa_self_assessment_editor",this.element).get(0));var exampleBasedAssessmentView=new OpenAssessment.EditExampleBasedAssessmentView($("#oa_ai_assessment_editor",this.element).get(0));var assessmentLookupDictionary={};assessmentLookupDictionary[studentTrainingView.getID()]=studentTrainingView;assessmentLookupDictionary[peerAssessmentView.getID()]=peerAssessmentView;assessmentLookupDictionary[selfAssessmentView.getID()]=selfAssessmentView;assessmentLookupDictionary[exampleBasedAssessmentView.getID()]=exampleBasedAssessmentView;this.settingsView=new OpenAssessment.EditSettingsView($("#oa_basic_settings_editor",this.element).get(0),assessmentLookupDictionary);this.rubricView=new OpenAssessment.EditRubricView($("#oa_rubric_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([new OpenAssessment.StudentTrainingListener]));$(".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).addClass("openassessment_full_height").parentsUntil(".modal-window").addClass("openassessment_full_height");$(this.element).closest(".modal-window").addClass("openassessment_modal_window")},initializeTabs:function(){if(typeof OpenAssessment.lastOpenEditingTab==="undefined"){OpenAssessment.lastOpenEditingTab=2}$(".openassessment_editor_content_and_tabs",this.element).tabs({active:OpenAssessment.lastOpenEditingTab})},saveTabState:function(){var tabElement=$(".openassessment_editor_content_and_tabs",this.element);OpenAssessment.lastOpenEditingTab=tabElement.tabs("option","active")},save:function(){var view=this;this.saveTabState();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(),imageSubmissionEnabled:view.settingsView.imageSubmissionEnabled(),editorAssessmentsOrder:view.settingsView.editorAssessmentsOrder()}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.saveTabState();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.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");this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_start_date","#peer_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_due_date","#peer_assessment_due_time").install()};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(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")}};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");this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_start_date","#self_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_due_date","#self_assessment_due_time").install()};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(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")}};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");this.exampleContainer=new OpenAssessment.Container(OpenAssessment.TrainingExample,{containerElement:$("#openassessment_training_example_list",this.element).get(0),templateElement:$("#openassessment_training_example_template",this.element).get(0),addButtonElement:$(".openassessment_add_training_example",this.element).get(0),removeButtonClass:"openassessment_training_example_remove",containerItemClass:"openassessment_training_example"})};OpenAssessment.EditStudentTrainingView.prototype={description:function(){return{examples:this.exampleContainer.getItemValues()}},isEnabled:function(isEnabled){var sel=$("#include_student_training",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},getID:function(){return $(this.element).attr("id")}};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_xml: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)},getID:function(){return $(this.element).attr("id")}};OpenAssessment.Fields={stringField:function(sel,value){if(typeof value!=="undefined"){sel.val(value)}return sel.val()},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.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).addClass("is--hidden");$(this.shownSelector,this.element).removeClass("is--hidden")},hide:function(){$(this.hiddenSelector,this.element).removeClass("is--hidden");$(this.shownSelector,this.element).addClass("is--hidden")}};OpenAssessment.DatetimeControl=function(element,datePicker,timePicker){this.element=element;this.datePicker=datePicker;this.timePicker=timePicker};OpenAssessment.DatetimeControl.prototype={install:function(){var dateString=$(this.datePicker,this.element).val();$(this.datePicker,this.element).datepicker({showButtonPanel:true}).datepicker("option","dateFormat","yy-mm-dd").datepicker("setDate",dateString);$(this.timePicker,this.element).timepicker({timeFormat:"H:i",step:60});return this},datetime:function(dateString,timeString){var datePickerSel=$(this.datePicker,this.element);var timePickerSel=$(this.timePicker,this.element);if(typeof dateString!=="undefined"){datePickerSel.datepicker("setDate",dateString)}if(typeof timeString!=="undefined"){timePickerSel.val(timeString)}if(datePickerSel.val()===""&&timePickerSel.val()===""){return null}return datePickerSel.val()+"T"+timePickerSel.val()}};OpenAssessment.StudentTrainingListener=function(){this.element=$("#oa_student_training_editor");this.alert=new OpenAssessment.ValidationAlert($("#openassessment_rubric_validation_alert"))};OpenAssessment.StudentTrainingListener.prototype={optionUpdated:function(data){var view=this;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){var criterion=this;var option=$('option[value="'+data.name+'"]',criterion);$(option).text(view._generateOptionString(data.label,data.points))})},optionAdd:function(data){var options=$('.openassessment_training_example_criterion_option[data-criterion="'+data.criterionName+'"]');var view=this;var criterionAdded=false;var examplesUpdated=false;if(options.length===0){this.criterionAdd(data);criterionAdded=true}$(".openassessment_training_example_criterion_option",this.element).each(function(){if($(this).data("criterion")===data.criterionName){var criterion=this;$(criterion).append($("<option></option>").attr("value",data.name).text(view._generateOptionString(data.label,data.points)));examplesUpdated=true}});if(criterionAdded&&examplesUpdated){this.displayAlertMsg(gettext("Criterion Addition requires Training Example Updates"),gettext("Because you added a criterion, student training examples will have to be updated."))}},optionRemove:function(data){var handler=this;var invalidated=false;$(".openassessment_training_example_criterion_option",this.element).each(function(){var criterionOption=this;if($(criterionOption).data("criterion")===data.criterionName){if($(criterionOption).val()===data.name.toString()){$(criterionOption).val("");$(criterionOption).addClass("openassessment_highlighted_field");$(criterionOption).click(function(){$(criterionOption).removeClass("openassessment_highlighted_field")});invalidated=true}$('option[value="'+data.name+'"]',criterionOption).remove();if($("option",criterionOption).length==1){handler.removeAllOptions(data);invalidated=false}}});if(invalidated){this.displayAlertMsg(gettext("Option Deletion Led to Invalidation"),gettext("Because you deleted an option, some student training examples had to be reset."))}},removeAllOptions:function(data){var changed=false;$(".openassessment_training_example_criterion",this.element).each(function(){var criterion=this;if($(criterion).data("criterion")==data.criterionName){$(criterion).remove();changed=true}});if(changed){this.displayAlertMsg(gettext("Option Deletion Led to Invalidation"),gettext("The deletion of the last criterion option caused the criterion to be removed in the student training examples."))}},criterionRemove:function(data){var changed=false;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(this).remove();changed=true});if(changed){this.displayAlertMsg(gettext("Criterion Deletion Led to Invalidation"),gettext("Because you deleted a criterion, there were student training examples where the criterion had to be removed."))}},displayAlertMsg:function(title,msg){this.alert.setMessage(title,msg);this.alert.show()},criterionUpdated:function(data){var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(".openassessment_training_example_criterion_name_wrapper",this).text(data.criterionLabel)})},criterionAdd:function(data){var view=this.element;var criterion=$("#openassessment_training_example_criterion_template").children().first().clone().removeAttr("id").attr("data-criterion",data.criterionName).toggleClass("is--hidden",false).appendTo(".openassessment_training_example_criteria_selections",view);criterion.find(".openassessment_training_example_criterion_option").attr("data-criterion",data.criterionName);criterion.find(".openassessment_training_example_criterion_name_wrapper").text(data.label)},examplesCriteriaLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion",this).each(function(){var criterionName=$(this).data("criterion");var criterionLabel=$(".openassessment_training_example_criterion_name_wrapper",this).text().trim();exampleDescription[criterionName]=criterionLabel});examples.push(exampleDescription)});return examples},examplesOptionsLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion_option",this).each(function(){var criterionName=$(this).data("criterion");exampleDescription[criterionName]={};$("option",this).each(function(){var optionName=$(this).val();var optionLabel=$(this).text().trim();exampleDescription[criterionName][optionName]=optionLabel})});examples.push(exampleDescription)});return examples},_generateOptionString:function(name,points){return name+" - "+points+gettext(" points")}};OpenAssessment.Notifier=function(listeners){this.listeners=listeners};OpenAssessment.Notifier.prototype={notificationFired:function(name,data){for(var i=0;i<this.listeners.length;i++){if(typeof this.listeners[i][name]==="function"){this.listeners[i][name](data)}}}};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,notifier){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_criterion_remove_button",containerItemClass:"openassessment_criterion",notifier:notifier});this.alert=new OpenAssessment.ValidationAlert($("#openassessment_rubric_validation_alert",this.element))};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)},addCriterion:function(){this.criteriaContainer.add()},removeCriterion:function(item){this.criteriaContainer.remove(item)},getAllCriteria:function(){return this.criteriaContainer.getAllItems()},getCriterionItem:function(index){return this.criteriaContainer.getItem(index)},addOption:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.add()},removeOption:function(criterionIndex,item){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.remove(item)},getAllOptions:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getAllItems()},getOptionItem:function(criterionIndex,optionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getItem(optionIndex)}};OpenAssessment.EditSettingsView=function(element,assessmentViews){this.settingsElement=element;this.assessmentsElement=$(element).siblings("#openassessment_assessment_module_settings_editors").get(0);this.assessmentViews=assessmentViews;this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_start_date","#openassessment_submission_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_due_date","#openassessment_submission_due_time").install();this.initializeSortableAssessments()};OpenAssessment.EditSettingsView.prototype={initializeSortableAssessments:function(){var view=this;
$("#openassessment_assessment_module_settings_editors",view.element).sortable({start:function(event,ui){$(".openassessment_assessment_module_editor",view.element).hide();var targetHeight="auto";ui.placeholder.height(targetHeight);ui.helper.height(targetHeight);$("#openassessment_assessment_module_settings_editors",view.element).sortable("refresh").sortable("refreshPositions")},stop:function(event,ui){$(".openassessment_assessment_module_editor",view.element).show()},snap:true,axis:"y",handle:".drag-handle",cursorAt:{top:20}});$("#openassessment_assessment_module_settings_editors .drag-handle",view.element).disableSelection()},displayName:function(name){var sel=$("#openassessment_title_editor",this.settingsElement);return OpenAssessment.Fields.stringField(sel,name)},submissionStart:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},submissionDue:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},imageSubmissionEnabled:function(isEnabled){var sel=$("#openassessment_submission_image_editor",this.settingsElement);if(typeof isEnabled!=="undefined"){if(isEnabled){sel.val(1)}else{sel.val(0)}}return sel.val()==1},assessmentsDescription:function(){var assessmentDescList=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];if(asmntView.isEnabled()){var description=asmntView.description();description["name"]=asmntView.name;assessmentDescList.push(description)}});return assessmentDescList},editorAssessmentsOrder:function(){var editorAssessments=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];editorAssessments.push(asmntView.name)});return editorAssessments}};OpenAssessment.ValidationAlert=function(element){var alert=this;this.element=element;this.rubricContentElement=$("#openassessment_rubric_content_editor");this.title=$(".openassessment_alert_title",this.element);this.message=$(".openassessment_alert_message",this.element);$(".openassessment_alert_close",element).click(function(eventObject){eventObject.preventDefault();alert.hide()})};OpenAssessment.ValidationAlert.prototype={hide:function(){this.element.addClass("is--hidden");this.rubricContentElement.removeClass("openassessment_alert_shown")},show:function(){this.element.removeClass("is--hidden");this.rubricContentElement.addClass("openassessment_alert_shown")},setMessage:function(newTitle,newMessage){this.title.text(newTitle);this.message.text(newMessage)},isVisible:function(){return!this.element.hasClass("is--hidden")},getTitle:function(){return this.title.text()},getMessage:function(){return this.message.text()}};
\ No newline at end of file
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.Logger==="undefined"){window.Logger={log:function(event_type,data,kwargs){}}}if(typeof OpenAssessment.Server=="undefined"||!OpenAssessment.Server){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,criterionFeedback,overallFeedback){var url=this.url("self_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.")])})})},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,editor_assessments_order:kwargs.editorAssessmentsOrder,allow_file_upload:kwargs.imageSubmissionEnabled});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()},getUploadUrl:function(contentType){var url=this.url("upload_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({contentType:contentType})}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("Could not retrieve upload url.")])})}).promise()},getDownloadUrl:function(){var url=this.url("download_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({})}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("Could not retrieve download url.")])})}).promise()}}}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.Logger==="undefined"){window.Logger={log:function(event_type,data,kwargs){}}}OpenAssessment.Container=function(containerItem,kwargs){this.containerElement=kwargs.containerElement;this.templateElement=kwargs.templateElement;this.addButtonElement=kwargs.addButtonElement;this.removeButtonClass=kwargs.removeButtonClass;this.containerItemClass=kwargs.containerItemClass;this.notifier=kwargs.notifier;var container=this;this.createContainerItem=function(element){return new containerItem(element,container.notifier)};$(this.addButtonElement).click($.proxy(this.add,this));$("."+this.removeButtonClass,this.containerElement).click(function(eventData){var item=container.createContainerItem(eventData.target);container.remove(item)});$("."+this.containerItemClass,this.containerElement).each(function(index,element){container.createContainerItem(element)})};OpenAssessment.Container.prototype={add:function(){$(this.templateElement).children().first().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){var containerItem=container.createContainerItem(eventData.target);container.remove(containerItem)});var handlerItem=container.createContainerItem(containerItem);handlerItem.addHandler()},remove:function(item){var itemElement=$(item.element).closest("."+this.containerItemClass);var containerItem=this.createContainerItem(itemElement);containerItem.removeHandler();itemElement.remove()},getItemValues:function(){var values=[];var container=this;$("."+this.containerItemClass,this.containerElement).each(function(index,element){var containerItem=container.createContainerItem(element);var fieldValues=containerItem.getFieldValues();values.push(fieldValues)});return values},getItem:function(index){var element=$("."+this.containerItemClass,this.containerElement).get(index);return element!==undefined?this.createContainerItem(element):null},getAllItems:function(){var container=this;return $("."+this.containerItemClass,this.containerElement).map(function(){return container.createContainerItem(this)})}};OpenAssessment.ItemUtilities={createUniqueName:function(selector,nameAttribute){var index=0;while(index<=selector.length){if(selector.parent().find("*["+nameAttribute+"='"+index+"']").length===0){return index.toString()}index++}return index.toString()}};OpenAssessment.RubricOption=function(element,notifier){this.element=element;this.notifier=notifier;this.MAX_POINTS=1e3;$(this.element).focusout($.proxy(this.updateHandler,this))};OpenAssessment.RubricOption.prototype={getFieldValues:function(){var fields={label:this.label(),points:this.points(),explanation:this.explanation()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_option_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){var sel=$(".openassessment_criterion_option_label",this.element);return OpenAssessment.Fields.stringField(sel,label)},points:function(points){var sel=$(".openassessment_criterion_option_points",this.element);return OpenAssessment.Fields.intField(sel,points)},explanation:function(explanation){var sel=$(".openassessment_criterion_option_explanation",this.element);return OpenAssessment.Fields.stringField(sel,explanation)},addHandler:function(){var criterionElement=$(this.element).closest(".openassessment_criterion");var criterionName=$(criterionElement).data("criterion");var criterionLabel=$(".openassessment_criterion_label",criterionElement).val();var options=$(".openassessment_criterion_option",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(options,"data-option");$(this.element).attr("data-criterion",criterionName).attr("data-option",name);$(".openassessment_criterion_option_name",this.element).attr("value",name);var fields=this.getFieldValues();this.notifier.notificationFired("optionAdd",{criterionName:criterionName,criterionLabel:criterionLabel,name:name,label:fields.label,points:fields.points})},removeHandler:function(){var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");this.notifier.notificationFired("optionRemove",{criterionName:criterionName,name:optionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");var optionLabel=fields.label;var optionPoints=fields.points;this.notifier.notificationFired("optionUpdated",{criterionName:criterionName,name:optionName,label:optionLabel,points:optionPoints})},validate:function(){var pointString=$(".openassessment_criterion_option_points",this.element).val();var matches=pointString.trim().match(/^\d{1,3}$/g);var isValid=matches!==null;if(!isValid){$(".openassessment_criterion_option_points",this.element).addClass("openassessment_highlighted_field")}return isValid},validationErrors:function(){var sel=$(".openassessment_criterion_option_points",this.element);var hasError=sel.hasClass("openassessment_highlighted_field");return hasError?["Option points are invalid"]:[]},clearValidationErrors:function(){$(".openassessment_criterion_option_points",this.element).removeClass("openassessment_highlighted_field")}};OpenAssessment.RubricCriterion=function(element,notifier){this.element=element;this.notifier=notifier;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_criterion_option_remove_button",containerItemClass:"openassessment_criterion_option",notifier:this.notifier});$(this.element).focusout($.proxy(this.updateHandler,this))};OpenAssessment.RubricCriterion.prototype={getFieldValues:function(){var fields={label:this.label(),prompt:this.prompt(),feedback:this.feedback(),options:this.optionContainer.getItemValues()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){var sel=$(".openassessment_criterion_label",this.element);return OpenAssessment.Fields.stringField(sel,label)},prompt:function(prompt){var sel=$(".openassessment_criterion_prompt",this.element);return OpenAssessment.Fields.stringField(sel,prompt)},feedback:function(){return $(".openassessment_criterion_feedback",this.element).val()},addOption:function(){this.optionContainer.add()},addHandler:function(){var criteria=$(".openassessment_criterion",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(criteria,"data-criterion");$(this.element).attr("data-criterion",name);$(".openassessment_criterion_name",this.element).attr("value",name)},removeHandler:function(){var criterionName=$(this.element).data("criterion");this.notifier.notificationFired("criterionRemove",{criterionName:criterionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=fields.name;var criterionLabel=fields.label;this.notifier.notificationFired("criterionUpdated",{criterionName:criterionName,criterionLabel:criterionLabel})},validate:function(){var isValid=true;$.each(this.optionContainer.getAllItems(),function(){isValid=isValid&&this.validate()});return isValid},validationErrors:function(){var errors=[];$.each(this.optionContainer.getAllItems(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){$.each(this.optionContainer.getAllItems(),function(){this.clearValidationErrors()})}};OpenAssessment.TrainingExample=function(element){this.element=element};OpenAssessment.TrainingExample.prototype={getFieldValues:function(){var optionsSelected=[];$(".openassessment_training_example_criterion_option",this.element).each(function(){optionsSelected.push({criterion:$(this).data("criterion"),option:$(this).prop("value")})});return{answer:$(".openassessment_training_example_essay",this.element).first().prop("value"),options_selected:optionsSelected}},addHandler:function(){},removeHandler:function(){},updateHandler:function(){},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.StudioView=function(runtime,element,server){this.element=element;this.runtime=runtime;this.server=server;this.fixModalHeight();this.initializeTabs();this.validationAlert=new OpenAssessment.ValidationAlert;this.validationAlert.installEventHandlers();this.promptView=new OpenAssessment.EditPromptView($("#oa_prompt_editor_wrapper",this.element).get(0));var studentTrainingView=new OpenAssessment.EditStudentTrainingView($("#oa_student_training_editor",this.element).get(0));var peerAssessmentView=new OpenAssessment.EditPeerAssessmentView($("#oa_peer_assessment_editor",this.element).get(0));var selfAssessmentView=new OpenAssessment.EditSelfAssessmentView($("#oa_self_assessment_editor",this.element).get(0));var exampleBasedAssessmentView=new OpenAssessment.EditExampleBasedAssessmentView($("#oa_ai_assessment_editor",this.element).get(0));var assessmentLookupDictionary={};assessmentLookupDictionary[studentTrainingView.getID()]=studentTrainingView;assessmentLookupDictionary[peerAssessmentView.getID()]=peerAssessmentView;assessmentLookupDictionary[selfAssessmentView.getID()]=selfAssessmentView;assessmentLookupDictionary[exampleBasedAssessmentView.getID()]=exampleBasedAssessmentView;this.settingsView=new OpenAssessment.EditSettingsView($("#oa_basic_settings_editor",this.element).get(0),assessmentLookupDictionary);this.rubricView=new OpenAssessment.EditRubricView($("#oa_rubric_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([new OpenAssessment.StudentTrainingListener]));$(".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).addClass("openassessment_full_height").parentsUntil(".modal-window").addClass("openassessment_full_height");$(this.element).closest(".modal-window").addClass("openassessment_modal_window")},initializeTabs:function(){if(typeof OpenAssessment.lastOpenEditingTab==="undefined"){OpenAssessment.lastOpenEditingTab=2}$(".openassessment_editor_content_and_tabs",this.element).tabs({active:OpenAssessment.lastOpenEditingTab})},saveTabState:function(){var tabElement=$(".openassessment_editor_content_and_tabs",this.element);OpenAssessment.lastOpenEditingTab=tabElement.tabs("option","active")},save:function(){var view=this;this.saveTabState();this.validationAlert.hide();this.clearValidationErrors();if(!this.validate()){this.validationAlert.setMessage(gettext("Validation Errors"),gettext("Some fields are not valid. Please update the fields.")).show()}else{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(),imageSubmissionEnabled:view.settingsView.imageSubmissionEnabled(),editorAssessmentsOrder:view.settingsView.editorAssessmentsOrder()}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.saveTabState();this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})},validate:function(){return this.settingsView.validate()&&this.rubricView.validate()},validationErrors:function(){return this.settingsView.validationErrors().concat(this.rubricView.validationErrors())},clearValidationErrors:function(){this.settingsView.clearValidationErrors();this.rubricView.clearValidationErrors()}};function OpenAssessmentEditor(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server)}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");this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_start_date","#peer_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_due_date","#peer_assessment_due_time").install()};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(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){return this.startDatetimeControl.validate()&&this.dueDatetimeControl.validate()},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Peer assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Peer assessment due is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors()}};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");this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_start_date","#self_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_due_date","#self_assessment_due_time").install()};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(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){return this.startDatetimeControl.validate()&&this.dueDatetimeControl.validate()},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Self assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Self assessment due is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors()}};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");this.exampleContainer=new OpenAssessment.Container(OpenAssessment.TrainingExample,{containerElement:$("#openassessment_training_example_list",this.element).get(0),templateElement:$("#openassessment_training_example_template",this.element).get(0),addButtonElement:$(".openassessment_add_training_example",this.element).get(0),removeButtonClass:"openassessment_training_example_remove",containerItemClass:"openassessment_training_example"})};OpenAssessment.EditStudentTrainingView.prototype={description:function(){return{examples:this.exampleContainer.getItemValues()}},isEnabled:function(isEnabled){var sel=$("#include_student_training",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},getID:function(){return $(this.element).attr("id")},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};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_xml: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)},getID:function(){return $(this.element).attr("id")},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.Fields={stringField:function(sel,value){if(typeof value!=="undefined"){sel.val(value)}return sel.val()},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.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).addClass("is--hidden");$(this.shownSelector,this.element).removeClass("is--hidden")},hide:function(){$(this.hiddenSelector,this.element).removeClass("is--hidden");$(this.shownSelector,this.element).addClass("is--hidden")}};OpenAssessment.DatetimeControl=function(element,datePicker,timePicker){this.element=element;this.datePicker=datePicker;this.timePicker=timePicker};OpenAssessment.DatetimeControl.prototype={install:function(){var dateString=$(this.datePicker,this.element).val();$(this.datePicker,this.element).datepicker({showButtonPanel:true}).datepicker("option","dateFormat","yy-mm-dd").val(dateString);$(this.timePicker,this.element).timepicker({timeFormat:"H:i",step:60});return this},datetime:function(dateString,timeString){var datePickerSel=$(this.datePicker,this.element);var timePickerSel=$(this.timePicker,this.element);if(typeof dateString!=="undefined"){datePickerSel.val(dateString)}if(typeof timeString!=="undefined"){timePickerSel.val(timeString)}return datePickerSel.val()+"T"+timePickerSel.val()},validate:function(){var datetimeString=this.datetime();var matches=datetimeString.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/g);var isValid=matches!==null;if(!isValid){$(this.datePicker,this.element).addClass("openassessment_highlighted_field");$(this.timePicker,this.element).addClass("openassessment_highlighted_field")}return isValid},clearValidationErrors:function(){$(this.datePicker,this.element).removeClass("openassessment_highlighted_field");$(this.timePicker,this.element).removeClass("openassessment_highlighted_field")},validationErrors:function(){var errors=[];var dateHasError=$(this.datePicker,this.element).hasClass("openassessment_highlighted_field");var timeHasError=$(this.timePicker,this.element).hasClass("openassessment_highlighted_field");if(dateHasError){errors.push("Date is invalid")}if(timeHasError){errors.push("Time is invalid")}return errors}};OpenAssessment.StudentTrainingListener=function(){this.element=$("#oa_student_training_editor");this.alert=new OpenAssessment.ValidationAlert($("#openassessment_rubric_validation_alert"))};OpenAssessment.StudentTrainingListener.prototype={optionUpdated:function(data){var view=this;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){var criterion=this;var option=$('option[value="'+data.name+'"]',criterion);$(option).text(view._generateOptionString(data.label,data.points))})},optionAdd:function(data){var options=$('.openassessment_training_example_criterion_option[data-criterion="'+data.criterionName+'"]');var view=this;var criterionAdded=false;var examplesUpdated=false;if(options.length===0){this.criterionAdd(data);criterionAdded=true}$(".openassessment_training_example_criterion_option",this.element).each(function(){if($(this).data("criterion")===data.criterionName){var criterion=this;$(criterion).append($("<option></option>").attr("value",data.name).text(view._generateOptionString(data.label,data.points)));examplesUpdated=true}});if(criterionAdded&&examplesUpdated){this.displayAlertMsg(gettext("Criterion Addition requires Training Example Updates"),gettext("Because you added a criterion, student training examples will have to be updated."))}},optionRemove:function(data){var handler=this;var invalidated=false;$(".openassessment_training_example_criterion_option",this.element).each(function(){var criterionOption=this;if($(criterionOption).data("criterion")===data.criterionName){if($(criterionOption).val()===data.name.toString()){$(criterionOption).val("");$(criterionOption).addClass("openassessment_highlighted_field");$(criterionOption).click(function(){$(criterionOption).removeClass("openassessment_highlighted_field")});invalidated=true}$('option[value="'+data.name+'"]',criterionOption).remove();if($("option",criterionOption).length==1){handler.removeAllOptions(data);invalidated=false}}});if(invalidated){this.displayAlertMsg(gettext("Option Deletion Led to Invalidation"),gettext("Because you deleted an option, some student training examples had to be reset."))}},removeAllOptions:function(data){var changed=false;$(".openassessment_training_example_criterion",this.element).each(function(){var criterion=this;if($(criterion).data("criterion")==data.criterionName){$(criterion).remove();changed=true}});if(changed){this.displayAlertMsg(gettext("Option Deletion Led to Invalidation"),gettext("The deletion of the last criterion option caused the criterion to be removed in the student training examples."))}},criterionRemove:function(data){var changed=false;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(this).remove();changed=true});if(changed){this.displayAlertMsg(gettext("Criterion Deletion Led to Invalidation"),gettext("Because you deleted a criterion, there were student training examples where the criterion had to be removed."))}},displayAlertMsg:function(title,msg){this.alert.setMessage(title,msg);this.alert.show()},criterionUpdated:function(data){var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(".openassessment_training_example_criterion_name_wrapper",this).text(data.criterionLabel)})},criterionAdd:function(data){var view=this.element;var criterion=$("#openassessment_training_example_criterion_template").children().first().clone().removeAttr("id").attr("data-criterion",data.criterionName).toggleClass("is--hidden",false).appendTo(".openassessment_training_example_criteria_selections",view);criterion.find(".openassessment_training_example_criterion_option").attr("data-criterion",data.criterionName);criterion.find(".openassessment_training_example_criterion_name_wrapper").text(data.label)},examplesCriteriaLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};
$(".openassessment_training_example_criterion",this).each(function(){var criterionName=$(this).data("criterion");var criterionLabel=$(".openassessment_training_example_criterion_name_wrapper",this).text().trim();exampleDescription[criterionName]=criterionLabel});examples.push(exampleDescription)});return examples},examplesOptionsLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion_option",this).each(function(){var criterionName=$(this).data("criterion");exampleDescription[criterionName]={};$("option",this).each(function(){var optionName=$(this).val();var optionLabel=$(this).text().trim();exampleDescription[criterionName][optionName]=optionLabel})});examples.push(exampleDescription)});return examples},_generateOptionString:function(name,points){return name+" - "+points+gettext(" points")}};OpenAssessment.Notifier=function(listeners){this.listeners=listeners};OpenAssessment.Notifier.prototype={notificationFired:function(name,data){for(var i=0;i<this.listeners.length;i++){if(typeof this.listeners[i][name]==="function"){this.listeners[i][name](data)}}}};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,notifier){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_criterion_remove_button",containerItemClass:"openassessment_criterion",notifier:notifier});this.alert=new OpenAssessment.ValidationAlert($("#openassessment_rubric_validation_alert",this.element))};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)},addCriterion:function(){this.criteriaContainer.add()},removeCriterion:function(item){this.criteriaContainer.remove(item)},getAllCriteria:function(){return this.criteriaContainer.getAllItems()},getCriterionItem:function(index){return this.criteriaContainer.getItem(index)},addOption:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.add()},removeOption:function(criterionIndex,item){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.remove(item)},getAllOptions:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getAllItems()},getOptionItem:function(criterionIndex,optionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getItem(optionIndex)},validate:function(){var isValid=true;$.each(this.getAllCriteria(),function(){isValid=isValid&&this.validate()});return isValid},validationErrors:function(){var errors=[];$.each(this.getAllCriteria(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){$.each(this.getAllCriteria(),function(){this.clearValidationErrors()})}};OpenAssessment.EditSettingsView=function(element,assessmentViews){this.settingsElement=element;this.assessmentsElement=$(element).siblings("#openassessment_assessment_module_settings_editors").get(0);this.assessmentViews=assessmentViews;this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_start_date","#openassessment_submission_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_due_date","#openassessment_submission_due_time").install();this.initializeSortableAssessments()};OpenAssessment.EditSettingsView.prototype={initializeSortableAssessments:function(){var view=this;$("#openassessment_assessment_module_settings_editors",view.element).sortable({start:function(event,ui){$(".openassessment_assessment_module_editor",view.element).hide();var targetHeight="auto";ui.placeholder.height(targetHeight);ui.helper.height(targetHeight);$("#openassessment_assessment_module_settings_editors",view.element).sortable("refresh").sortable("refreshPositions")},stop:function(event,ui){$(".openassessment_assessment_module_editor",view.element).show()},snap:true,axis:"y",handle:".drag-handle",cursorAt:{top:20}});$("#openassessment_assessment_module_settings_editors .drag-handle",view.element).disableSelection()},displayName:function(name){var sel=$("#openassessment_title_editor",this.settingsElement);return OpenAssessment.Fields.stringField(sel,name)},submissionStart:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},submissionDue:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},imageSubmissionEnabled:function(isEnabled){var sel=$("#openassessment_submission_image_editor",this.settingsElement);if(typeof isEnabled!=="undefined"){if(isEnabled){sel.val(1)}else{sel.val(0)}}return sel.val()==1},assessmentsDescription:function(){var assessmentDescList=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];if(asmntView.isEnabled()){var description=asmntView.description();description["name"]=asmntView.name;assessmentDescList.push(description)}});return assessmentDescList},editorAssessmentsOrder:function(){var editorAssessments=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];editorAssessments.push(asmntView.name)});return editorAssessments},validate:function(){var isValid=this.startDatetimeControl.validate()&&this.dueDatetimeControl.validate();$.each(this.assessmentViews,function(){isValid=isValid&&this.validate()});return isValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Submission start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Submission due is invalid")}$.each(this.assessmentViews,function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors();$.each(this.assessmentViews,function(){this.clearValidationErrors()})}};OpenAssessment.ValidationAlert=function(){this.element=$("#openassessment_rubric_validation_alert");this.rubricContentElement=$("#openassessment_rubric_content_editor");this.title=$(".openassessment_alert_title",this.element);this.message=$(".openassessment_alert_message",this.element)};OpenAssessment.ValidationAlert.prototype={installEventHandlers:function(){var alert=this;$(".openassessment_alert_close",this.element).click(function(eventObject){eventObject.preventDefault();alert.hide()})},hide:function(){this.element.addClass("is--hidden");this.rubricContentElement.removeClass("openassessment_alert_shown");return this},show:function(){this.element.removeClass("is--hidden");this.rubricContentElement.addClass("openassessment_alert_shown");return this},setMessage:function(newTitle,newMessage){this.title.text(newTitle);this.message.text(newMessage);return this},isVisible:function(){return!this.element.hasClass("is--hidden")},getTitle:function(){return this.title.text()},getMessage:function(){return this.message.text()}};
\ No newline at end of file
......@@ -45,8 +45,8 @@ describe("OpenAssessment.StudioView", function() {
title: "The most important of all questions.",
prompt: "How much do you like waffles?",
feedbackPrompt: "",
submissionStart: null,
submissionDue: null,
submissionStart: "2014-01-02T12:15",
submissionDue: "2014-10-01T04:53",
imageSubmissionEnabled: false,
criteria: [
{
......@@ -100,15 +100,15 @@ describe("OpenAssessment.StudioView", function() {
assessments: [
{
name: "peer-assessment",
start: null,
due: null,
start: "2014-01-02T00:00",
due: "2014-01-03T00:00",
must_grade: 5,
must_be_graded_by: 3
},
{
name: "self-assessment",
start: null,
due: null
start: "2014-01-04T00:00",
due: "2014-01-05T00:00"
}
],
editorAssessmentsOrder: [
......@@ -204,4 +204,35 @@ describe("OpenAssessment.StudioView", function() {
}
});
});
it("validates fields before saving", function() {
// Initially, there should not be a validation alert
expect(view.validationAlert.isVisible()).toBe(false);
// Introduce a validation error (date field does format invalid)
view.settingsView.submissionStart("Not a valid date!", "00:00");
// Try to save the view
view.save();
// Since there was an invalid field, expect that data was NOT sent to the server.
// Also expect that an error is displayed
expect(server.receivedData).toBe(null);
expect(view.validationAlert.isVisible()).toBe(true);
// Expect that individual fields were highlighted
expect(view.validationErrors()).toContain(
"Submission start is invalid"
);
// Fix the error and try to save again
view.settingsView.submissionStart("2014-04-01", "00:00");
view.save();
// Expect that the validation errors were cleared
// and that data was successfully sent to the server.
expect(view.validationErrors()).toEqual([]);
expect(view.validationAlert.isVisible()).toBe(false);
expect(server.receivedData).not.toBe(null);
});
});
......@@ -11,6 +11,21 @@ describe("OpenAssessment edit assessment views", function() {
expect(view.isEnabled()).toBe(true);
};
var testValidateDate = function(view, datetimeControl, expectedError) {
// Test an invalid datetime
datetimeControl.datetime("invalid", "invalid");
expect(view.validate()).toBe(false);
expect(view.validationErrors()).toContain(expectedError);
// Clear validation errors (simulate re-saving)
view.clearValidationErrors();
// Test a valid datetime
datetimeControl.datetime("2014-04-05", "00:00");
expect(view.validate()).toBe(true);
expect(view.validationErrors()).toEqual([]);
};
var testLoadXMLExamples = function(view) {
var xml = "XML DEFINITIONS WOULD BE HERE";
view.exampleDefinitions(xml);
......@@ -28,11 +43,13 @@ describe("OpenAssessment edit assessment views", function() {
beforeEach(function() {
var element = $("#oa_peer_assessment_editor").get(0);
view = new OpenAssessment.EditPeerAssessmentView(element);
view.startDatetime("2014-01-01", "00:00");
view.dueDatetime("2014-01-01", "00:00");
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() {
it("loads a description", function() {
view.mustGradeNum(1);
view.mustBeGradedByNum(2);
view.startDatetime("2014-01-01", "00:00");
......@@ -45,11 +62,18 @@ describe("OpenAssessment edit assessment views", function() {
});
});
it("Handles default dates", function() {
view.startDatetime("");
view.dueDatetime("");
expect(view.description().start).toBe(null);
expect(view.description().due).toBe(null);
it("validates the start date and time", function() {
testValidateDate(
view, view.startDatetimeControl,
"Peer assessment start is invalid"
);
});
it("validates the due date and time", function() {
testValidateDate(
view, view.dueDatetimeControl,
"Peer assessment due is invalid"
);
});
});
......@@ -59,11 +83,13 @@ describe("OpenAssessment edit assessment views", function() {
beforeEach(function() {
var element = $("#oa_self_assessment_editor").get(0);
view = new OpenAssessment.EditSelfAssessmentView(element);
view.startDatetime("2014-01-01", "00:00");
view.dueDatetime("2014-01-01", "00:00");
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() {
it("loads a description", function() {
view.startDatetime("2014-01-01", "00:00");
view.dueDatetime("2014-03-04", "00:00");
expect(view.description()).toEqual({
......@@ -72,11 +98,18 @@ describe("OpenAssessment edit assessment views", function() {
});
});
it("Handles default dates", function() {
view.startDatetime("", "");
view.dueDatetime("", "");
expect(view.description().start).toBe(null);
expect(view.description().due).toBe(null);
it("validates the start date and time", function() {
testValidateDate(
view, view.startDatetimeControl,
"Self assessment start is invalid"
);
});
it("validates the due date and time", function() {
testValidateDate(
view, view.dueDatetimeControl,
"Self assessment due is invalid"
);
});
});
......@@ -88,19 +121,19 @@ describe("OpenAssessment edit assessment views", function() {
view = new OpenAssessment.EditStudentTrainingView(element);
});
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function () {
it("enables and disables", function() { testEnableAndDisable(view); });
it("loads a description", function () {
// This assumes a particular structure of the DOM,
// which is set by the HTML fixture.
var examples = view.exampleContainer.getItemValues();
expect(examples.length).toEqual(0);
});
it("Modifies a description", function () {
it("modifies a description", function () {
view.exampleContainer.add();
var examples = view.exampleContainer.getItemValues();
expect(examples.length).toEqual(1);
});
it("Returns the correct format", function () {
it("returns the correct format", function () {
view.exampleContainer.add();
var examples = view.exampleContainer.getItemValues();
expect(examples).toEqual(
......
describe("OpenAssessment.DatetimeControl", function() {
var datetimeControl = null;
beforeEach(function() {
// Install a minimal HTML fixture
// containing text fields for the date and time
setFixtures(
'<div id="datetime_parent">' +
'<input type="text" class="date_field" />' +
'<input type="text" class="time_field" />' +
'</div>'
);
// Create the datetime control, which uses elements
// available in the fixture.
datetimeControl = new OpenAssessment.DatetimeControl(
$("#datetime_parent").get(0),
".date_field",
".time_field"
);
datetimeControl.install();
});
// Set the date and time values, then check whether
// the datetime control has the expected validation status
var testValidateDate = function(control, dateValue, timeValue, isValid, expectedError) {
control.datetime(dateValue, timeValue);
var actualIsValid = control.validate();
expect(actualIsValid).toBe(isValid);
if (isValid) { expect(control.validationErrors()).toEqual([]); }
else { expect(control.validationErrors()).toContain(expectedError); }
};
it("validates invalid dates", function() {
var expectedError = "Date is invalid";
testValidateDate(datetimeControl, "", "00:00", false, expectedError);
testValidateDate(datetimeControl, "1", "00:00", false, expectedError);
testValidateDate(datetimeControl, "123abcd", "00:00", false, expectedError);
testValidateDate(datetimeControl, "2014-", "00:00", false, expectedError);
testValidateDate(datetimeControl, "99999999-01-01", "00:00", false, expectedError);
testValidateDate(datetimeControl, "2014-99999-01", "00:00", false, expectedError);
testValidateDate(datetimeControl, "2014-01-99999", "00:00", false, expectedError);
});
it("validates invalid times", function() {
var expectedError = "Time is invalid";
testValidateDate(datetimeControl, "2014-04-01", "", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "00:00abcd", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "1", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "1.23", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "1:1", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "000:00", false, expectedError);
testValidateDate(datetimeControl, "2014-04-01", "00:000", false, expectedError);
});
it("validates valid dates and times", function() {
testValidateDate(datetimeControl, "2014-04-01", "00:00", true);
testValidateDate(datetimeControl, "9999-01-01", "00:00", true);
testValidateDate(datetimeControl, "2001-12-31", "00:00", true);
testValidateDate(datetimeControl, "2014-04-01", "12:34", true);
testValidateDate(datetimeControl, "2014-04-01", "23:59", true);
});
it("clears validation errors", function() {
// Set an invalid state
datetimeControl.datetime("invalid", "invalid");
datetimeControl.validate();
expect(datetimeControl.validationErrors().length).toEqual(2);
// Clear validation errors
datetimeControl.clearValidationErrors();
expect(datetimeControl.validationErrors()).toEqual([]);
});
});
\ No newline at end of file
......@@ -208,4 +208,27 @@ describe("OpenAssessment.EditRubricView", function() {
data: {criterionName : 'criterion_1'}
});
});
it("validates option points", function () {
// Test that a particular value is marked as valid/invalid
var testValidateOptionPoints = function(value, isValid) {
var option = view.getOptionItem(0, 0);
option.points(value);
expect(view.validate()).toBe(isValid);
};
// Invalid option point values
testValidateOptionPoints("", false);
testValidateOptionPoints("123abcd", false);
testValidateOptionPoints("-1", false);
testValidateOptionPoints("1000", false);
testValidateOptionPoints("0.5", false);
// Valid option point values
testValidateOptionPoints("0", true);
testValidateOptionPoints("1", true);
testValidateOptionPoints("2", true);
testValidateOptionPoints("998", true);
testValidateOptionPoints("999", true);
});
});
......@@ -5,6 +5,9 @@ describe("OpenAssessment.EditSettingsView", function() {
var StubView = function(name, descriptionText) {
this.name = name;
this.isValid = true;
var validationErrors = [];
this.description = function() {
return { dummy: descriptionText };
......@@ -15,27 +18,55 @@ describe("OpenAssessment.EditSettingsView", function() {
if (typeof(isEnabled) !== "undefined") { this._enabled = isEnabled; }
return this._enabled;
};
this.validate = function() {
return this.isValid;
};
this.setValidationErrors = function(errors) { validationErrors = errors; };
this.validationErrors = function() { return validationErrors; };
this.clearValidationErrors = function() { validationErrors = []; };
};
var testValidateDate = function(datetimeControl, expectedError) {
// Test an invalid datetime
datetimeControl.datetime("invalid", "invalid");
expect(view.validate()).toBe(false);
expect(view.validationErrors()).toContain(expectedError);
view.clearValidationErrors();
// Test a valid datetime
datetimeControl.datetime("2014-04-05", "00:00");
expect(view.validate()).toBe(true);
expect(view.validationErrors()).toEqual([]);
};
var view = null;
var assessmentViews = null;
// The Peer and Self Editor ID's
var PEER = "oa_peer_assessment_editor";
var SELF = "oa_self_assessment_editor";
var AI = "oa_ai_assessment_editor";
var TRAINING = "oa_student_training_editor";
beforeEach(function() {
// Load the DOM fixture
loadFixtures('oa_edit.html');
// Create the stub assessment views
assessmentViews = {
"oa_self_assessment_editor": new StubView("self-assessment", "Self assessment description"),
"oa_peer_assessment_editor": new StubView("peer-assessment", "Peer assessment description"),
"oa_ai_assessment_editor": new StubView("ai-assessment", "Example Based assessment description"),
"oa_student_training_editor": new StubView("student-training", "Student Training description")
};
assessmentViews = {};
assessmentViews[SELF] = new StubView("self-assessment", "Self assessment description");
assessmentViews[PEER] = new StubView("peer-assessment", "Peer assessment description");
assessmentViews[AI] = new StubView("ai-assessment", "Example Based assessment description");
assessmentViews[TRAINING] = new StubView("student-training", "Student Training description");
// Create the view
var element = $("#oa_basic_settings_editor").get(0);
view = new OpenAssessment.EditSettingsView(element, assessmentViews);
view.submissionStart("2014-01-01", "00:00");
view.submissionDue("2014-03-04", "00:00");
});
it("sets and loads display name", function() {
......@@ -46,17 +77,11 @@ describe("OpenAssessment.EditSettingsView", function() {
});
it("sets and loads the submission start/due dates", function() {
view.submissionStart("", "");
expect(view.submissionStart()).toBe(null);
view.submissionStart("2014-04-01", "12:34");
expect(view.submissionStart()).toEqual("2014-04-01T12:34");
view.submissionStart("2014-04-01", "00:00");
expect(view.submissionStart()).toEqual("2014-04-01T00:00");
view.submissionDue("", "");
expect(view.submissionDue()).toBe(null);
view.submissionDue("2014-05-02", "00:00");
expect(view.submissionDue()).toEqual("2014-05-02T00:00");
view.submissionDue("2014-05-02", "12:34");
expect(view.submissionDue()).toEqual("2014-05-02T12:34");
});
it("sets and loads the image enabled state", function() {
......@@ -67,29 +92,21 @@ describe("OpenAssessment.EditSettingsView", function() {
});
it("builds a description of enabled assessments", function() {
// In this test we also verify that the mechansim that reads off of the DOM is correct, in that it gets
// the right order of assessments, in addition to performing the correct calls. Note that this test's
// success depends on our Template having the original order (as it does in an unconfigured ORA problem)
// of TRAINING -> PEER -> SELF -> AI
// The Peer and Self Editor ID's
var peerID = "oa_peer_assessment_editor";
var selfID = "oa_self_assessment_editor";
var aiID = "oa_ai_assessment_editor";
var studentID = "oa_student_training_editor";
// Depends on the template having an original order
// of training --> peer --> self --> ai
// Disable all assessments, and expect an empty description
assessmentViews[peerID].isEnabled(false);
assessmentViews[selfID].isEnabled(false);
assessmentViews[aiID].isEnabled(false);
assessmentViews[studentID].isEnabled(false);
assessmentViews[PEER].isEnabled(false);
assessmentViews[SELF].isEnabled(false);
assessmentViews[AI].isEnabled(false);
assessmentViews[TRAINING].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([]);
// Enable the first assessment only
assessmentViews[peerID].isEnabled(false);
assessmentViews[selfID].isEnabled(true);
assessmentViews[aiID].isEnabled(false);
assessmentViews[studentID].isEnabled(false);
assessmentViews[PEER].isEnabled(false);
assessmentViews[SELF].isEnabled(true);
assessmentViews[AI].isEnabled(false);
assessmentViews[TRAINING].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([
{
name: "self-assessment",
......@@ -98,10 +115,10 @@ describe("OpenAssessment.EditSettingsView", function() {
]);
// Enable the second assessment only
assessmentViews[peerID].isEnabled(true);
assessmentViews[selfID].isEnabled(false);
assessmentViews[aiID].isEnabled(false);
assessmentViews[studentID].isEnabled(false);
assessmentViews[PEER].isEnabled(true);
assessmentViews[SELF].isEnabled(false);
assessmentViews[AI].isEnabled(false);
assessmentViews[TRAINING].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([
{
name: "peer-assessment",
......@@ -110,10 +127,10 @@ describe("OpenAssessment.EditSettingsView", function() {
]);
// Enable both assessments
assessmentViews[peerID].isEnabled(true);
assessmentViews[selfID].isEnabled(true);
assessmentViews[aiID].isEnabled(false);
assessmentViews[studentID].isEnabled(false);
assessmentViews[PEER].isEnabled(true);
assessmentViews[SELF].isEnabled(true);
assessmentViews[AI].isEnabled(false);
assessmentViews[TRAINING].isEnabled(false);
expect(view.assessmentsDescription()).toEqual([
{
name: "peer-assessment",
......@@ -125,4 +142,29 @@ describe("OpenAssessment.EditSettingsView", function() {
}
]);
});
it("validates submission start datetime fields", function() {
testValidateDate(
view.startDatetimeControl,
"Submission start is invalid"
);
});
it("validates submission due datetime fields", function() {
testValidateDate(
view.dueDatetimeControl,
"Submission due is invalid"
);
});
it("validates assessment views", function() {
// Simulate one of the assessment views being invalid
assessmentViews[PEER].isValid = false;
assessmentViews[PEER].setValidationErrors(["test error"]);
// Expect that the parent view is also invalid
expect(view.validate()).toBe(false);
debugger;
expect(view.validationErrors()).toContain("test error");
});
});
......@@ -38,6 +38,7 @@ Returns:
OpenAssessment.RubricOption = function(element, notifier) {
this.element = element;
this.notifier = notifier;
this.MAX_POINTS = 1000;
$(this.element).focusout($.proxy(this.updateHandler, this));
};
......@@ -56,15 +57,9 @@ OpenAssessment.RubricOption.prototype = {
**/
getFieldValues: function () {
var fields = {
label: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_option_label', this.element)
),
points: OpenAssessment.Fields.intField(
$('.openassessment_criterion_option_points', this.element)
),
explanation: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_option_explanation', this.element)
)
label: this.label(),
points: this.points(),
explanation: this.explanation()
};
// New options won't have unique names assigned.
......@@ -79,6 +74,51 @@ OpenAssessment.RubricOption.prototype = {
},
/**
Get or set the label of the option.
Args:
label (string, optional): If provided, set the label to this string.
Returns:
string
**/
label: function(label) {
var sel = $('.openassessment_criterion_option_label', this.element);
return OpenAssessment.Fields.stringField(sel, label);
},
/**
Get or set the point value of the option.
Args:
points (int, optional): If provided, set the point value of the option.
Returns:
int
**/
points: function(points) {
var sel = $('.openassessment_criterion_option_points', this.element);
return OpenAssessment.Fields.intField(sel, points);
},
/**
Get or set the explanation for the option.
Args:
explanation (string, optional): If provided, set the explanation to this string.
Returns:
string
**/
explanation: function(explanation) {
var sel = $('.openassessment_criterion_option_explanation', this.element);
return OpenAssessment.Fields.stringField(sel, explanation);
},
/**
Hook into the event handler for addition of a criterion option.
*/
......@@ -146,6 +186,46 @@ OpenAssessment.RubricOption.prototype = {
"points": optionPoints
}
);
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the option is valid.
**/
validate: function() {
var pointString = $(".openassessment_criterion_option_points", this.element).val();
var matches = pointString.trim().match(/^\d{1,3}$/g);
var isValid = (matches !== null);
if (!isValid) {
$(".openassessment_criterion_option_points", this.element)
.addClass("openassessment_highlighted_field");
}
return isValid;
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var sel = $(".openassessment_criterion_option_points", this.element);
var hasError = sel.hasClass("openassessment_highlighted_field");
return hasError ? ["Option points are invalid"] : [];
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
$(".openassessment_criterion_option_points", this.element)
.removeClass("openassessment_highlighted_field");
}
};
......@@ -200,15 +280,9 @@ OpenAssessment.RubricCriterion.prototype = {
**/
getFieldValues: function () {
var fields = {
label: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_label', this.element)
),
prompt: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_prompt', this.element)
),
feedback: OpenAssessment.Fields.stringField(
$('.openassessment_criterion_feedback', this.element)
),
label: this.label(),
prompt: this.prompt(),
feedback: this.feedback(),
options: this.optionContainer.getItemValues()
};
......@@ -224,6 +298,48 @@ OpenAssessment.RubricCriterion.prototype = {
},
/**
Get or set the label of the criterion.
Args:
label (string, optional): If provided, set the label to this string.
Returns:
string
**/
label: function(label) {
var sel = $('.openassessment_criterion_label', this.element);
return OpenAssessment.Fields.stringField(sel, label);
},
/**
Get or set the prompt of the criterion.
Args:
prompt (string, optional): If provided, set the prompt to this string.
Returns:
string
**/
prompt: function(prompt) {
var sel = $('.openassessment_criterion_prompt', this.element);
return OpenAssessment.Fields.stringField(sel, prompt);
},
/**
Get the feedback value for the criterion.
This is one of: "disabled", "optional", or "required".
Returns:
string
**/
feedback: function() {
return $('.openassessment_criterion_feedback', this.element).val();
},
/**
Add an option to the criterion.
Uses the client-side template to create the new option.
**/
......@@ -265,6 +381,46 @@ OpenAssessment.RubricCriterion.prototype = {
"criterionUpdated",
{'criterionName': criterionName, 'criterionLabel': criterionLabel}
);
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the criterion is valid.
**/
validate: function() {
var isValid = true;
$.each(this.optionContainer.getAllItems(), function() {
isValid = (isValid && this.validate());
});
return isValid;
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
$.each(this.optionContainer.getAllItems(), function() {
errors = errors.concat(this.validationErrors());
});
return errors;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
$.each(this.optionContainer.getAllItems(), function() {
this.clearValidationErrors();
});
}
};
......@@ -307,5 +463,9 @@ OpenAssessment.TrainingExample.prototype = {
addHandler: function() {},
removeHandler: function() {},
updateHandler: function() {}
updateHandler: function() {},
validate: function() { return true; },
validationErrors: function() { return []; },
clearValidationErrors: function() {}
};
\ No newline at end of file
......@@ -22,6 +22,10 @@ OpenAssessment.StudioView = function(runtime, element, server) {
// Initializes the tabbing functionality and activates the last used.
this.initializeTabs();
// Initialize the validation alert
this.validationAlert = new OpenAssessment.ValidationAlert();
this.validationAlert.installEventHandlers();
// Initialize the prompt tab view
this.promptView = new OpenAssessment.EditPromptView(
$("#oa_prompt_editor_wrapper", this.element).get(0)
......@@ -120,6 +124,25 @@ OpenAssessment.StudioView.prototype = {
save: function () {
var view = this;
this.saveTabState();
// Perform client-side validation:
// * Hide the validation alert
// * Clear errors from any field marked as invalid.
// * Mark invalid fields in the UI.
// * If there are any validation errors, show an alert.
//
// The `validate()` method calls `validate()` on any subviews,
// so that each subview has the opportunity to validate
// its fields.
this.validationAlert.hide();
this.clearValidationErrors();
if (!this.validate()) {
this.validationAlert.setMessage(
gettext("Validation Errors"),
gettext("Some fields are not valid. Please update the fields.")
).show();
}
else {
// Check whether the problem has been released; if not,
// warn the user and allow them to cancel.
this.server.checkReleased().done(
......@@ -134,6 +157,7 @@ OpenAssessment.StudioView.prototype = {
).fail(function (errMsg) {
view.showError(errMsg);
});
}
},
/**
......@@ -197,6 +221,39 @@ OpenAssessment.StudioView.prototype = {
showError: function (errorMsg) {
this.runtime.notify('error', {msg: errorMsg});
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/
validate: function() {
return this.settingsView.validate() && this.rubricView.validate();
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
return this.settingsView.validationErrors().concat(
this.rubricView.validationErrors()
);
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
this.settingsView.clearValidationErrors();
this.rubricView.clearValidationErrors();
},
};
......
......@@ -137,7 +137,45 @@ OpenAssessment.EditPeerAssessmentView.prototype = {
**/
getID: function() {
return $(this.element).attr('id');
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/
validate: function() {
return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate();
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
if (this.startDatetimeControl.validationErrors().length > 0) {
errors.push("Peer assessment start is invalid");
}
if (this.dueDatetimeControl.validationErrors().length > 0) {
errors.push("Peer assessment due is invalid");
}
return errors;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors();
this.dueDatetimeControl.clearValidationErrors();
},
};
......@@ -249,7 +287,45 @@ OpenAssessment.EditSelfAssessmentView.prototype = {
**/
getID: function() {
return $(this.element).attr('id');
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/
validate: function() {
return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate();
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
if (this.startDatetimeControl.validationErrors().length > 0) {
errors.push("Self assessment start is invalid");
}
if (this.dueDatetimeControl.validationErrors().length > 0) {
errors.push("Self assessment due is invalid");
}
return errors;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors();
this.dueDatetimeControl.clearValidationErrors();
},
};
/**
......@@ -340,7 +416,11 @@ OpenAssessment.EditStudentTrainingView.prototype = {
**/
getID: function() {
return $(this.element).attr('id');
}
},
validate: function() { return true; },
validationErrors: function() { return []; },
clearValidationErrors: function() {},
};
/**
......@@ -422,5 +502,9 @@ OpenAssessment.EditExampleBasedAssessmentView.prototype = {
**/
getID: function() {
return $(this.element).attr('id');
}
},
validate: function() { return true; },
validationErrors: function() { return []; },
clearValidationErrors: function() {},
};
\ No newline at end of file
......@@ -96,7 +96,7 @@ OpenAssessment.DatetimeControl.prototype = {
var dateString = $(this.datePicker, this.element).val();
$(this.datePicker, this.element).datepicker({ showButtonPanel: true })
.datepicker("option", "dateFormat", "yy-mm-dd")
.datepicker("setDate", dateString);
.val(dateString);
$(this.timePicker, this.element).timepicker({
timeFormat: 'H:i',
step: 60
......@@ -108,7 +108,7 @@ OpenAssessment.DatetimeControl.prototype = {
Get or set the date and time.
Args:
dateString (string, optional): If provided, set the date (YY-MM-DD).
dateString (string, optional): If provided, set the date (YYYY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
......@@ -118,12 +118,55 @@ OpenAssessment.DatetimeControl.prototype = {
datetime: function(dateString, timeString) {
var datePickerSel = $(this.datePicker, this.element);
var timePickerSel = $(this.timePicker, this.element);
if (typeof(dateString) !== "undefined") { datePickerSel.datepicker("setDate", dateString); }
if (typeof(dateString) !== "undefined") { datePickerSel.val(dateString); }
if (typeof(timeString) !== "undefined") { timePickerSel.val(timeString); }
if (datePickerSel.val() === "" && timePickerSel.val() === "") {
return null;
}
return datePickerSel.val() + "T" + timePickerSel.val();
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the fields are valid.
**/
validate: function() {
var datetimeString = this.datetime();
var matches = datetimeString.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/g);
var isValid = (matches !== null);
if (!isValid) {
$(this.datePicker, this.element).addClass("openassessment_highlighted_field");
$(this.timePicker, this.element).addClass("openassessment_highlighted_field");
}
return isValid;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
$(this.datePicker, this.element).removeClass("openassessment_highlighted_field");
$(this.timePicker, this.element).removeClass("openassessment_highlighted_field");
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
var dateHasError = $(this.datePicker, this.element).hasClass("openassessment_highlighted_field");
var timeHasError = $(this.timePicker, this.element).hasClass("openassessment_highlighted_field");
if (dateHasError) { errors.push("Date is invalid"); }
if (timeHasError) { errors.push("Time is invalid"); }
return errors;
},
};
\ No newline at end of file
......@@ -189,5 +189,49 @@ OpenAssessment.EditRubricView.prototype = {
getOptionItem: function(criterionIndex, optionIndex) {
var criterionItem = this.getCriterionItem(criterionIndex);
return criterionItem.optionContainer.getItem(optionIndex);
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/
validate: function() {
var isValid = true;
$.each(this.getAllCriteria(), function() {
isValid = (isValid && this.validate());
});
return isValid;
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
$.each(this.getAllCriteria(), function() {
errors = errors.concat(this.validationErrors());
});
return errors;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
$.each(this.getAllCriteria(), function() {
this.clearValidationErrors();
});
}
};
......@@ -198,4 +198,61 @@ OpenAssessment.EditSettingsView.prototype = {
);
return editorAssessments;
},
/**
Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/
validate: function() {
// Validate the start and due datetime controls
var isValid = (
this.startDatetimeControl.validate() &&
this.dueDatetimeControl.validate()
);
// Validate each of the assessment views
$.each(this.assessmentViews, function() {
isValid = (isValid && this.validate());
});
return isValid;
},
/**
Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/
validationErrors: function() {
var errors = [];
if (this.startDatetimeControl.validationErrors().length > 0) {
errors.push("Submission start is invalid");
}
if (this.dueDatetimeControl.validationErrors().length > 0) {
errors.push("Submission due is invalid");
}
$.each(this.assessmentViews, function() {
errors = errors.concat(this.validationErrors());
});
return errors;
},
/**
Clear all validation errors from the UI.
**/
clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors();
this.dueDatetimeControl.clearValidationErrors();
$.each(this.assessmentViews, function() {
this.clearValidationErrors();
});
},
};
\ No newline at end of file
......@@ -2,41 +2,53 @@
A class which controls the validation alert which we place at the top of the rubric page after
changes are made which will propagate to the settings section.
Args:
element (element): The element that specifies the div that the validation consists of.
Returns:
Openassessment.ValidationAlert
*/
OpenAssessment.ValidationAlert = function (element) {
var alert = this;
this.element = element;
OpenAssessment.ValidationAlert = function() {
this.element = $('#openassessment_rubric_validation_alert');
this.rubricContentElement = $('#openassessment_rubric_content_editor');
this.title = $(".openassessment_alert_title", this.element);
this.message = $(".openassessment_alert_message", this.element);
$(".openassessment_alert_close", element).click(function(eventObject) {
};
OpenAssessment.ValidationAlert.prototype = {
/**
Install the event handlers for the alert.
**/
installEventHandlers: function() {
var alert = this;
$(".openassessment_alert_close", this.element).click(
function(eventObject) {
eventObject.preventDefault();
alert.hide();
}
);
};
OpenAssessment.ValidationAlert.prototype = {
},
/**
Hides the alert.
Returns:
OpenAssessment.ValidationAlert
*/
hide: function() {
this.element.addClass('is--hidden');
this.rubricContentElement.removeClass('openassessment_alert_shown');
return this;
},
/**
Displays the alert.
Returns:
OpenAssessment.ValidationAlert
*/
show : function() {
this.element.removeClass('is--hidden');
this.rubricContentElement.addClass('openassessment_alert_shown');
return this;
},
/**
......@@ -46,10 +58,14 @@ OpenAssessment.ValidationAlert.prototype = {
Args:
newTitle (str): the new title that the message will have
newMessage (str): the new text that the message's body will contain
Returns:
OpenAssessment.ValidationAlert
*/
setMessage: function(newTitle, newMessage) {
this.title.text(newTitle);
this.message.text(newMessage);
return this;
},
/**
......
......@@ -25,6 +25,9 @@ templates.json file's directory.
import sys
import os.path
import json
import re
import dateutil.parser
import pytz
# This is a bit of a hack to ensure that the root repo directory
# is in the Python path, so Django can find the settings module.
......@@ -36,6 +39,47 @@ from django.template.loader import get_template
USAGE = u"{prog} TEMPLATE_DESC"
DATETIME_REGEX = re.compile("^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$")
def parse_dates(context):
"""
Transform datetime strings into Python datetime objects.
JSON does not provide a standard way to serialize datetime objects,
but some of the templates expect that the context contains
Python datetime objects.
This (somewhat hacky) solution recursively searches the context
for formatted datetime strings of the form "2014-01-02T12:34"
and converts them to Python datetime objects with the timezone
set to UTC.
Args:
context (JSON-serializable): The context (or part of the context)
that will be passed to the template. Dictionaries and lists
will be recursively searched and transformed.
Returns:
JSON-serializable of the same type as the `context` argument.
"""
if isinstance(context, dict):
return {
key: parse_dates(value)
for key, value in context.iteritems()
}
elif isinstance(context, list):
return [
parse_dates(item)
for item in context
]
elif isinstance(context, basestring):
if DATETIME_REGEX.match(context) is not None:
return dateutil.parser.parse(context).replace(tzinfo=pytz.utc)
return context
def render_templates(root_dir, template_json):
"""
Create rendered templates.
......@@ -51,7 +95,8 @@ def render_templates(root_dir, template_json):
"""
for template_dict in template_json:
template = get_template(template_dict['template'])
rendered = template.render(Context(template_dict['context']))
context = parse_dates(template_dict['context'])
rendered = template.render(Context(context))
output_path = os.path.join(root_dir, template_dict['output'])
try:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment