Commit 111f7533 by Victor Shnayder

Display (unstyled) progress in problems inside sequences

* verticals not-in-sequences next
parent 53d0a6ad
...@@ -157,6 +157,8 @@ class LoncapaProblem(object): ...@@ -157,6 +157,8 @@ class LoncapaProblem(object):
def get_max_score(self): def get_max_score(self):
''' '''
Return maximum score for this problem. Return maximum score for this problem.
TODO (vshnayder): Is this fixed once the problem is instantiated? Can we compute it only once?
''' '''
maxscore = 0 maxscore = 0
for response, responder in self.responders.iteritems(): for response, responder in self.responders.iteritems():
...@@ -217,7 +219,7 @@ class LoncapaProblem(object): ...@@ -217,7 +219,7 @@ class LoncapaProblem(object):
# Get a list of timestamps of all queueing requests, then convert it to a DateTime object # Get a list of timestamps of all queueing requests, then convert it to a DateTime object
queuetime_strs = [self.correct_map.get_queuetime_str(answer_id) queuetime_strs = [self.correct_map.get_queuetime_str(answer_id)
for answer_id in self.correct_map for answer_id in self.correct_map
if self.correct_map.is_queued(answer_id)] if self.correct_map.is_queued(answer_id)]
queuetimes = [datetime.strptime(qt_str, xqueue_interface.dateformat) for qt_str in queuetime_strs] queuetimes = [datetime.strptime(qt_str, xqueue_interface.dateformat) for qt_str in queuetime_strs]
......
...@@ -207,15 +207,21 @@ class CapaModule(XModule): ...@@ -207,15 +207,21 @@ class CapaModule(XModule):
return None return None
def get_html(self): def get_html(self):
progress = self.get_progress()
return self.system.render_template('problem_ajax.html', { return self.system.render_template('problem_ajax.html', {
'element_id': self.location.html_id(), 'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'progress_status': Progress.to_js_status_str(progress),
'progress_detail': Progress.to_js_detail_str(progress),
}) })
def get_problem_html(self, encapsulate=True): def get_problem_html(self):
'''Return html for the problem. Adds check, reset, save buttons """
as necessary based on the problem config and state.''' Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.
"""
try: try:
html = self.lcp.get_html() html = self.lcp.get_html()
...@@ -242,11 +248,11 @@ class CapaModule(XModule): ...@@ -242,11 +248,11 @@ class CapaModule(XModule):
# check button is context-specific. # check button is context-specific.
# Put a "Check" button if unlimited attempts or still some left # Put a "Check" button if unlimited attempts or still some left
if self.max_attempts is None or self.attempts < self.max_attempts-1: if self.max_attempts is None or self.attempts < self.max_attempts-1:
check_button = "Check" check_button = "Check"
else: else:
# Will be final check so let user know that # Will be final check so let user know that
check_button = "Final Check" check_button = "Final Check"
reset_button = True reset_button = True
save_button = True save_button = True
...@@ -286,33 +292,29 @@ class CapaModule(XModule): ...@@ -286,33 +292,29 @@ class CapaModule(XModule):
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'attempts_used': self.attempts, 'attempts_used': self.attempts,
'attempts_allowed': self.max_attempts, 'attempts_allowed': self.max_attempts,
'progress': self.get_progress(),
} }
html = self.system.render_template('problem.html', context) html = self.system.render_template('problem.html', context)
if encapsulate:
html = '<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(
id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "</div>"
return self.system.replace_urls(html, self.metadata['data_dir']) return self.system.replace_urls(html, self.metadata['data_dir'])
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' """
This is called by courseware.module_render, to handle an AJAX call. This is called by courseware.module_render, to handle an AJAX call.
"get" is request.POST. "get" is request.POST.
Returns a json dictionary: Returns a json dictionary:
{ 'progress_changed' : True/False, { 'progress_changed' : True/False,
'progress' : 'none'/'in_progress'/'done', 'progress_status' : 'none'/'in_progress'/'done',
'progress_detail' : 'NA' / '0/3' / '3/8' / '21/20'
<other request-specific values here > } <other request-specific values here > }
''' """
handlers = { handlers = {
'problem_get': self.get_problem, 'problem_get': self.get_problem,
'problem_check': self.check_problem, 'problem_check': self.check_problem,
'problem_reset': self.reset_problem, 'problem_reset': self.reset_problem,
'problem_save': self.save_problem, 'problem_save': self.save_problem,
'problem_show': self.get_answer, 'problem_show': self.get_answer,
'score_update': self.update_score, 'score_update': self.update_score, # Callback for xqueue/grader, not the user's browser.
} }
if dispatch not in handlers: if dispatch not in handlers:
...@@ -324,6 +326,7 @@ class CapaModule(XModule): ...@@ -324,6 +326,7 @@ class CapaModule(XModule):
d.update({ d.update({
'progress_changed': after != before, 'progress_changed': after != before,
'progress_status': Progress.to_js_status_str(after), 'progress_status': Progress.to_js_status_str(after),
'progress_detail': Progress.to_js_detail_str(after),
}) })
return json.dumps(d, cls=ComplexEncoder) return json.dumps(d, cls=ComplexEncoder)
...@@ -414,7 +417,7 @@ class CapaModule(XModule): ...@@ -414,7 +417,7 @@ class CapaModule(XModule):
Used if we want to reconfirm we have the right thing e.g. after Used if we want to reconfirm we have the right thing e.g. after
several AJAX calls. several AJAX calls.
''' '''
return {'html': self.get_problem_html(encapsulate=False)} return {'html': self.get_problem_html()}
@staticmethod @staticmethod
def make_dict_of_responses(get): def make_dict_of_responses(get):
...@@ -467,11 +470,11 @@ class CapaModule(XModule): ...@@ -467,11 +470,11 @@ class CapaModule(XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit # Problem queued. Students must wait a specified waittime before they are allowed to submit
if self.lcp.is_queued(): if self.lcp.is_queued():
current_time = datetime.datetime.now() current_time = datetime.datetime.now()
prev_submit_time = self.lcp.get_recentmost_queuetime() prev_submit_time = self.lcp.get_recentmost_queuetime()
waittime_between_requests = self.system.xqueue['waittime'] waittime_between_requests = self.system.xqueue['waittime']
if (current_time-prev_submit_time).total_seconds() < waittime_between_requests: if (current_time-prev_submit_time).total_seconds() < waittime_between_requests:
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
try: try:
old_state = self.lcp.get_state() old_state = self.lcp.get_state()
...@@ -514,7 +517,7 @@ class CapaModule(XModule): ...@@ -514,7 +517,7 @@ class CapaModule(XModule):
self.system.psychometrics_handler(self.get_instance_state()) self.system.psychometrics_handler(self.get_instance_state())
# render problem into HTML # render problem into HTML
html = self.get_problem_html(encapsulate=False) html = self.get_problem_html()
return {'success': success, return {'success': success,
'contents': html, 'contents': html,
...@@ -587,7 +590,7 @@ class CapaModule(XModule): ...@@ -587,7 +590,7 @@ class CapaModule(XModule):
event_info['new_state'] = self.lcp.get_state() event_info['new_state'] = self.lcp.get_state()
self.system.track_function('reset_problem', event_info) self.system.track_function('reset_problem', event_info)
return {'html': self.get_problem_html(encapsulate=False)} return {'html': self.get_problem_html()}
class CapaDescriptor(RawDescriptor): class CapaDescriptor(RawDescriptor):
......
...@@ -30,6 +30,10 @@ nav.sequence-nav { ...@@ -30,6 +30,10 @@ nav.sequence-nav {
pointer-events: none; pointer-events: none;
} }
.student-progress {
display: block;
}
.sequence-list-wrapper { .sequence-list-wrapper {
position: relative; position: relative;
z-index: 99; z-index: 99;
...@@ -154,7 +158,7 @@ nav.sequence-nav { ...@@ -154,7 +158,7 @@ nav.sequence-nav {
background-image: url('../images/sequence-nav/list-unstarted.png'); background-image: url('../images/sequence-nav/list-unstarted.png');
} }
&.progress-some, &.progress-in_progress { &.progress-in_progress {
background-image: url('../images/sequence-nav/list-unfinished.png'); background-image: url('../images/sequence-nav/list-unfinished.png');
} }
......
...@@ -19,7 +19,6 @@ class @Problem ...@@ -19,7 +19,6 @@ class @Problem
@$('section.action input:button').click @refreshAnswers @$('section.action input:button').click @refreshAnswers
@$('section.action input.check').click @check_fd @$('section.action input.check').click @check_fd
#@$('section.action input.check').click @check
@$('section.action input.reset').click @reset @$('section.action input.reset').click @reset
@$('section.action input.show').click @show @$('section.action input.show').click @show
@$('section.action input.save').click @save @$('section.action input.save').click @save
...@@ -27,7 +26,8 @@ class @Problem ...@@ -27,7 +26,8 @@ class @Problem
updateProgress: (response) => updateProgress: (response) =>
if response.progress_changed if response.progress_changed
@el.attr progress: response.progress_status @el.data('progress_status', response.progress_status)
@el.data('progress_detail', response.progress_detail)
@el.trigger('progressChanged') @el.trigger('progressChanged')
queueing: => queueing: =>
......
...@@ -18,11 +18,10 @@ class @Sequence ...@@ -18,11 +18,10 @@ class @Sequence
initProgress: -> initProgress: ->
@progressTable = {} # "#problem_#{id}" -> progress @progressTable = {} # "#problem_#{id}" -> progress
hookUpProgressEvent: -> hookUpProgressEvent: ->
$('.problems-wrapper').bind 'progressChanged', @updateProgress $('.problems-wrapper').bind 'progressChanged', @updateProgress
mergeProgress: (p1, p2) -> mergeProgressStatus: (p1, p2) ->
# if either is "NA", return the other one # if either is "NA", return the other one
if p1 == "NA" if p1 == "NA"
return p2 return p2
...@@ -41,25 +40,55 @@ class @Sequence ...@@ -41,25 +40,55 @@ class @Sequence
return "none" return "none"
updateOverallScore: (details) =>
# Given a list of "a/b" strings, compute the sum(a)/sum(b), and the corresponding percentage
gotten = 0
possible = 0
for d in details
if d? and d.indexOf('/') > 0
a = d.split('/')
got = parseInt(a[0])
pos = parseInt(a[1])
gotten += got
possible += pos
if possible > 0
s = (gotten / possible * 100).toFixed(1) + "%"
else
s = "0%"
s += " (" + gotten + '/' + possible + ")"
@el.find('.overall-progress').html(s)
updateProgress: => updateProgress: =>
new_progress = "NA" new_progress_status = "NA"
scores_list = @el.find('.progress-score-list')
scores_list.empty()
details = []
_this = this _this = this
$('.problems-wrapper').each (index) -> $('.problems-wrapper').each (index) ->
progress = $(this).attr 'progress' progress_status = $(this).data('progress_status')
new_progress = _this.mergeProgress progress, new_progress new_progress_status = _this.mergeProgressStatus progress_status, new_progress_status
progress_detail = $(this).data('progress_detail')
scores_list.append("<li>" + progress_detail + "</li>")
details.push(progress_detail)
@progressTable[@position] = new_progress @progressTable[@position] = new_progress_status
@setProgress(new_progress, @link_for(@position)) @setProgress(new_progress_status, @link_for(@position))
@updateOverallScore(details)
setProgress: (progress, element) -> setProgress: (progress, element) ->
# If progress is "NA", don't add any css class # If progress is "NA", don't add any css class
element.removeClass('progress-none') element.removeClass('progress-none')
.removeClass('progress-some') .removeClass('progress-in_progress')
.removeClass('progress-done') .removeClass('progress-done')
switch progress switch progress
when 'none' then element.addClass('progress-none') when 'none' then element.addClass('progress-none')
when 'in_progress' then element.addClass('progress-some') when 'in_progress' then element.addClass('progress-in_progress')
when 'done' then element.addClass('progress-done') when 'done' then element.addClass('progress-done')
toggleArrows: => toggleArrows: =>
...@@ -95,6 +124,8 @@ class @Sequence ...@@ -95,6 +124,8 @@ class @Sequence
sequence_links = @$('#seq_content a.seqnav') sequence_links = @$('#seq_content a.seqnav')
sequence_links.click @goto sequence_links.click @goto
# update score lists
@updateProgress()
goto: (event) => goto: (event) =>
event.preventDefault() event.preventDefault()
......
...@@ -23,7 +23,9 @@ class VerticalModule(XModule): ...@@ -23,7 +23,9 @@ class VerticalModule(XModule):
}) })
def get_progress(self): def get_progress(self):
# TODO: Cache progress or children array? """
Combine the progress of all the children.
"""
children = self.get_children() children = self.get_children()
progresses = [child.get_progress() for child in children] progresses = [child.get_progress() for child in children]
progress = reduce(Progress.add_counts, progresses, None) progress = reduce(Progress.add_counts, progresses, None)
......
<section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}"></section> <section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}" data-progress_status="${progress_status}" data-progress_detail="${progress_detail}"></section>
<div id="sequence_${element_id}" class="sequence" data-id="${item_id}" data-position="${position}" data-course_modx_root="/course/modx" > <div id="sequence_${element_id}" class="sequence" data-id="${item_id}" data-position="${position}" data-course_modx_root="/course/modx" >
<div class="student-progress">
<div class="summary">
Overall: <span class="overall-progress"> </span>
</div>
<div class="details">
Scores:
<ul class="progress-score-list">
</ul>
</div>
</div>
<nav aria-label="Section Navigation" class="sequence-nav"> <nav aria-label="Section Navigation" class="sequence-nav">
<div class="sequence-list-wrapper"> <div class="sequence-list-wrapper">
<ol id="sequence-list"> <ol id="sequence-list">
......
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