Commit 9d6ae576 by Sarina Canelake

Remove combined_open_ended XModule JS code (ORA1)

parent f6892716
class @Rubric
rubric_category_sel: '.rubric-category'
rubric_sel: '.rubric'
constructor: (el) ->
@el = el
initialize: (location) =>
@$(@rubric_sel).data("location", location)
@$('input[class="score-selection"]').change @tracking_callback
# set up the hotkeys
$(window).unbind('keydown', @keypress_callback)
$(window).keydown @keypress_callback
# display the 'current' carat
@categories = @$(@rubric_category_sel)
@category = @$(@categories.first())
@category_index = 0
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
keypress_callback: (event) =>
# don't try to do this when user is typing in a text input
if @$(event.target).is('input, textarea')
return
# for when we select via top row
if event.which >= 48 and event.which <= 57
selected = event.which - 48
# for when we select via numpad
else if event.which >= 96 and event.which <= 105
selected = event.which - 96
# we don't want to do anything since we haven't pressed a number
else
return
# if we actually have a current category (not past the end)
if(@category_index <= @categories.length)
# find the valid selections for this category
inputs = @$("input[name='score-selection-#{@category_index}']")
max_score = inputs.length - 1
if selected > max_score or selected < 0
return
inputs.filter("input[value=#{selected}]").click()
@category_index++
@category = @$(@categories[@category_index])
tracking_callback: (event) =>
target_selection = @$(event.target).val()
# chop off the beginning of the name so that we can get the number of the category
category = @$(event.target).data("category")
location = @$(@rubric_sel).data('location')
# probably want the original problem location as well
data = {location: location, selection: target_selection, category: category}
Logger.log 'rubric_select', data
# finds the scores for each rubric category
get_score_list: () =>
# find the number of categories:
num_categories = @$(@rubric_category_sel).length
score_lst = []
# get the score for each one
for i in [0..(num_categories-1)]
score = @$("input[name='score-selection-#{i}']:checked").val()
score_lst.push(score)
return score_lst
get_total_score: () =>
score_lst = @get_score_list()
tot = 0
for score in score_lst
tot += parseInt(score)
return tot
check_complete: () =>
# check to see whether or not any categories have not been scored
num_categories = @$(@rubric_category_sel).length
for i in [0..(num_categories-1)]
score = @$("input[name='score-selection-#{i}']:checked").val()
if score == undefined
return false
return true
class @CombinedOpenEnded
wrapper_sel: 'section.xmodule_CombinedOpenEndedModule'
coe_sel: 'section.combined-open-ended'
reset_button_sel: '.reset-button'
next_step_sel: '.next-step-button'
question_header_sel: '.question-header'
submit_evaluation_sel: '.submit-evaluation-button'
result_container_sel: 'div.result-container'
combined_rubric_sel: '.combined-rubric-container'
open_ended_child_sel: 'section.open-ended-child'
error_sel: '.error'
answer_area_sel: 'textarea.answer'
answer_area_div_sel : 'div.answer'
prompt_sel: '.prompt'
rubric_wrapper_sel: '.rubric-wrapper'
hint_wrapper_sel: '.hint-wrapper'
message_wrapper_sel: '.message-wrapper'
submit_button_sel: '.submit-button'
skip_button_sel: '.skip-button'
file_upload_sel: '.file-upload'
file_upload_box_sel: '.file-upload-box'
file_upload_preview_sel: '.file-upload-preview'
fof_sel: 'textarea.feedback-on-feedback'
sub_id_sel: 'input.submission_id'
grader_id_sel: 'input.grader_id'
grader_status_sel: '.grader-status'
info_rubric_elements_sel: '.rubric-info-item'
rubric_collapse_sel: '.rubric-collapse'
next_rubric_sel: '.rubric-next-button'
previous_rubric_sel: '.rubric-previous-button'
oe_alert_sel: '.open-ended-alert'
save_button_sel: '.save-button'
constructor: (el) ->
@el=el
@$el = $(el)
@reinitialize(el)
$(window).keydown @keydown_handler
$(window).keyup @keyup_handler
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
reinitialize: () ->
@has_been_reset = false
@wrapper=@$(@wrapper_sel)
@coe = @$(@coe_sel)
@ajax_url = @coe.data('ajax-url')
@get_html()
@coe = @$(@coe_sel)
#Get data from combinedopenended
@allow_reset = @coe.data('allow_reset')
@id = @coe.data('id')
@state = @coe.data('state')
@task_count = @coe.data('task-count')
@task_number = @coe.data('task-number')
@accept_file_upload = @coe.data('accept-file-upload')
@location = @coe.data('location')
# set up handlers for click tracking
@rub = new Rubric(@coe)
@rub.initialize(@location)
@is_ctrl = false
#Setup reset
@reset_button = @$(@reset_button_sel)
@reset_button.click @confirm_reset
#Setup next problem
@next_problem_button = @$(@next_step_sel)
@next_problem_button.click @next_problem
@question_header = @$(@question_header_sel)
@question_header.click @collapse_question
# valid states: 'initial', 'assessing', 'post_assessment', 'done'
Collapsible.setCollapsibles(@$el)
@submit_evaluation_button = @$(@submit_evaluation_sel)
@submit_evaluation_button.click @message_post
@results_container = @$(@result_container_sel)
@combined_rubric_container = @$(@combined_rubric_sel)
# Where to put the rubric once we load it
@oe = @$(@open_ended_child_sel)
@errors_area = @$(@oe).find(@error_sel)
@answer_area = @$(@oe).find(@answer_area_sel)
@prompt_container = @$(@oe).find(@prompt_sel)
@rubric_wrapper = @$(@oe).find(@rubric_wrapper_sel)
@hint_wrapper = @$(@oe).find(@hint_wrapper_sel)
@message_wrapper = @$(@oe).find(@message_wrapper_sel)
@submit_button = @$(@oe).find(@submit_button_sel)
@save_button = @$(@oe).find(@save_button_sel)
@child_state = @oe.data('state')
@child_type = @oe.data('child-type')
if @child_type=="openended"
@skip_button = @$(@oe).find(@skip_button_sel)
@skip_button.click @skip_post_assessment
@file_upload_area = @$(@oe).find(@file_upload_sel)
@can_upload_files = false
@open_ended_child= @$(@oe).find(@open_ended_child_sel)
@out_of_sync_message = 'The problem state got out of sync. Try reloading the page.'
if @task_number>1
@prompt_hide()
else if @task_number==1 and @child_state!='initial'
@prompt_hide()
@find_assessment_elements()
@find_hint_elements()
@rebind()
get_html_callback: (response) =>
@coe.replaceWith(response.html)
get_html: () =>
url = "#{@ajax_url}/get_html"
$.ajaxWithPrefix({
type: 'POST',
url: url,
data: {},
success: @get_html_callback,
async:false
});
show_combined_rubric_current: () =>
data = {}
$.postWithPrefix "#{@ajax_url}/get_combined_rubric", data, (response) =>
if response.success
@combined_rubric_container.after(response.html).remove()
@combined_rubric_container= @$(@combined_rubric_sel)
@toggle_rubric("")
@rubric_collapse = @$(@rubric_collapse_sel)
@rubric_collapse.click @toggle_rubric
@hide_rubrics()
@$(@previous_rubric_sel).click @previous_rubric
@$(@next_rubric_sel).click @next_rubric
if response.hide_reset
@reset_button.hide()
message_post: (event)=>
external_grader_message=$(event.target).parent().parent().parent()
evaluation_scoring = $(event.target).parent()
fd = new FormData()
feedback = @$(evaluation_scoring).find(@fof_sel)[0].value
submission_id = @$(external_grader_message).find(@sub_id_sel)[0].value
grader_id = @$(external_grader_message).find(@grader_id_sel)[0].value
score = @$(evaluation_scoring).find("input:radio[name='evaluation-score']:checked").val()
fd.append('feedback', feedback)
fd.append('submission_id', submission_id)
fd.append('grader_id', grader_id)
if(!score)
###
Translators: A "rating" is a score a student gives to indicate how well
they feel they were graded on this problem
###
@gentle_alert gettext "You need to pick a rating before you can submit."
return
else
fd.append('score', score)
settings =
type: "POST"
data: fd
processData: false
contentType: false
success: (response) =>
@gentle_alert response.msg
@$('section.evaluation').slideToggle()
@message_wrapper.html(response.message_html)
$.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings)
rebind: () =>
# rebind to the appropriate function for the current state
@submit_button.unbind('click')
@submit_button.show()
@save_button.unbind('click')
@save_button.hide()
@reset_button.hide()
@hide_file_upload()
@next_problem_button.hide()
@hint_area.attr('disabled', false)
if @task_number==1 and @child_state=='assessing'
@prompt_hide()
if @child_state == 'done'
@rubric_wrapper.hide()
if @child_type=="openended"
@skip_button.hide()
if @allow_reset=="True"
@show_combined_rubric_current()
@reset_button.show()
@submit_button.hide()
@answer_area.attr("disabled", true)
@replace_text_inputs()
@hint_area.attr('disabled', true)
if @task_number<@task_count
###
Translators: this message appears when transitioning between openended grading
types (i.e. self assesment to peer assessment). Sometimes, if a student
did not perform well at one step, they cannot move on to the next one.
###
@gentle_alert gettext "Your score did not meet the criteria to move to the next step."
else if @child_state == 'initial'
@answer_area.attr("disabled", false)
@submit_button.prop('value', gettext 'Submit')
@submit_button.click @confirm_save_answer
@setup_file_upload()
@save_button.click @store_answer
@save_button.show()
else if @child_state == 'assessing'
@answer_area.attr("disabled", true)
@replace_text_inputs()
@hide_file_upload()
###
Translators: one clicks this button after one has finished filling out the grading
form for an openended assessment
###
@submit_button.prop('value', gettext 'Submit assessment')
@submit_button.click @save_assessment
@submit_button.attr("disabled",true)
if @child_type == "openended"
@submit_button.hide()
@queueing()
@grader_status = @$(@grader_status_sel)
@grader_status.html("<span class='grading'>" + (gettext "Your response has been submitted. Please check back later for your grade.") + "</span>")
else if @child_type == "selfassessment"
@setup_score_selection()
else if @child_state == 'post_assessment'
if @child_type=="openended"
@skip_button.show()
@skip_post_assessment()
@answer_area.attr("disabled", true)
@replace_text_inputs()
###
Translators: this button is clicked to submit a student's rating of
an evaluator's assessment
###
@submit_button.prop('value', gettext 'Submit post-assessment')
if @child_type=="selfassessment"
@submit_button.click @save_hint
else
@submit_button.click @message_post
else if @child_state == 'done'
@show_combined_rubric_current()
@rubric_wrapper.hide()
@answer_area.attr("disabled", true)
@replace_text_inputs()
@hint_area.attr('disabled', true)
@submit_button.hide()
if @child_type=="openended"
@skip_button.hide()
if @task_number<@task_count
@next_problem_button.show()
else
@reset_button.show()
find_assessment_elements: ->
@assessment = @$('input[name="grade-selection"]')
find_hint_elements: ->
@hint_area = @$('textarea.post_assessment')
store_answer: (event) =>
event.preventDefault()
if @child_state == 'initial'
data = {'student_answer' : @answer_area.val()}
@save_button.attr("disabled",true)
$.postWithPrefix "#{@ajax_url}/store_answer", data, (response) =>
if response.success
@gentle_alert(gettext "Answer saved, but not yet submitted.")
else
@errors_area.html(response.error)
@save_button.attr("disabled",false)
else
@errors_area.html(@out_of_sync_message)
replace_answer: (response) =>
if response.success
@rubric_wrapper.html(response.rubric_html)
@rubric_wrapper.show()
@rub = new Rubric(@coe)
@rub.initialize(@location)
@child_state = 'assessing'
@find_assessment_elements()
@answer_area.val(response.student_response)
@rebind()
answer_area_div = @$(@answer_area_div_sel)
answer_area_div.html(response.student_response)
else
@submit_button.show()
@submit_button.attr('disabled', false)
@gentle_alert response.error
confirm_save_answer: (event) =>
###
Translators: This string appears in a confirmation box after one tries to submit
an openended problem
###
confirmation_text = gettext 'Please confirm that you wish to submit your work. You will not be able to make any changes after submitting.'
accessible_confirm confirmation_text, =>
@save_answer(event)
save_answer: (event) =>
@$el.find(@oe_alert_sel).remove()
@submit_button.attr("disabled",true)
@submit_button.hide()
event.preventDefault()
@answer_area.attr("disabled", true)
max_filesize = 2*1000*1000 #2MB
if @child_state == 'initial'
files = ""
valid_files_attached = false
if @can_upload_files == true
files = @$(@file_upload_box_sel)[0].files[0]
if files != undefined
valid_files_attached = true
if files.size > max_filesize
files = ""
# Don't submit the file in the case of it being too large, deal with the error locally.
@submit_button.show()
@submit_button.attr('disabled', false)
@gentle_alert gettext "You are trying to upload a file that is too large for our system. Please choose a file under 2MB or paste a link to it into the answer box."
return
fd = new FormData()
fd.append('student_answer', @answer_area.val())
fd.append('student_file', files)
fd.append('valid_files_attached', valid_files_attached)
that=this
settings =
type: "POST"
data: fd
processData: false
contentType: false
async: false
success: (response) =>
@replace_answer(response)
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
else
@errors_area.html(@out_of_sync_message)
keydown_handler: (event) =>
# Previously, responses were submitted when hitting enter. Add in a modifier that ensures that ctrl+enter is needed.
if event.which == 17 && @is_ctrl==false
@is_ctrl=true
else if @is_ctrl==true && event.which == 13 && @child_state == 'assessing' && @rub.check_complete()
@save_assessment(event)
keyup_handler: (event) =>
# Handle keyup event when ctrl key is released
if event.which == 17 && @is_ctrl==true
@is_ctrl=false
save_assessment: (event) =>
@submit_button.attr("disabled",true)
@submit_button.hide()
event.preventDefault()
if @child_state == 'assessing' && @rub.check_complete()
checked_assessment = @rub.get_total_score()
score_list = @rub.get_score_list()
data = {'assessment' : checked_assessment, 'score_list' : score_list}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success
@child_state = response.state
if @child_state == 'post_assessment'
@hint_wrapper.html(response.hint_html)
@find_hint_elements()
else if @child_state == 'done'
@rubric_wrapper.hide()
@rebind()
else
@gentle_alert response.error
else
@errors_area.html(@out_of_sync_message)
save_hint: (event) =>
event.preventDefault()
if @child_state == 'post_assessment'
data = {'hint' : @hint_area.val()}
$.postWithPrefix "#{@ajax_url}/save_post_assessment", data, (response) =>
if response.success
@message_wrapper.html(response.message_html)
@child_state = 'done'
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html(@out_of_sync_message)
skip_post_assessment: =>
if @child_state == 'post_assessment'
$.postWithPrefix "#{@ajax_url}/skip_post_assessment", {}, (response) =>
if response.success
@child_state = 'done'
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html(@out_of_sync_message)
confirm_reset: (event) =>
message = gettext 'Are you sure you want to remove your previous response to this question?'
accessible_confirm message, =>
@reset(event)
reset: (event) =>
event.preventDefault()
if @child_state == 'done' or @allow_reset=="True"
$.postWithPrefix "#{@ajax_url}/reset", {}, (response) =>
if response.success
@answer_area.val('')
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@child_state = 'initial'
@coe.after(response.html).remove()
@allow_reset="False"
@reinitialize(@element)
@has_been_reset = true
@rebind()
@reset_button.hide()
else
@errors_area.html(response.error)
else
@errors_area.html(@out_of_sync_message)
next_problem: =>
if @child_state == 'done'
$.postWithPrefix "#{@ajax_url}/next_problem", {}, (response) =>
if response.success
@answer_area.val('')
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@child_state = 'initial'
@coe.after(response.html).remove()
@reinitialize(@element)
@rebind()
@next_problem_button.hide()
if !response.allow_reset
@gentle_alert gettext "Moved to next step."
else
###
Translators: this message appears when transitioning between openended grading
types (i.e. self assesment to peer assessment). Sometimes, if a student
did not perform well at one step, they cannot move on to the next one.
###
@gentle_alert gettext "Your score did not meet the criteria to move to the next step."
@show_combined_rubric_current()
else
@errors_area.html(response.error)
else
@errors_area.html(@out_of_sync_message)
gentle_alert: (msg) =>
if @$el.find(@oe_alert_sel).length
@$el.find(@oe_alert_sel).remove()
alert_elem = "<div class='open-ended-alert' role='alert'>" + msg + "</div>"
@$el.find('.open-ended-action').after(alert_elem)
@$el.find(@oe_alert_sel).css(opacity: 0).animate(opacity: 1, 700)
queueing: =>
if @child_state=="assessing" and @child_type=="openended"
if window.queuePollerID # Only one poller 'thread' per Problem
window.clearTimeout(window.queuePollerID)
window.queuePollerID = window.setTimeout(@poll, 10000)
poll: =>
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
if response.state == "done" or response.state=="post_assessment"
delete window.queuePollerID
@reload()
else
window.queuePollerID = window.setTimeout(@poll, 10000)
setup_file_upload: =>
if @accept_file_upload == "True"
if window.File and window.FileReader and window.FileList and window.Blob
@can_upload_files = true
@file_upload_area.html('<input type="file" class="file-upload-box"><img class="file-upload-preview" src="#" alt="Uploaded image" />')
@file_upload_area.show()
@$(@file_upload_preview_sel).hide()
@$(@file_upload_box_sel).change @preview_image
else
@gentle_alert gettext 'File uploads are required for this question, but are not supported in your browser. Try the newest version of Google Chrome. Alternatively, if you have uploaded the image to another website, you can paste a link to it into the answer box.'
hide_file_upload: =>
if @accept_file_upload == "True"
@file_upload_area.hide()
replace_text_inputs: =>
answer_class = @answer_area.attr('class')
answer_id = @answer_area.attr('id')
answer_val = @answer_area.val()
new_text = ''
new_text = "<div class='#{answer_class}' id='#{answer_id}'>#{answer_val}</div>"
@answer_area.replaceWith(new_text)
# wrap this so that it can be mocked
reload: ->
@reinitialize()
collapse_question: (event) =>
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
if @prompt_container.hasClass('open')
###
Translators: "Show Question" is some text that, when clicked, shows a question's
content that had been hidden
###
new_text = gettext "Show Question"
Logger.log 'oe_show_question', {location: @location}
else
###
Translators: "Hide Question" is some text that, when clicked, hides a question's
content
###
Logger.log 'oe_hide_question', {location: @location}
new_text = gettext "Hide Question"
@question_header.text(new_text)
return false
hide_rubrics: () =>
rubrics = @$(@combined_rubric_sel)
for rub in rubrics
if @$(rub).data('status')=="shown"
@$(rub).show()
else
@$(rub).hide()
next_rubric: =>
@shift_rubric(1)
return false
previous_rubric: =>
@shift_rubric(-1)
return false
shift_rubric: (i) =>
rubrics = @$(@combined_rubric_sel)
number = 0
for rub in rubrics
if @$(rub).data('status')=="shown"
number = @$(rub).data('number')
@$(rub).data('status','hidden')
if i==1 and number < rubrics.length - 1
number = number + i
if i==-1 and number>0
number = number + i
@$(rubrics[number]).data('status', 'shown')
@hide_rubrics()
prompt_show: () =>
if @prompt_container.is(":hidden")==true
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
@question_header.text(gettext "Hide Question")
prompt_hide: () =>
if @prompt_container.is(":visible")==true
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
@question_header.text(gettext "Show Question")
log_feedback_click: (event) ->
target = @$(event.target)
if target.hasClass('see-full-feedback')
Logger.log 'oe_show_full_feedback', {}
else if target.hasClass('respond-to-feedback')
Logger.log 'oe_show_respond_to_feedback', {}
else
generated_event_type = link_text.toLowerCase().replace(" ","_")
Logger.log "oe_" + generated_event_type, {}
log_feedback_selection: (event) ->
target_selection = @$(event.target).val()
Logger.log 'oe_feedback_response_selected', {value: target_selection}
remove_attribute: (name) =>
if @$(@file_upload_preview_sel).attr(name)
@$(@file_upload_preview_sel)[0].removeAttribute(name)
preview_image: () =>
if @$(@file_upload_box_sel)[0].files && @$(@file_upload_box_sel)[0].files[0]
reader = new FileReader()
reader.onload = (e) =>
max_dim = 150
@remove_attribute('src')
@remove_attribute('height')
@remove_attribute('width')
@$(@file_upload_preview_sel).attr('src', e.target.result)
height_px = @$(@file_upload_preview_sel)[0].height
width_px = @$(@file_upload_preview_sel)[0].width
scale_factor = 0
if height_px>width_px
scale_factor = height_px/max_dim
else
scale_factor = width_px/max_dim
@$(@file_upload_preview_sel)[0].width = width_px/scale_factor
@$(@file_upload_preview_sel)[0].height = height_px/scale_factor
@$(@file_upload_preview_sel).show()
reader.readAsDataURL(@$(@file_upload_box_sel)[0].files[0])
toggle_rubric: (event) =>
info_rubric_elements = @$(@info_rubric_elements_sel)
info_rubric_elements.slideToggle()
return false
setup_score_selection: () =>
@$("input[class='score-selection']").change @graded_callback
graded_callback: () =>
if @rub.check_complete()
@submit_button.attr("disabled",false)
@submit_button.show()
class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor
# TODO really, these templates should come from or also feed the cheatsheet
@rubricTemplate : """
[rubric]
+ Ideas
- Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.
- Attempts a main idea. Sometimes loses focus or ineffectively displays focus.
- Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.
- Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.
+ Content
- Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.
- Includes little information and few or no details. Explores only one or two facets of the topic.
- Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.
- Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.
+ Organization
- Ideas organized illogically, transitions weak, and response difficult to follow.
- Attempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions.
- Ideas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions.
+ Style
- Contains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns.
- Contains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns).
- Includes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences.
+ Voice
- Demonstrates language and tone that may be inappropriate to task and reader.
- Demonstrates an attempt to adjust language and tone to task and reader.
- Demonstrates effective adjustment of language and tone to task and reader.
[rubric]
"""
@tasksTemplate: "[tasks]\n(Self), ({4-12}AI), ({9-12}Peer)\n[tasks]\n"
@promptTemplate: """
[prompt]\n
<h3>Censorship in the Libraries</h3>
<p>'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
</p>
<p>
Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
</p>
[prompt]\n
"""
constructor: (element) ->
@element = element
if $(".markdown-box", @element).length != 0
@markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
lineWrapping: true
mode: null
})
@setCurrentEditor(@markdown_editor)
selection = @markdown_editor.getSelection()
#Auto-add in the needed template if it isn't already in there.
if(@markdown_editor.getValue() == "")
@markdown_editor.setValue(OpenEndedMarkdownEditingDescriptor.promptTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.rubricTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.tasksTemplate)
# Add listeners for toolbar buttons (only present for markdown editor)
@element.on('click', '.xml-tab', @onShowXMLButton)
@element.on('click', '.format-buttons a', @onToolbarButton)
@element.on('click', '.cheatsheet-toggle', @toggleCheatsheet)
# Hide the XML text area
$(@element.find('.xml-box')).hide()
else
@createXMLEditor()
@alertTaskRubricModification()
###
Creates the XML Editor and sets it as the current editor. If text is passed in,
it will replace the text present in the HTML template.
text: optional argument to override the text passed in via the HTML template
###
createXMLEditor: (text) ->
@xml_editor = CodeMirror.fromTextArea($(".xml-box", @element)[0], {
mode: "xml"
lineNumbers: true
lineWrapping: true
})
if text
@xml_editor.setValue(text)
@setCurrentEditor(@xml_editor)
$(@xml_editor.getWrapperElement()).toggleClass("CodeMirror-advanced");
# Need to refresh to get line numbers to display properly.
@xml_editor.refresh()
###
User has clicked to show the XML editor. Before XML editor is swapped in,
the user will need to confirm the one-way conversion.
###
onShowXMLButton: (e) =>
e.preventDefault();
if @cheatsheet && @cheatsheet.hasClass('shown')
@cheatsheet.toggleClass('shown')
@toggleCheatsheetVisibility()
if @confirmConversionToXml()
@createXMLEditor(OpenEndedMarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()))
# Put cursor position to 0.
@xml_editor.setCursor(0)
# Hide markdown-specific toolbar buttons
$(@element.find('.editor-bar')).hide()
alertTaskRubricModification: ->
return alert("Before you edit, please note that if you alter the tasks block or the rubric block of this question after students have submitted responses, it may result in their responses and grades being deleted! Use caution when altering problems that have already been released to students.")
###
Have the user confirm the one-way conversion to XML.
Returns true if the user clicked OK, else false.
###
confirmConversionToXml: ->
# TODO: use something besides a JavaScript confirm dialog?
return confirm("If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?")
###
Event listener for toolbar buttons (only possible when markdown editor is visible).
###
onToolbarButton: (e) =>
e.preventDefault();
selection = @markdown_editor.getSelection()
revisedSelection = null
switch $(e.currentTarget).attr('class')
when "rubric-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric(selection)
when "prompt-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt(selection)
when "tasks-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks(selection)
else # ignore click
if revisedSelection != null
@markdown_editor.replaceSelection(revisedSelection)
@markdown_editor.focus()
###
Event listener for toggling cheatsheet (only possible when markdown editor is visible).
###
toggleCheatsheet: (e) =>
e.preventDefault();
if !$(@markdown_editor.getWrapperElement()).find('.simple-editor-open-ended-cheatsheet')[0]
@cheatsheet = $($('#simple-editor-open-ended-cheatsheet').html())
$(@markdown_editor.getWrapperElement()).append(@cheatsheet)
@toggleCheatsheetVisibility()
setTimeout (=> @cheatsheet.toggleClass('shown')), 10
###
Function to toggle cheatsheet visibility.
###
toggleCheatsheetVisibility: () =>
$('.modal-content').toggleClass('cheatsheet-is-shown')
###
Stores the current editor and hides the one that is not displayed.
###
setCurrentEditor: (editor) ->
if @current_editor
$(@current_editor.getWrapperElement()).hide()
@current_editor = editor
$(@current_editor.getWrapperElement()).show()
$(@current_editor).focus();
###
Called when save is called. Listeners are unregistered because editing the block again will
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
when cancel is called the instance of the descriptor is reused if edit is selected again.
###
save: ->
@element.off('click', '.xml-tab', @changeEditor)
@element.off('click', '.format-buttons a', @onToolbarButton)
@element.off('click', '.cheatsheet-toggle', @toggleCheatsheet)
if @current_editor == @markdown_editor
{
data: OpenEndedMarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())
metadata:
markdown: @markdown_editor.getValue()
}
else
{
data: @xml_editor.getValue()
nullout: ['markdown']
}
@insertRubric: (selectedText) ->
return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[rubric]', '[rubric]', OpenEndedMarkdownEditingDescriptor.rubricTemplate)
@insertPrompt: (selectedText) ->
return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[prompt]', '[prompt]', OpenEndedMarkdownEditingDescriptor.promptTemplate)
@insertTasks: (selectedText) ->
return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[tasks]', '[tasks]', OpenEndedMarkdownEditingDescriptor.tasksTemplate)
@insertGenericInput: (selectedText, lineStart, lineEnd, template) ->
if selectedText.length > 0
new_string = selectedText.replace(/^\s+|\s+$/g,'')
if new_string.substring(0,lineStart.length) != lineStart
new_string = lineStart + new_string
if new_string.substring((new_string.length)-lineEnd.length,new_string.length) != lineEnd
new_string = new_string + lineEnd
return new_string
else
return template
@markdownToXml: (markdown)->
toXml = `function(markdown) {
function template(template_html,data){
return template_html.replace(/%(\w*)%/g,function(m,key){return data.hasOwnProperty(key)?data[key]:"";});
}
var xml = markdown;
// group rubrics
xml = xml.replace(/\[rubric\]\n?([^\]]*)\[\/?rubric\]/gmi, function(match, p) {
var groupString = '<rubric>\n<rubric>\n';
var options = p.split('\n');
var category_open = false;
for(var i = 0; i < options.length; i++) {
if(options[i].length > 0) {
var value = options[i].replace(/^\s+|\s+$/g,'');
if (value.charAt(0)=="+") {
if(i>0){
if(category_open==true){
groupString += "</category>\n";
category_open = false;
}
}
groupString += "<category>\n<description>\n";
category_open = true;
text = value.substr(1);
text = text.replace(/^\s+|\s+$/g,'');
groupString += text;
groupString += "\n</description>\n";
} else if (value.charAt(0) == "-") {
groupString += "<option>\n";
text = value.substr(1);
text = text.replace(/^\s+|\s+$/g,'');
groupString += text;
groupString += "\n</option>\n";
}
}
if(i==options.length-1 && category_open == true){
groupString += "\n</category>\n";
}
}
groupString += '</rubric>\n</rubric>\n';
return groupString;
});
// group tasks
xml = xml.replace(/\[tasks\]\n?([^\]]*)\[\/?tasks\]/gmi, function(match, p) {
var open_ended_template = $('#open-ended-template').html();
if(open_ended_template == null) {
open_ended_template = "<openended %min_max_string%>%grading_config%</openended>";
}
var groupString = '';
var options = p.split(",");
for(var i = 0; i < options.length; i++) {
if(options[i].length > 0) {
var value = options[i].replace(/^\s+|\s+$/g,'');
var lower_option = value.toLowerCase();
type = lower_option.match(/(peer|self|ai)/gmi)
if(type != null) {
type = type[0]
var min_max = value.match(/\{\n?([^\]]*)\}/gmi);
var min_max_string = "";
if(min_max!=null) {
min_max = min_max[0].replace(/^{|}/gmi,'');
min_max = min_max.split("-");
min = min_max[0];
max = min_max[1];
min_max_string = 'min_score_to_attempt="' + min + '" max_score_to_attempt="' + max + '" ';
}
groupString += "<task>\n"
if(type=="self") {
groupString +="<selfassessment" + min_max_string + "/>"
} else if (type=="peer") {
config = "peer_grading.conf"
groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
} else if (type=="ai") {
config = "ml_grading.conf"
groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
}
groupString += "</task>\n"
}
}
}
return groupString;
});
// replace prompts
xml = xml.replace(/\[prompt\]\n?([^\]]*)\[\/?prompt\]/gmi, function(match, p1) {
var selectString = '<prompt>\n' + p1 + '\n</prompt>';
return selectString;
});
// rid white space
xml = xml.replace(/\n\n\n/g, '\n');
// surround w/ combinedopenended tag
xml = '<combinedopenended>\n' + xml + '\n</combinedopenended>';
return xml;
}
`
return toXml markdown
# This is a simple class that just hides the error container
# and message container when they are empty
# Can (and should be) expanded upon when our problem list
# becomes more sophisticated
class @PeerGrading
peer_grading_sel: '.peer-grading'
peer_grading_container_sel: '.peer-grading-container'
error_container_sel: '.error-container'
message_container_sel: '.message-container'
problem_button_sel: '.problem-button'
problem_list_sel: '.problem-list'
progress_bar_sel: '.progress-bar'
constructor: (element) ->
@el = element
@peer_grading_container = @$(@peer_grading_sel)
@use_single_location = @peer_grading_container.data('use-single-location')
@peer_grading_outer_container = @$(@peer_grading_container_sel)
@ajax_url = @peer_grading_container.data('ajax-url')
if @use_single_location.toLowerCase() == "true"
#If the peer grading element is linked to a single location, then activate the backend for that location
@activate_problem()
else
#Otherwise, activate the panel view.
@error_container = @$(@error_container_sel)
@error_container.toggle(not @error_container.is(':empty'))
@message_container = @$(@message_container_sel)
@message_container.toggle(not @message_container.is(':empty'))
@problem_button = @$(@problem_button_sel)
@problem_button.click @show_results
@problem_list = @$(@problem_list_sel)
@construct_progress_bar()
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
construct_progress_bar: () =>
problems = @problem_list.find('tr').next()
problems.each( (index, element) =>
problem = $(element)
progress_bar = problem.find(@progress_bar_sel)
bar_value = parseInt(problem.data('graded'))
bar_max = parseInt(problem.data('required')) + bar_value
progress_bar.progressbar({value: bar_value, max: bar_max})
)
show_results: (event) =>
location_to_fetch = $(event.target).data('location')
data = {'location' : location_to_fetch}
$.postWithPrefix "#{@ajax_url}problem", data, (response) =>
if response.success
@peer_grading_outer_container.after(response.html).remove()
backend = new PeerGradingProblemBackend(@ajax_url, false)
new PeerGradingProblem(backend, @el)
else
@gentle_alert response.error
activate_problem: () =>
backend = new PeerGradingProblemBackend(@ajax_url, false)
new PeerGradingProblem(backend, @el)
\ No newline at end of file
##################################
#
# This is the JS that renders the peer grading problem page.
# Fetches the correct problem and/or calibration essay
# and sends back the grades
#
# Should not be run when we don't have a location to send back
# to the server
#
# PeerGradingProblemBackend -
# makes all the ajax requests and provides a mock interface
# for testing purposes
#
# PeerGradingProblem -
# handles the rendering and user interactions with the interface
#
##################################
class @PeerGradingProblemBackend
constructor: (ajax_url, mock_backend) ->
@mock_backend = mock_backend
@ajax_url = ajax_url
@mock_cnt = 0
post: (cmd, data, callback) ->
if @mock_backend
callback(@mock(cmd, data))
else
# if this post request fails, the error callback will catch it
$.post(@ajax_url + cmd, data, callback)
.error => callback({success: false, error: "Error occurred while performing this operation"})
mock: (cmd, data) ->
if cmd == 'is_student_calibrated'
# change to test each version
response =
success: true
calibrated: @mock_cnt >= 2
else if cmd == 'show_calibration_essay'
#response =
# success: false
# error: "There was an error"
@mock_cnt++
response =
success: true
submission_id: 1
submission_key: 'abcd'
student_response: '''
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
'''
prompt: '''
<h2>S11E3: Metal Bands</h2>
<p>Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.</p>
<p>* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled? </p>
<p>This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.</p>
'''
rubric: '''
<table class="rubric"><tbody><tr><th>Purpose</th>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-0" value="0"><label for="score-0-0">No product</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-1" value="1"><label for="score-0-1">Unclear purpose or main idea</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-2" value="2"><label for="score-0-2">Communicates an identifiable purpose and/or main idea for an audience</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-3" value="3"><label for="score-0-3">Achieves a clear and distinct purpose for a targeted audience and communicates main ideas with effectively used techniques to introduce and represent ideas and insights</label>
</td>
</tr><tr><th>Organization</th>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-0" value="0"><label for="score-1-0">No product</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-1" value="1"><label for="score-1-1">Organization is unclear; introduction, body, and/or conclusion are underdeveloped, missing or confusing.</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-2" value="2"><label for="score-1-2">Organization is occasionally unclear; introduction, body or conclusion may be underdeveloped.</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-3" value="3"><label for="score-1-3">Organization is clear and easy to follow; introduction, body and conclusion are defined and aligned with purpose.</label>
</td>
</tr></tbody></table>
'''
max_score: 4
else if cmd == 'get_next_submission'
response =
success: true
submission_id: 1
submission_key: 'abcd'
student_response: '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec tristique ante. Proin at mauris sapien, quis varius leo. Morbi laoreet leo nisi. Morbi aliquam lacus ante. Cras iaculis velit sed diam mattis a fermentum urna luctus. Duis consectetur nunc vitae felis facilisis eget vulputate risus viverra. Cras consectetur ullamcorper lobortis. Nam eu gravida lorem. Nulla facilisi. Nullam quis felis enim. Mauris orci lectus, dictum id cursus in, vulputate in massa.
Phasellus non varius sem. Nullam commodo lacinia odio sit amet egestas. Donec ullamcorper sapien sagittis arcu volutpat placerat. Phasellus ut pretium ante. Nam dictum pulvinar nibh dapibus tristique. Sed at tellus mi, fringilla convallis justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique rutrum nulla sed eleifend. Praesent at nunc arcu. Mauris condimentum faucibus nibh, eget commodo quam viverra sed. Morbi in tincidunt dolor. Morbi sed augue et augue interdum fermentum.
Curabitur tristique purus ac arcu consequat cursus. Cras diam felis, dignissim quis placerat at, aliquet ac metus. Mauris vulputate est eu nibh imperdiet varius. Cras aliquet rhoncus elit a laoreet. Mauris consectetur erat et erat scelerisque eu faucibus dolor consequat. Nam adipiscing sagittis nisl, eu mollis massa tempor ac. Nulla scelerisque tempus blandit. Phasellus ac ipsum eros, id posuere arcu. Nullam non sapien arcu. Vivamus sit amet lorem justo, ac tempus turpis. Suspendisse pharetra gravida imperdiet. Pellentesque lacinia mi eu elit luctus pellentesque. Sed accumsan libero a magna elementum varius. Nunc eget pellentesque metus. '''
prompt: '''
<h2>S11E3: Metal Bands</h2>
<p>Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.</p>
<p>* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled? </p>
<p>This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.</p>
'''
rubric: '''
<table class="rubric"><tbody><tr><th>Purpose</th>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-0" value="0"><label for="score-0-0">No product</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-1" value="1"><label for="score-0-1">Unclear purpose or main idea</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-2" value="2"><label for="score-0-2">Communicates an identifiable purpose and/or main idea for an audience</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-0" id="score-0-3" value="3"><label for="score-0-3">Achieves a clear and distinct purpose for a targeted audience and communicates main ideas with effectively used techniques to introduce and represent ideas and insights</label>
</td>
</tr><tr><th>Organization</th>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-0" value="0"><label for="score-1-0">No product</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-1" value="1"><label for="score-1-1">Organization is unclear; introduction, body, and/or conclusion are underdeveloped, missing or confusing.</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-2" value="2"><label for="score-1-2">Organization is occasionally unclear; introduction, body or conclusion may be underdeveloped.</label>
</td>
<td>
<input type="radio" class="score-selection" name="score-selection-1" id="score-1-3" value="3"><label for="score-1-3">Organization is clear and easy to follow; introduction, body and conclusion are defined and aligned with purpose.</label>
</td>
</tr></tbody></table>
'''
max_score: 4
else if cmd == 'save_calibration_essay'
response =
success: true
actual_score: 2
else if cmd == 'save_grade'
response =
success: true
return response
class @PeerGradingProblem
prompt_wrapper_sel: '.prompt-wrapper'
peer_grading_container_sel: '.peer-grading-container'
submission_container_sel: '.submission-container'
prompt_container_sel: '.prompt-container'
rubric_container_sel: '.rubric-container'
flag_student_container_sel: '.flag-student-container'
calibration_panel_sel: '.calibration-panel'
grading_panel_sel: '.grading-panel'
content_panel_sel: '.content-panel'
grading_message_sel: '.grading-message'
question_header_sel: '.question-header'
flag_submission_confirmation_sel: '.flag-submission-confirmation'
flag_submission_confirmation_button_sel: '.flag-submission-confirmation-button'
flag_submission_removal_button_sel: '.flag-submission-removal-button'
grading_wrapper_sel: '.grading-wrapper'
calibration_feedback_sel: '.calibration-feedback'
interstitial_page_sel: '.interstitial-page'
calibration_interstitial_page_sel: '.calibration-interstitial-page'
error_container_sel: '.error-container'
peer_grading_instructions_sel: '.peer-grading-instructions'
feedback_area_sel: '.feedback-area'
ice_legend_sel: '.ice-legend'
score_selection_container_sel: '.score-selection-container'
rubric_selection_container_sel: '.rubric-selection-container'
submit_button_sel: '.submit-button'
action_button_sel: '.action-button'
calibration_feedback_button_sel: '.calibration-feedback-button'
interstitial_page_button_sel: '.interstitial-page-button'
calibration_interstitial_page_button_sel: '.calibration-interstitial-page-button'
flag_checkbox_sel: '.flag-checkbox'
calibration_text_sel: '.calibration-text'
grading_text_sel: '.grading-text'
calibration_feedback_wrapper_sel: '.calibration-feedback-wrapper'
constructor: (backend, el) ->
@el = el
@prompt_wrapper = $(@prompt_wrapper_sel)
@backend = backend
@is_ctrl = false
@el = $(@peer_grading_container_sel)
# get the location of the problem
@location = $('.peer-grading').data('location')
# prevent this code from trying to run
# when we don't have a location
if(!@location)
return
# get the other elements we want to fill in
@submission_container = @$(@submission_container_sel)
@prompt_container = @$(@prompt_container_sel)
@rubric_container = @$(@rubric_container_sel)
@flag_student_container = @$(@flag_student_container_sel)
@calibration_panel = @$(@calibration_panel_sel)
@grading_panel = @$(@grading_panel_sel)
@content_panel = @$(@content_panel_sel)
@grading_message = @$(@grading_message_sel)
@grading_message.hide()
@question_header = @$(@question_header_sel)
@question_header.click @collapse_question
@flag_submission_confirmation = @$(@flag_submission_confirmation_sel)
@flag_submission_confirmation_button = @$(@flag_submission_confirmation_button_sel)
@flag_submission_removal_button = @$(@flag_submission_removal_button_sel)
@flag_submission_confirmation_button.click @close_dialog_box
@flag_submission_removal_button.click @remove_flag
@grading_wrapper = @$(@grading_wrapper_sel)
@calibration_feedback_panel = @$(@calibration_feedback_sel)
@interstitial_page = @$(@interstitial_page_sel)
@interstitial_page.hide()
@calibration_interstitial_page = @$(@calibration_interstitial_page_sel)
@calibration_interstitial_page.hide()
@error_container = @$(@error_container_sel)
@submission_key_input = $("input[name='submission-key']")
@essay_id_input = @$("input[name='essay-id']")
@peer_grading_instructions = @$(@peer_grading_instructions_sel)
@feedback_area = @$(@feedback_area_sel)
@ice_legend = @$(@ice_legend_sel)
@score_selection_container = @$(@score_selection_container_sel)
@rubric_selection_container = @$(@rubric_selection_container_sel)
@grade = null
@calibration = null
@submit_button = @$(@submit_button_sel)
@action_button = @$(@action_button_sel)
@calibration_feedback_button = @$(@calibration_feedback_button_sel)
@interstitial_page_button = @$(@interstitial_page_button_sel)
@calibration_interstitial_page_button = @$(@calibration_interstitial_page_button_sel)
@flag_student_checkbox = @$(@flag_checkbox_sel)
$(window).keydown @keydown_handler
$(window).keyup @keyup_handler
Collapsible.setCollapsibles(@content_panel)
# Set up the click event handlers
@action_button.click -> history.back()
@calibration_feedback_button.click =>
@calibration_feedback_panel.hide()
@grading_wrapper.show()
@gentle_alert "Calibration essay saved. Fetching the next essay."
@is_calibrated_check()
@interstitial_page_button.click =>
@interstitial_page.hide()
@is_calibrated_check()
@calibration_interstitial_page_button.click =>
@calibration_interstitial_page.hide()
@is_calibrated_check()
@flag_student_checkbox.click =>
@flag_box_checked()
@calibration_feedback_button.hide()
@calibration_feedback_panel.hide()
@error_container.hide()
@flag_submission_confirmation.hide()
if @tracking_changes()
@change_tracker = new TrackChanges(@el)
@is_calibrated_check()
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
##########
#
# Ajax calls to the backend
#
##########
is_calibrated_check: () =>
@backend.post('is_student_calibrated', {location: @location}, @calibration_check_callback)
fetch_calibration_essay: () =>
@backend.post('show_calibration_essay', {location: @location}, @render_calibration)
fetch_submission_essay: () =>
@backend.post('get_next_submission', {location: @location}, @render_submission)
construct_data: () ->
if @tracking_changes()
feedback_content = @feedback_area.html()
else
feedback_content = @feedback_area.val()
data =
rubric_scores: @rub.get_score_list()
score: @rub.get_total_score()
location: @location
submission_id: @essay_id_input.val()
submission_key: @submission_key_input.val()
feedback: feedback_content
submission_flagged: @flag_student_checkbox.is(':checked')
# hardcoding answer_unknown to false
answer_unknown: false
return data
submit_calibration_essay: ()=>
data = @construct_data()
@submit_button.hide()
@backend.post('save_calibration_essay', data, @calibration_callback)
submit_grade: () =>
data = @construct_data()
@submit_button.hide()
@backend.post('save_grade', data, @submission_callback)
##########
#
# Callbacks for various events
#
##########
remove_flag: () =>
@flag_student_checkbox.removeAttr("checked")
@close_dialog_box()
@submit_button.attr('disabled', true)
close_dialog_box: () =>
$(@flag_submission_confirmation_sel).dialog('close')
flag_box_checked: () =>
if @flag_student_checkbox.is(':checked')
@$(@flag_submission_confirmation_sel).dialog({ height: 400, width: 400 })
@submit_button.attr('disabled', false)
# called after we perform an is_student_calibrated check
calibration_check_callback: (response) =>
if response.success
# if we haven't been calibrating before
if response.calibrated and (@calibration == null or @calibration == false)
@calibration = false
@fetch_submission_essay()
# If we were calibrating before and no longer need to,
# show the interstitial page
else if response.calibrated and @calibration == true
@calibration = false
@render_interstitial_page()
else if not response.calibrated and @calibration==null
@calibration=true
@render_calibration_interstitial_page()
else
@calibration = true
@fetch_calibration_essay()
else if response.error
@render_error(response.error)
else
@render_error("Error contacting the grading service")
# called after we submit a calibration score
calibration_callback: (response) =>
if response.success
@render_calibration_feedback(response)
else if response.error
@render_error(response.error)
else
@render_error("Error saving calibration score")
# called after we submit a submission score
submission_callback: (response) =>
if response.success
@is_calibrated_check()
@grading_message.fadeIn()
message = "<p>Successfully saved your feedback. Fetching the next essay."
if response.required_done
message = message + " You have done the required number of peer assessments but may continue grading if you like."
message = message + "</p>"
@grading_message.html(message)
else
if response.error
@render_error(response.error)
else
@render_error("Error occurred while submitting grade")
# called after a grade is selected on the interface
graded_callback: (event) =>
ev = @$(event.target).parent().parent()
ul = ev.parent().parent()
ul.find(".rubric-label-selected").removeClass('rubric-label-selected')
ev.addClass('rubric-label-selected')
# check to see whether or not any categories have not been scored
if @rub.check_complete()
# show button if we have scores for all categories
@grading_message.hide()
@show_submit_button()
@grade = @rub.get_total_score()
keydown_handler: (event) =>
#Previously, responses were submitted when hitting enter. Add in a modifier that ensures that ctrl+enter is needed.
if event.which == 17 && @is_ctrl==false
@is_ctrl=true
else if event.which == 13 && @submit_button.is(':visible') && @is_ctrl==true
if @calibration
@submit_calibration_essay()
else
@submit_grade()
keyup_handler: (event) =>
#Handle keyup event when ctrl key is released
if event.which == 17 && @is_ctrl==true
@is_ctrl=false
##########
#
# Rendering methods and helpers
#
##########
# renders a calibration essay
render_calibration: (response) =>
if response.success
# load in all the data
@submission_container.html("")
@render_submission_data(response)
# TODO: indicate that we're in calibration mode
@calibration_panel.addClass('current-state')
@grading_panel.removeClass('current-state')
# Display the right text
# both versions of the text are written into the template itself
# we only need to show/hide the correct ones at the correct time
@calibration_panel.find(@calibration_text_sel).show()
@grading_panel.find(@calibration_text_sel).show()
@calibration_panel.find(@grading_text_sel).hide()
@grading_panel.find(@grading_text_sel).hide()
@flag_student_container.hide()
@peer_grading_instructions.hide()
@feedback_area.attr('disabled', true)
feedback_text = "Once you are done learning to grade, and are grading your peers' work, you will be asked to share written feedback with them in addition to scoring them."
if @tracking_changes()
@ice_legend.hide()
@feedback_area.attr('contenteditable', false)
@feedback_area.text(feedback_text)
else
@feedback_area.val(feedback_text)
@submit_button.show()
@submit_button.unbind('click')
@submit_button.click @submit_calibration_essay
@submit_button.attr('disabled', true)
@scroll_to_top()
else if response.error
@render_error(response.error)
else
@render_error("An error occurred while retrieving the next calibration essay")
tracking_changes: () =>
return @grading_wrapper.data('track-changes') == true
# Renders a student submission to be graded
render_submission: (response) =>
if response.success
@submit_button.hide()
@submission_container.html("")
@render_submission_data(response)
@calibration_panel.removeClass('current-state')
@grading_panel.addClass('current-state')
# Display the correct text
# both versions of the text are written into the template itself
# we only need to show/hide the correct ones at the correct time
@calibration_panel.find(@calibration_text_sel).hide()
@grading_panel.find(@calibration_text_sel).hide()
@calibration_panel.find(@grading_text_sel).show()
@grading_panel.find(@grading_text_sel).show()
@flag_student_container.show()
@peer_grading_instructions.show()
if @tracking_changes()
@ice_legend.show()
@feedback_area.html(@make_paragraphs(response.student_response))
@change_tracker.rebindTracker()
else
@feedback_area.val("")
@feedback_area.attr('disabled', false)
@flag_student_checkbox.removeAttr("checked")
@submit_button.show()
@submit_button.unbind('click')
@submit_button.click @submit_grade
@submit_button.attr('disabled', true)
@scroll_to_top()
else if response.error
@render_error(response.error)
else
@render_error("An error occurred when retrieving the next submission.")
make_paragraphs: (text) ->
paragraph_split = text.split(/\n\s*\n/)
new_text = ''
for paragraph in paragraph_split
new_text += "<p>#{paragraph}</p>"
return new_text
# render common information between calibration and grading
render_submission_data: (response) =>
@content_panel.show()
@error_container.hide()
@submission_container.append(@make_paragraphs(response.student_response))
@prompt_container.html(response.prompt)
@rubric_selection_container.html(response.rubric)
@submission_key_input.val(response.submission_key)
@essay_id_input.val(response.submission_id)
@setup_score_selection(response.max_score)
@submit_button.hide()
@action_button.hide()
@calibration_feedback_panel.hide()
@rub = new Rubric(@el)
@rub.initialize(@location)
render_calibration_feedback: (response) =>
# display correct grade
@calibration_feedback_panel.slideDown()
calibration_wrapper = @$(@calibration_feedback_wrapper_sel)
calibration_wrapper.html("<p>The score you gave was: #{@grade}. The instructor score is: #{response.actual_score}</p>")
score = parseInt(@grade)
actual_score = parseInt(response.actual_score)
if score == actual_score
calibration_wrapper.append("<p>Your score matches the instructor score!</p>")
else
calibration_wrapper.append("<p>You may want to review the rubric again.</p>")
if response.actual_rubric != undefined
calibration_wrapper.append("<div>Instructor Scored Rubric: #{response.actual_rubric}</div>")
if response.actual_feedback.feedback!=undefined
calibration_wrapper.append("<div>Instructor Feedback: #{response.actual_feedback}</div>")
# disable score selection and submission from the grading interface
@$("input[name='score-selection']").attr('disabled', true)
@submit_button.hide()
@calibration_feedback_button.show()
render_interstitial_page: () =>
@content_panel.hide()
@grading_message.hide()
@interstitial_page.show()
render_calibration_interstitial_page: () =>
@content_panel.hide()
@action_button.hide()
@calibration_interstitial_page.show()
render_error: (error_message) =>
@error_container.show()
@calibration_feedback_panel.hide()
@error_container.html(error_message)
@content_panel.hide()
@action_button.show()
show_submit_button: () =>
@submit_button.attr('disabled', false)
@submit_button.show()
setup_score_selection: (max_score) =>
# And now hook up an event handler again
@$("input[class='score-selection']").change @graded_callback
gentle_alert: (msg) =>
@grading_message.fadeIn()
@grading_message.html("<p>" + msg + "</p>")
collapse_question: (event) =>
@prompt_container.slideToggle()
@prompt_container.toggleClass('open')
if @question_header.text() == "Hide Question"
new_text = "Show Question"
Logger.log 'oe_hide_question', {location: @location}
else
Logger.log 'oe_show_question', {location: @location}
new_text = "Hide Question"
@question_header.text(new_text)
return false
scroll_to_top: () =>
$('html, body').animate({
scrollTop: $(".peer-grading").offset().top
}, 200)
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