Initial progress display.
* add module_from_xml param to I4xSystem * use it to implement xmodule.get_children() * fix a few comments here and there * Render-time progress display for seq and vertical modules. - Computes fraction of subproblems done. * Pass problem state back to js during ajax calls. * general cleanup in capa_module.py * add progress_changed and progress fields to json returned from each ajax handler * Coffeescript changes to hook up sequence tracking of problem progress * net result: sequence 'a' tags now have a progress class * properly set css class on initial load * fire event when progress changes after ajax calls * also save state in 'progress' property of problems-wrapper tag * event handler finds those tags, computes updated progress
This commit is contained in:
committed by
Matthew Mongeau
parent
6dc481516c
commit
b5368f2a4f
@@ -174,6 +174,8 @@ def get_score(user, problem, cache, coursename=None):
|
||||
else:
|
||||
## HACK 1: We shouldn't specifically reference capa_module
|
||||
## HACK 2: Backwards-compatibility: This should be written when a grade is saved, and removed from the system
|
||||
# TODO: These are no longer correct params for I4xSystem -- figure out what this code
|
||||
# does, clean it up.
|
||||
from module_render import I4xSystem
|
||||
system = I4xSystem(None, None, None, coursename=coursename)
|
||||
total=float(xmodule.capa_module.Module(system, etree.tostring(problem), "id").max_score())
|
||||
|
||||
@@ -34,7 +34,8 @@ class I4xSystem(object):
|
||||
and user, or other environment-specific info.
|
||||
'''
|
||||
def __init__(self, ajax_url, track_function, render_function,
|
||||
render_template, request=None, filestore=None):
|
||||
module_from_xml, render_template, request=None,
|
||||
filestore=None):
|
||||
'''
|
||||
Create a closure around the system environment.
|
||||
|
||||
@@ -43,6 +44,8 @@ class I4xSystem(object):
|
||||
or otherwise tracking the event.
|
||||
TODO: Not used, and has inconsistent args in different
|
||||
files. Update or remove.
|
||||
module_from_xml - function that takes (module_xml) and returns a corresponding
|
||||
module instance object.
|
||||
render_function - function that takes (module_xml) and renders it,
|
||||
returning a dictionary with a context for rendering the
|
||||
module to html. Dictionary will contain keys 'content'
|
||||
@@ -62,6 +65,7 @@ class I4xSystem(object):
|
||||
if settings.DEBUG:
|
||||
log.info("[courseware.module_render.I4xSystem] filestore path = %s",
|
||||
filestore)
|
||||
self.module_from_xml = module_from_xml
|
||||
self.render_function = render_function
|
||||
self.render_template = render_template
|
||||
self.exception404 = Http404
|
||||
@@ -127,6 +131,18 @@ def grade_histogram(module_id):
|
||||
return []
|
||||
return grades
|
||||
|
||||
|
||||
def make_module_from_xml_fn(user, request, student_module_cache, position):
|
||||
'''Create the make_from_xml() function'''
|
||||
def module_from_xml(xml):
|
||||
'''Modules need a way to convert xml to instance objects.
|
||||
Pass the rest of the context through.'''
|
||||
(instance, sm, module_type) = get_module(
|
||||
user, request, xml, student_module_cache, position)
|
||||
return instance
|
||||
return module_from_xml
|
||||
|
||||
|
||||
def get_module(user, request, module_xml, student_module_cache, position=None):
|
||||
''' Get an instance of the xmodule class corresponding to module_xml,
|
||||
setting the state based on an existing StudentModule, or creating one if none
|
||||
@@ -165,6 +181,9 @@ def get_module(user, request, module_xml, student_module_cache, position=None):
|
||||
# Setup system context for module instance
|
||||
ajax_url = settings.MITX_ROOT_URL + '/modx/' + module_type + '/' + module_id + '/'
|
||||
|
||||
module_from_xml = make_module_from_xml_fn(
|
||||
user, request, student_module_cache, position)
|
||||
|
||||
system = I4xSystem(track_function = make_track_function(request),
|
||||
render_function = lambda xml: render_x_module(
|
||||
user, request, xml, student_module_cache, position),
|
||||
@@ -172,6 +191,7 @@ def get_module(user, request, module_xml, student_module_cache, position=None):
|
||||
ajax_url = ajax_url,
|
||||
request = request,
|
||||
filestore = OSFS(data_root),
|
||||
module_from_xml = module_from_xml,
|
||||
)
|
||||
# pass position specified in URL to module through I4xSystem
|
||||
system.set('position', position)
|
||||
@@ -295,9 +315,17 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
|
||||
response = HttpResponse(json.dumps({'success': error_msg}))
|
||||
return response
|
||||
|
||||
# TODO: This doesn't have a cache of child student modules. Just
|
||||
# passing the current one. If ajax calls end up needing children,
|
||||
# this won't work (but fixing it may cause performance issues...)
|
||||
# Figure out :)
|
||||
module_from_xml = make_module_from_xml_fn(
|
||||
request.user, request, [s], None)
|
||||
|
||||
# Create the module
|
||||
system = I4xSystem(track_function = make_track_function(request),
|
||||
render_function = None,
|
||||
render_function = None,
|
||||
module_from_xml = module_from_xml,
|
||||
render_template = render_to_string,
|
||||
ajax_url = ajax_url,
|
||||
request = request,
|
||||
@@ -316,7 +344,11 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
|
||||
return response
|
||||
|
||||
# Let the module handle the AJAX
|
||||
ajax_return = instance.handle_ajax(dispatch, request.POST)
|
||||
try:
|
||||
ajax_return = instance.handle_ajax(dispatch, request.POST)
|
||||
except:
|
||||
log.exception("error processing ajax call")
|
||||
raise
|
||||
|
||||
# Save the state back to the database
|
||||
s.state = instance.get_state()
|
||||
|
||||
@@ -17,12 +17,20 @@ class @Problem
|
||||
@$('section.action input.save').click @save
|
||||
@$('input.math').keyup(@refreshMath).each(@refreshMath)
|
||||
|
||||
update_progress: (response) =>
|
||||
if response.progress_changed
|
||||
@element.attr progress: response.progress
|
||||
@element.trigger('progressChanged')
|
||||
|
||||
render: (content) ->
|
||||
if content
|
||||
@element.html(content)
|
||||
@bind()
|
||||
else
|
||||
@element.load @content_url, @bind
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_get", '', (response) =>
|
||||
@element.html(response.html)
|
||||
@bind()
|
||||
|
||||
|
||||
check: =>
|
||||
Logger.log 'problem_check', @answers
|
||||
@@ -30,19 +38,22 @@ class @Problem
|
||||
switch response.success
|
||||
when 'incorrect', 'correct'
|
||||
@render(response.contents)
|
||||
@update_progress response
|
||||
else
|
||||
alert(response.success)
|
||||
|
||||
reset: =>
|
||||
Logger.log 'problem_reset', @answers
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (content) =>
|
||||
@render(content)
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) =>
|
||||
@render(response.html)
|
||||
@update_progress response
|
||||
|
||||
show: =>
|
||||
if !@element.hasClass 'showed'
|
||||
Logger.log 'problem_show', problem: @id
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
|
||||
$.each response, (key, value) =>
|
||||
answers = response.answers
|
||||
$.each answers, (key, value) =>
|
||||
if $.isArray(value)
|
||||
for choice in value
|
||||
@$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
|
||||
@@ -51,6 +62,7 @@ class @Problem
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@$('.show').val 'Hide Answer'
|
||||
@element.addClass 'showed'
|
||||
@update_progress response
|
||||
else
|
||||
@$('[id^=answer_], [id^=solution_]').text ''
|
||||
@$('[correct_answer]').attr correct_answer: null
|
||||
@@ -62,6 +74,7 @@ class @Problem
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) =>
|
||||
if response.success
|
||||
alert 'Saved'
|
||||
@update_progress response
|
||||
|
||||
refreshMath: (event, element) =>
|
||||
element = event.target unless element
|
||||
|
||||
@@ -2,6 +2,7 @@ class @Sequence
|
||||
constructor: (@id, @elements, @tag, position) ->
|
||||
@element = $("#sequence_#{@id}")
|
||||
@buildNavigation()
|
||||
@initProgress()
|
||||
@bind()
|
||||
@render position
|
||||
|
||||
@@ -11,11 +12,52 @@ class @Sequence
|
||||
bind: ->
|
||||
@$('#sequence-list a').click @goto
|
||||
|
||||
initProgress: ->
|
||||
@progressTable = {} # "#problem_#{id}" -> progress
|
||||
|
||||
|
||||
hookUpProgressEvent: ->
|
||||
$('.problems-wrapper').bind 'progressChanged', @updateProgress
|
||||
|
||||
mergeProgress: (p1, p2) ->
|
||||
if p1 == "done" and p2 == "done"
|
||||
return "done"
|
||||
# not done, so if any progress on either, in_progress
|
||||
w1 = p1 == "done" or p1 == "in_progress"
|
||||
w2 = p2 == "done" or p2 == "in_progress"
|
||||
if w1 or w2
|
||||
return "in_progress"
|
||||
|
||||
return "none"
|
||||
|
||||
updateProgress: =>
|
||||
new_progress = "none"
|
||||
_this = this
|
||||
$('.problems-wrapper').each (index) ->
|
||||
progress = $(this).attr 'progress'
|
||||
new_progress = _this.mergeProgress progress, new_progress
|
||||
|
||||
@progressTable[@position] = new_progress
|
||||
@setProgress(new_progress, @link_for(@position))
|
||||
|
||||
setProgress: (progress, element) ->
|
||||
element.removeClass('progress-none')
|
||||
.removeClass('progress-some')
|
||||
.removeClass('progress-done')
|
||||
switch progress
|
||||
when 'none' then element.addClass('progress-none')
|
||||
when 'in_progress' then element.addClass('progress-some')
|
||||
when 'done' then element.addClass('progress-done')
|
||||
|
||||
buildNavigation: ->
|
||||
$.each @elements, (index, item) =>
|
||||
link = $('<a>').attr class: "seq_#{item.type}_inactive", 'data-element': index + 1
|
||||
title = $('<p>').html(item.title)
|
||||
# TODO: add item.progress_str either to the title or somewhere else.
|
||||
# Make sure it gets updated after ajax calls
|
||||
list_item = $('<li>').append(link.append(title))
|
||||
@setProgress item.progress_stat, link
|
||||
|
||||
@$('#sequence-list').append list_item
|
||||
|
||||
toggleArrows: =>
|
||||
@@ -36,13 +78,14 @@ class @Sequence
|
||||
if @position != undefined
|
||||
@mark_visited @position
|
||||
$.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position
|
||||
|
||||
|
||||
@mark_active new_position
|
||||
@$('#seq_content').html @elements[new_position - 1].content
|
||||
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub])
|
||||
@position = new_position
|
||||
@toggleArrows()
|
||||
@hookUpProgressEvent()
|
||||
@element.trigger 'contentChanged'
|
||||
|
||||
goto: (event) =>
|
||||
@@ -67,7 +110,17 @@ class @Sequence
|
||||
@$("#sequence-list a[data-element=#{position}]")
|
||||
|
||||
mark_visited: (position) ->
|
||||
@link_for(position).attr class: "seq_#{@elements[position - 1].type}_visited"
|
||||
# Don't overwrite class attribute to avoid changing Progress class
|
||||
type = @elements[position - 1].type
|
||||
element = @link_for(position)
|
||||
element.removeClass("seq_#{type}_inactive")
|
||||
.removeClass("seq_#{type}_active")
|
||||
.addClass("seq_#{type}_visited")
|
||||
|
||||
mark_active: (position) ->
|
||||
@link_for(position).attr class: "seq_#{@elements[position - 1].type}_active"
|
||||
# Don't overwrite class attribute to avoid changing Progress class
|
||||
type = @elements[position - 1].type
|
||||
element = @link_for(position)
|
||||
element.removeClass("seq_#{type}_inactive")
|
||||
.removeClass("seq_#{type}_visited")
|
||||
.addClass("seq_#{type}_active")
|
||||
|
||||
Reference in New Issue
Block a user