From 0ec7043e0580dfed57726faf5d6f8177af52ad64 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 12 Nov 2012 12:14:12 -0500 Subject: [PATCH] mostly working version of self-assessment TODO: * better css * green checkmark * reset button * tests --- .../js/src/selfassessment/display.coffee | 86 ++++++++++++----- .../xmodule/xmodule/self_assessment_module.py | 92 ++++++++++--------- lms/templates/self_assessment_prompt.html | 3 +- 3 files changed, 114 insertions(+), 67 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index e6255ae9e8..dbfd047a11 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -3,44 +3,88 @@ class @SelfAssessment @el = $(element).find('section.self-assessment') @id = @el.data('id') @ajax_url = @el.data('ajax-url') + @state = @el.data('state') + # valid states: 'initial', 'assessing', 'request_hint', 'done' # Where to put the rubric once we load it - @rubric_wrapper = @$('.rubric-wrapper') - @check_button = @$('.submit-button') - @answer_area = @$('textarea.answer') @errors_area = @$('.error') - @state = 'prompt' # switches to 'eval' after answer is submitted + @answer_area = @$('textarea.answer') + + @rubric_wrapper = @$('.rubric-wrapper') + @hint_wrapper = @$('.hint-wrapper') + @message_wrapper = @$('.message-wrapper') + @check_button = @$('.submit-button') + + @find_assessment_elements() + @find_hint_elements() + @bind() # locally scoped jquery. $: (selector) -> $(selector, @el) - bind: -> - @check_button.click @show_rubric + bind: () => + # rebind to the appropriate function for the current state + @check_button.unbind('click') + if @state == 'initial' + @check_button.click @save_answer + else if @state == 'assessing' + @check_button.click @save_assessment + else if @state == 'request_hint' + @check_button.click @save_hint + else if @state == 'done' + @check_button.hide() - find_eval_elements: -> - # find the elements we'll need from the newly loaded rubric data + find_assessment_elements: -> @assessment = @$('select.assessment') - @hint = @$('textarea.hint') - @save_message = @$('.save_message') - show_rubric: (event) => + find_hint_elements: -> + @hint_area = @$('textarea.hint') + + save_answer: (event) => event.preventDefault() - if @state == 'prompt' + if @state == 'initial' data = {'student_answer' : @answer_area.val()} - $.postWithPrefix "#{@ajax_url}/show", data, (response) => + $.postWithPrefix "#{@ajax_url}/save_answer", data, (response) => if response.success - @rubric_wrapper.html(response.rubric) - @state = 'eval' - @find_eval_elements() + @rubric_wrapper.html(response.rubric_html) + @state = 'assessing' + @find_assessment_elements() + @bind() else @errors_area.html(response.message) else - data = {'assessment' : @assessment.find(':selected').text(), 'hint' : @hint.val()} - - $.postWithPrefix "#{@ajax_url}/save", data, (response) => + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + save_assessment: (event) => + event.preventDefault() + if @state == 'assessing' + data = {'assessment' : @assessment.find(':selected').text()} + $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => if response.success - @rubric_wrapper.html(response.message) + @hint_wrapper.html(response.hint_html) + @state = 'request_hint' + @find_hint_elements() + @bind() else - @errors_area.html('There was an error saving your response.') + @errors_area.html(response.message) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + + save_hint: (event) => + event.preventDefault() + if @state == 'request_hint' + data = {'hint' : @hint_area.val()} + + $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => + if response.success + @message_wrapper.html(response.message_html) + @state = 'done' + @bind() + else + @errors_area.html(response.message) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 3f84e24386..80f88dec31 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -61,10 +61,6 @@ class SelfAssessmentModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} js_module_name = "SelfAssessment" - def get_html(self): - # cdodge: perform link substitutions for any references to course static content (e.g. images) - return rewrite_links(self.html, self.rewrite_content_links) - def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, @@ -100,9 +96,8 @@ class SelfAssessmentModule(XModule): else: instance_state = {} + # Note: assessment responses are 'incorrect'/'correct' self.student_answers = instance_state.get('student_answers', []) - - # assessment responses are 'incorrect'/'correct' self.assessment = instance_state.get('assessment', []) self.hints = instance_state.get('hints', []) @@ -111,7 +106,7 @@ class SelfAssessmentModule(XModule): # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). self.score = instance_state.get('score', 0) - self.max_score = instance_state.get('max_score', MAX_SCORE) + self._max_score = instance_state.get('max_score', MAX_SCORE) self.attempts = instance_state.get('attempts', 0) @@ -122,18 +117,23 @@ class SelfAssessmentModule(XModule): self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] + def get_html(self): + #set context variables and render template previous_answer = self.student_answers[-1] if self.student_answers else '' - #set context variables and render template context = { - 'prompt' : self.prompt, - 'previous_answer' : previous_answer, - 'ajax_url' : system.ajax_url, - 'initial_rubric' : self.get_rubric_html(), - 'initial_hint' : self.get_hint_html(), - 'initial_message' : self.get_message_html(), + 'prompt': self.prompt, + 'previous_answer': previous_answer, + 'ajax_url': self.system.ajax_url, + 'initial_rubric': self.get_rubric_html(), + 'initial_hint': self.get_hint_html(), + 'initial_message': self.get_message_html(), + 'state': self.state, } - self.html = self.system.render_template('self_assessment_prompt.html', context) + html = self.system.render_template('self_assessment_prompt.html', context) + + # cdodge: perform link substitutions for any references to course static content (e.g. images) + return rewrite_links(html, self.rewrite_content_links) def get_score(self): """ @@ -145,15 +145,15 @@ class SelfAssessmentModule(XModule): """ Return max_score """ - return self.max_score + return self._max_score def get_progress(self): ''' For now, just return score / max_score ''' - if self.max_score > 0: + if self._max_score > 0: try: - return Progress(self.score, self.max_score) + return Progress(self.score, self._max_score) except Exception as err: log.exception("Got bad progress") return None @@ -167,7 +167,7 @@ class SelfAssessmentModule(XModule): Returns a json dictionary: { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', + 'progress': 'none'/'in_progress'/'done', } """ @@ -207,7 +207,7 @@ class SelfAssessmentModule(XModule): return '' # we'll render it - context = {'rubric' : self.rubric} + context = {'rubric': self.rubric} if self.state == self.ASSESSING: context['read_only'] = False @@ -226,8 +226,9 @@ class SelfAssessmentModule(XModule): return '' # else we'll render it + hint = self.hints[-1] if len(self.hints) > 0 else '' context = {'hint_prompt': self.hint_prompt, - 'hint': self.hint} + 'hint': hint} if self.state == self.REQUEST_HINT: context['read_only'] = False @@ -245,7 +246,7 @@ class SelfAssessmentModule(XModule): if self.state != self.DONE: return "" - return """
{0}
""".format(self.message) + return """
{0}
""".format(self.submit_message) def save_answer(self, get): @@ -253,7 +254,7 @@ class SelfAssessmentModule(XModule): After the answer is submitted, show the rubric. """ # Check to see if attempts are less than max - if self.attempts < self.max_attempts: + if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and # seeing rubric. In normal use, students shouldn't see this because # they won't see the reset button once they're out of attempts. @@ -265,23 +266,23 @@ class SelfAssessmentModule(XModule): if self.state != self.INITIAL: return self.out_of_sync_error(get) - self.student_answers.append = get['student_answer'] + self.student_answers.append(get['student_answer']) self.state = self.ASSESSING return { 'success': True, - 'rubric': self.get_rubric_html() + 'rubric_html': self.get_rubric_html() } def save_assessment(self, get): """ Save the assessment. - Returns a dict { 'success' : bool, 'hint_html': hint_html 'error' : error-msg}, + Returns a dict { 'success': bool, 'hint_html': hint_html 'error': error-msg}, with 'error' only present if 'success' is False, and 'hint_html' only if success is true """ - if (self.state != self.ASSESSMENT or + if (self.state != self.ASSESSING or len(self.student_answers) != len(self.assessment) + 1): return self.out_of_sync_error(get) @@ -294,9 +295,9 @@ class SelfAssessmentModule(XModule): def save_hint(self, get): ''' Save the hint. - Returns a dict { 'success' : bool, + Returns a dict { 'success': bool, 'message_html': message_html, - 'error' : error-msg}, + 'error': error-msg}, with the error key only present if success is False and message_html only if True. ''' @@ -313,13 +314,14 @@ class SelfAssessmentModule(XModule): # To the tracking logs! event_info = { - 'selfassessment_id' : self.location.url(), + 'selfassessment_id': self.location.url(), 'state': { 'student_answers': self.student_answers, 'assessment': self.assessment, - 'hints' : self.hints, + 'hints': self.hints, 'score': points, - 'done': self.done,}} + } + } self.system.track_function('save_hint', event_info) return {'success': True, @@ -335,7 +337,7 @@ class SelfAssessmentModule(XModule): """ if self.state != DONE: return self.out_of_sync_error(get) - if self.attempts < self.max_attempts: + if self.attempts > self.max_attempts: return { 'success': False, 'error': 'Too many attempts.' @@ -353,12 +355,12 @@ class SelfAssessmentModule(XModule): state = { 'student_answers': self.student_answers, - 'temp_answer': self.temp_answer, - 'hints' : self.hints, 'assessment': self.assessment, + 'hints': self.hints, + 'state': self.state, 'score': points, - 'max_score' : MAX_SCORE, - 'attempts' : self.attempts + 'max_score': self._max_score, + 'attempts': self.attempts } return json.dumps(state) @@ -385,10 +387,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): Returns: { - 'rubric' : 'some-html', - 'prompt' : 'some-html', - 'submitmessage' : 'some-html' - 'hintprompt' : 'some-html' + 'rubric': 'some-html', + 'prompt': 'some-html', + 'submitmessage': 'some-html' + 'hintprompt': 'some-html' } """ expected_children = ['rubric', 'prompt', 'submitmessage', 'hintprompt'] @@ -400,10 +402,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """Assumes that xml_object has child k""" return stringify_children(xml_object.xpath(k)[0]) - return {'rubric' : parse('rubric'), - 'prompt' : parse('prompt'), - 'submitmessage' : parse('submitmessage'), - 'hintprompt' : parse('hintprompt'), + return {'rubric': parse('rubric'), + 'prompt': parse('prompt'), + 'submitmessage': parse('submitmessage'), + 'hintprompt': parse('hintprompt'), } diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 2d2c5779b2..f7ad908e72 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -1,4 +1,5 @@ -
+
${prompt}