From 34cc112cfbba795dd6234b78ed9937e0ec1d3a2f Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Tue, 19 Feb 2013 14:18:56 -0500 Subject: [PATCH] Saving work in progress on annotations v2 with classification problems. --- .../lib/xmodule/xmodule/annotatable_module.py | 111 ++++++++++++++---- .../xmodule/css/annotatable/display.scss | 7 ++ .../xmodule/js/src/annotatable/display.coffee | 15 ++- lms/templates/annotatable.html | 41 +++---- lms/templates/annotatable_problem.html | 23 ++-- 5 files changed, 135 insertions(+), 62 deletions(-) diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py index 2cec2bef7a..a7327c1430 100644 --- a/common/lib/xmodule/xmodule/annotatable_module.py +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -73,19 +73,25 @@ class AnnotatableModule(XModule): def _set_problem(self, span, index, xmltree): """ Sets the associated problem. """ - problem = span.find('problem') - if problem is not None: + problem_el = span.find('problem') + if problem_el is not None: problem_id = str(index + 1) - problem.set('problem_id', problem_id) + problem_el.set('problem_id', problem_id) span.set('data-problem-id', problem_id) - parsed_problem = self._parse_problem(problem) + parsed_problem = self._parse_problem(problem_el) + parsed_problem['discussion_id'] = span.get('data-discussion-id') if parsed_problem is not None: self.problems.append(parsed_problem) + span.remove(problem_el) - def _parse_problem(self, problem): - prompt_el = problem.find('prompt') - answer_el = problem.find('answer') - tags_el = problem.find('tags') + def _parse_problem(self, problem_el): + """ Returns the problem XML as a dict. """ + prompt_el = problem_el.find('prompt') + answer_el = problem_el.find('answer') + tags_el = problem_el.find('tags') + + if any(v is None for v in (prompt_el, answer_el, tags_el)): + return None tags = [] for tag_el in tags_el.iterchildren('tag'): @@ -96,20 +102,14 @@ class AnnotatableModule(XModule): 'answer': tag_el.get('answer', 'n') == 'y' }) - parsed = { - 'problem_id': problem.get('problem_id'), + result = { + 'problem_id': problem_el.get('problem_id'), 'prompt': prompt_el.text, 'answer': answer_el.text, 'tags': tags } - log.debug('parsed problem id = ' + parsed['problem_id']) - log.debug("problem keys: " + ", ".join(parsed.keys())) - log.debug('prompt = ' + parsed['prompt']) - log.debug('answer = ' + parsed['answer']) - log.debug('num tags = ' + str(len(parsed['tags']))) - - return parsed + return result def _get_marker_color(self, span): """ Returns the name of the marker/highlight color for the span if it is valid, otherwise none.""" @@ -151,16 +151,48 @@ class AnnotatableModule(XModule): return etree.tostring(xmltree) + def _render_items(self): + items = [] + + if self.render_as_problems: + discussions = {} + for child in self.get_display_items(): + discussions[child.discussion_id] = child.get_html() + + for problem in self.problems: + discussion = None + discussion_id = problem['discussion_id'] + if discussion_id in discussions: + discussion = { + 'discussion_id': discussion_id, + 'content': discussions[discussion_id] + } + items.append({ + 'problem': problem, + 'discussion': discussion + }) + else: + for child in self.get_display_items(): + items.append({ 'discussion': { + 'discussion_id': child.discussion_id, + 'content': child.get_html() + }}) + + return items + def get_html(self): """ Renders parameters to template. """ - + + html_content = self._render_content() + items = self._render_items() + context = { 'display_name': self.display_name, 'problem_name': self.problem_name, 'element_id': self.element_id, - 'html_content': self._render_content(), - 'has_problems': self.has_problems, - 'problems': self.problems + 'html_content': html_content, + 'render_as_problems': self.render_as_problems, + 'items': items } return self.system.render_template('annotatable.html', context) @@ -175,7 +207,7 @@ class AnnotatableModule(XModule): self.element_id = self.location.html_id(); self.content = self.definition['data'] self.problem_type = xmltree.get('problem_type') - self.has_problems = (self.problem_type == 'classification') + self.render_as_problems = (self.problem_type == 'classification') self.problem_name = self._get_problem_name(self.problem_type) self.problems = [] @@ -184,3 +216,38 @@ class AnnotatableDescriptor(RawDescriptor): module_class = AnnotatableModule stores_state = True template_dir_name = "annotatable" + + + @classmethod + def definition_from_xml(cls, xml_object, system): + children = [] + for child in xml_object.findall('discussion'): + try: + children.append(system.process_xml(etree.tostring(child, encoding='unicode')).location.url()) + xml_object.remove(child) + except Exception as e: + log.exception("Unable to load child when parsing Sequence. Continuing...") + if system.error_tracker is not None: + system.error_tracker("ERROR: " + str(e)) + continue + return { + 'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode'), + 'children': children + } + + def definition_to_xml(self, resource_fs): + try: + root = etree.fromstring(self.definition['data']) + for child in self.get_children(): + root.append(etree.fromstring(child.export_to_xml(resource_fs))) + return root + except etree.XMLSyntaxError as err: + # Can't recover here, so just add some info and + # re-raise + lines = self.definition['data'].split('\n') + line, offset = err.position + msg = ("Unable to create xml for problem {loc}. " + "Context: '{context}'".format( + context=lines[line - 1][offset - 40:offset + 40], + loc=self.location)) + raise Exception, msg, sys.exc_info()[2] \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index b5cc14d77d..b9925c40b5 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -62,6 +62,9 @@ .annotatable-problems { margin: 25px 0 0 0; + .annotatable-discussion { + display: none; + } } .annotatable-problem { border: 1px solid #ccc; @@ -88,11 +91,15 @@ margin: 1em 0; padding: 0; li { + cursor: pointer; display: inline; padding: .5em; margin: 0 .5em 0 0; background-color: #ccc; border: 1px solid #000; + &.selected { + background-color: rgba(255,255,10,0.3); + } } } .annotatable-problem-controls { diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee index dadea5c1a1..a9a86c8716 100644 --- a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -8,6 +8,9 @@ class @Annotatable replySelector: '.annotatable-reply' helpSelector: '.annotatable-help-icon' returnSelector: '.annotatable-return' + problemSelector: '.annotatable-problem' + problemSubmitSelector: '.annotatable-problem-submit' + problemTagSelector: '.annotatable-problem-tags > li' discussionXModuleSelector: '.xmodule_DiscussionModule' discussionSelector: '.discussion-module' @@ -25,7 +28,6 @@ class @Annotatable init: () -> @initEvents() @initTips() - @initDiscussionReturnLinks() initEvents: () -> @annotationsHidden = false @@ -33,6 +35,13 @@ class @Annotatable @$(@wrapperSelector).delegate @replySelector, 'click', @onClickReply $(@discussionXModuleSelector).delegate @returnSelector, 'click', @onClickReturn + problemSelector = @problemSelector + @$(@problemSubmitSelector).bind 'click', (e) -> + $(this).closest(problemSelector).next().show() + + @$(@problemTagSelector).bind 'click', (e) -> + $(this).toggleClass('selected') + initTips: () -> @savedTips = [] @$(@spanSelector).each (index, el) => $(el).qtip(@getTipOptions el) @@ -45,10 +54,6 @@ class @Annotatable title: 'Annotated Reading' text: true # use title attribute of this element - initDiscussionReturnLinks: () -> - $(@discussionXModuleSelector).find(@discussionSelector).each (index, el) => - $(el).before @createReturnLink(@getDiscussionId el) - getTipOptions: (el) -> content: title: diff --git a/lms/templates/annotatable.html b/lms/templates/annotatable.html index cad8707ae5..16bba4789a 100644 --- a/lms/templates/annotatable.html +++ b/lms/templates/annotatable.html @@ -1,3 +1,5 @@ +<%namespace name="annotatable" file="annotatable_problem.html"/> +
% if display_name is not UNDEFINED and display_name is not None: @@ -12,32 +14,23 @@
${html_content}
- % if has_problems: + % if render_as_problems:
- % for problem in problems: -
-
- Classification Exercise: (${loop.index + 1} / ${len(problems)}) -
-
-
${problem['prompt']}
-
    - % for tag in problem['tags']: -
  • ${tag['name']}
  • - % endfor -
- Explain the rationale for your tag selections:
- -
- - - Return to annotation -
-
- -
+ % for item in items: + ${annotatable.render_problem(item['problem'],loop.index,len(items))} + % if item['discussion']: +
${item['discussion']['content']}
+ % endif % endfor
+ % else: +
+ % for item in items: +
+ Return to annotation + ${item['discussion']['content']} +
+ % endfor +
% endif
diff --git a/lms/templates/annotatable_problem.html b/lms/templates/annotatable_problem.html index fe77f15357..ccc389bbc2 100644 --- a/lms/templates/annotatable_problem.html +++ b/lms/templates/annotatable_problem.html @@ -1,23 +1,24 @@ -
+<%def name="render_problem(problem,index,total)"> +
- Classification Exercise: - - ${problem_index} / ${total_problems} - + Classification Exercise: (${index + 1} / ${total})
-
${problem_prompt}
+
${problem['prompt']}
    - % for tag in problem_tags: -
  • ${tag.name}
  • + % for tag in problem['tags']: +
  • ${tag['name']}
  • % endfor
Explain the rationale for your tag selections:
+
+ + + Return to annotation +
+ \ No newline at end of file