From 72d9c1d7d27d39bbf56674eceff5dc1341744aa4 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Mon, 18 Feb 2013 19:02:54 -0500 Subject: [PATCH] Saving work-in-progress of guided classification exercise. --- .../lib/xmodule/xmodule/annotatable_module.py | 111 ++++++++++++++---- common/lib/xmodule/xmodule/artie.py | 79 +++++++++++++ .../xmodule/css/annotatable/display.scss | 50 ++++++++ .../discussion/discussion_module_view.coffee | 2 +- lms/templates/annotatable.html | 35 +++++- lms/templates/annotatable_problem.html | 23 ++++ 6 files changed, 271 insertions(+), 29 deletions(-) create mode 100644 common/lib/xmodule/xmodule/artie.py create mode 100644 lms/templates/annotatable_problem.html diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py index 2818efd7ce..2cec2bef7a 100644 --- a/common/lib/xmodule/xmodule/annotatable_module.py +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -1,3 +1,4 @@ +import pprint import json import logging import re @@ -42,14 +43,7 @@ class AnnotatableModule(XModule): callback(element, index, xmltree) index += 1 - def _set_span_data(self, span, index, xmltree): - """ Sets the associated discussion id for the span. """ - - if 'discussion' in span.attrib: - span.set('data-discussion-id', span.get('discussion')) - del span.attrib['discussion'] - - def _decorate_span(self, span, index, xmltree): + def _set_span_highlight(self, span, index, xmltree): """ Adds a highlight class to the span. """ cls = ['annotatable-span', 'highlight'] @@ -60,19 +54,63 @@ class AnnotatableModule(XModule): span.set('class', ' '.join(cls)) span.tag = 'div' - def _decorate_comment(self, span, index, xmltree): + def _set_span_comment(self, span, index, xmltree): """ Sets the comment class. """ - comment = None - for child in span.iterchildren(): - if child.get('class') == 'comment': - comment = child - break - + comment = span.find('comment') if comment is not None: comment.tag = 'div' comment.set('class', 'annotatable-comment') + def _set_span_discussion(self, span, index, xmltree): + """ Sets the associated discussion id for the span. """ + + if 'discussion' in span.attrib: + discussion = span.get('discussion') + span.set('data-discussion-id', discussion) + del span.attrib['discussion'] + + def _set_problem(self, span, index, xmltree): + """ Sets the associated problem. """ + + problem = span.find('problem') + if problem is not None: + problem_id = str(index + 1) + problem.set('problem_id', problem_id) + span.set('data-problem-id', problem_id) + parsed_problem = self._parse_problem(problem) + if parsed_problem is not None: + self.problems.append(parsed_problem) + + def _parse_problem(self, problem): + prompt_el = problem.find('prompt') + answer_el = problem.find('answer') + tags_el = problem.find('tags') + + tags = [] + for tag_el in tags_el.iterchildren('tag'): + tags.append({ + 'name': tag_el.get('name', ''), + 'display_name': tag_el.get('display_name', ''), + 'weight': tag_el.get('weight', 0), + 'answer': tag_el.get('answer', 'n') == 'y' + }) + + parsed = { + 'problem_id': problem.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 + def _get_marker_color(self, span): """ Returns the name of the marker/highlight color for the span if it is valid, otherwise none.""" @@ -84,17 +122,33 @@ class AnnotatableModule(XModule): return marker return None - def _render(self): + def _get_problem_name(self, problem_type): + """ Returns the display name for the problem type. Defaults to annotated reading if none given. """ + problem_types = { + 'classification': 'Classification Exercise + Guided Discussion', + 'annotated_reading': 'Annotated Reading + Guided Discussion' + } + + if problem_type is not None and problem_type in problem_types.keys(): + return problem_types[problem_type] + return problem_types['annotated_reading'] + + + def _render_content(self): """ Renders annotatable content by transforming spans and adding discussions. """ + callbacks = [ + self._set_span_highlight, + self._set_span_comment, + self._set_span_discussion, + self._set_problem + ] + xmltree = etree.fromstring(self.content) - self._iterspans(xmltree, [ - self._set_span_data, - self._decorate_span, - self._decorate_comment - ]) xmltree.tag = 'div' + self._iterspans(xmltree, callbacks) + return etree.tostring(xmltree) def get_html(self): @@ -102,20 +156,29 @@ class AnnotatableModule(XModule): context = { 'display_name': self.display_name, + 'problem_name': self.problem_name, 'element_id': self.element_id, - 'html_content': self._render() + 'html_content': self._render_content(), + 'has_problems': self.has_problems, + 'problems': self.problems } - # template dir: lms/templates return self.system.render_template('annotatable.html', context) def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) - + + xmltree = etree.fromstring(self.definition['data']) + 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.problem_name = self._get_problem_name(self.problem_type) + self.problems = [] + class AnnotatableDescriptor(RawDescriptor): module_class = AnnotatableModule diff --git a/common/lib/xmodule/xmodule/artie.py b/common/lib/xmodule/xmodule/artie.py new file mode 100644 index 0000000000..9941906c7e --- /dev/null +++ b/common/lib/xmodule/xmodule/artie.py @@ -0,0 +1,79 @@ +from lxml import etree + +class AnnotatableSource: + def __init__(self, source, **kwargs): + self._source = source + self._annotations = [] + + def render(self): + result = { 'html': None, 'json': None } + return result + + def problems(self): + return [] + + def annotations(self): + return self.annotations + +class Annotation: + def __init__(self, target, body, **kwargs): + self.target = target + self.body = body + self.problems = [] + +class Problem: + def __init__(self, definition, **kwargs): + self.definition = definition + + +TEXT = """ + +
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + Pellentesque id mauris sit amet lectus interdum tincidunt quis at mi. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum massa enim, sollicitudin id rutrum non, laoreet quis massa. Donec pharetra porta est id feugiat. Suspendisse aliquet cursus augue, at placerat magna adipiscing sit amet. Suspendisse velit dolor, congue in venenatis eget, consectetur pharetra massa. Vivamus facilisis tincidunt mi, nec imperdiet nibh vehicula sit amet. Donec lectus nisl, interdum sit amet faucibus et, porttitor in est. + Instructor prompt here...Explanation here... + + Sed semper malesuada est et mattis. Mauris vel aliquet dolor. Vivamus rhoncus tristique dictum. Duis eu neque et enim euismod venenatis. Praesent porttitor commodo erat, hendrerit interdum risus sollicitudin a. Fusce neque augue, volutpat vitae vestibulum sit amet, gravida ut urna. Vivamus rutrum laoreet turpis, a gravida velit fringilla a.

+

+ Nullam quis nisi non erat auctor tristique. Suspendisse a elit tellus. In consectetur mauris quis erat consectetur eu porta turpis sodales. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque egestas aliquam dignissim. Suspendisse fringilla, ante facilisis molestie ullamcorper, nisl erat elementum orci, a convallis ante massa id tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec leo eget enim imperdiet congue eget et quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean a vulputate dui. Quisque gravida volutpat dolor eu porttitor. Sed varius aliquam dictum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla dictum ligula cursus nisl volutpat consectetur. Nunc iaculis tellus orci, id aliquet purus. Sed ac justo tellus. Mauris at lacus nisi. In tincidunt nisl sit amet nisi interdum non malesuada nulla pulvinar. Aliquam scelerisque ligula ut urna fermentum tincidunt. Aenean lacinia blandit metus et interdum. Phasellus porttitor porttitor consequat. Cras ultrices dictum velit, sit amet turpis duis. + Maecenas eu volutpat lacus. + + Morbi luctus est + + tincidunt + Ignore this for now. It's not important. + + mauris dictum sit amet ornare augue eleifend. Quisque sagittis varius enim vulputate congue. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque egestas aliquam dignissim. Suspendisse fringilla, ante facilisis molestie ullamcorper, nisl erat elementum orci, a convallis ante massa id tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec leo eget enim imperdiet congue eget et quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean a vulputate dui. Quisque gravida volutpat dolor eu porttitor. Sed varius aliquam dictum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla dictum ligula cursus nisl volutpat consectetur. Nunc iaculis tellus orci, id aliquet purus. Sed ac justo tellus. Mauris at lacus nisi. In tincidunt nisl sit amet nisi interdum non malesuada nulla pulvinar. Aliquam scelerisque ligula ut urna fermentum tincidunt. Aenean lacinia blandit metus et interdum. Phasellus porttitor porttitor consequat. Cras ultrices dictum velit, sit amet turpis duis. + Mauris facilisis mauris id nunc euismod vehicula. Mauris dictum nisi ac ligula posuere ultricies. Maecenas eros nisl, aliquet non eleifend ac, posuere in ante. Aliquam erat volutpat. Mauris consequat fringilla cursus. Suspendisse euismod eros et mauris imperdiet a placerat sapien semper. + + Sed molestie laoreet magna in pharetra. Nunc mattis eleifend ultrices. Aenean ut quam vitae risus tincidunt tempor vitae sed arcu. +

+

+ In adipiscing metus sit amet quam sollicitudin sed suscipit diam gravida. Maecenas aliquet ante id nunc scelerisque pulvinar. Praesent ante erat, condimentum vel scelerisque non, aliquam vel urna. Cras euismod, mi at congue dignissim, velit velit aliquet est, vel vestibulum sem turpis a dui. Donec vel rutrum felis. Fusce nulla risus, volutpat sit amet molestie non, sollicitudin quis felis. Maecenas a turpis mauris. Donec vel pulvinar nulla. + Chicken tenderloin boudin pig pork chop. Biltong rump frankfurter swine jowl turducken. Venison ham hock chuck pork chop, jowl chicken meatball doner meatloaf beef ribs ball tip ham. Pork drumstick fatback ribeye chicken pork chop frankfurter andouille ball tip strip steak spare ribs biltong capicola. + Vivamus nec mi quam, non gravida erat. Fusce iaculis eros eget mi tempus vitae cursus nulla ornare. Donec a nibh purus. + + Ut id risus quis nibh tincidunt consectetur sed ac metus. Praesent accumsan scelerisque neque, eu imperdiet justo pharetra euismod. Suspendisse potenti. Suspendisse turpis lectus, fermentum id pellentesque eu, iaculis ut tortor. Nullam ut accumsan diam. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque egestas aliquam dignissim. Suspendisse fringilla, ante facilisis molestie ullamcorper, nisl erat elementum orci, a convallis ante massa id tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec leo eget enim imperdiet congue eget et quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean a vulputate dui. Quisque gravida volutpat dolor eu porttitor. Sed varius aliquam dictum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla dictum ligula cursus nisl volutpat consectetur. Nunc iaculis tellus orci, id aliquet purus. Sed ac justo tellus. Mauris at lacus nisi. In tincidunt nisl sit amet nisi interdum non malesuada nulla pulvinar. Aliquam scelerisque ligula ut urna fermentum tincidunt. Aenean lacinia blandit metus et interdum. Phasellus porttitor porttitor consequat. Cras ultrices dictum velit, sit amet turpis duis. + Duis nisl nunc, iaculis et pretium vel, bibendum eget diam. Vestibulum consectetur facilisis pretium. Morbi tristique dui a dui tempus vitae fermentum nunc dapibus. Vestibulum bibendum nunc nec dui sollicitudin viverra. Cras quam justo, consectetur fringilla varius vitae, malesuada eu lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec nisi lacus, feugiat sed lobortis nec, sodales sit amet tortor. + + + Vestibulum lobortis mollis cursus. + foo! + +

+
+""" + +source = AnnotatableSource(TEXT) +rendered = source.render() +print ", ".join(rendered.keys()) + diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index e5dacb9e87..b5cc14d77d 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -60,6 +60,54 @@ } } +.annotatable-problems { + margin: 25px 0 0 0; +} +.annotatable-problem { + border: 1px solid #ccc; + border-radius: 1em; + margin: 0 0 1em 0; +} +.annotatable-problem-header { + font-weight: bold; + border-bottom: 1px solid #ccc; + .annotatable-problem-index { font-weight: normal; } +} +.annotatable-problem-body { + position: relative; + textarea { + display: inline-block; + width: 55%; + } + .annotatable-problem-prompt { + font-style: italic; + } + ul.annotatable-problem-tags { + display: block; + list-style-type: none; + margin: 1em 0; + padding: 0; + li { + display: inline; + padding: .5em; + margin: 0 .5em 0 0; + background-color: #ccc; + border: 1px solid #000; + } + } + .annotatable-problem-controls { + display: inline-block; + margin: 0 4px 0 8px; + } +} +.annotatable-problem-footer {} + +.annotatable-problem-header, +.annotatable-problem-body, +.annotatable-problem-footer { + padding: .5em 1em; +} + .ui-tooltip.qtip.ui-tooltip { font-size: $body-font-size; border: 1px solid #333; @@ -137,3 +185,5 @@ border-top-color: rgba(0, 0, 0, .85); } } + + diff --git a/common/static/coffee/src/discussion/discussion_module_view.coffee b/common/static/coffee/src/discussion/discussion_module_view.coffee index 63bd6bc733..554f292c71 100644 --- a/common/static/coffee/src/discussion/discussion_module_view.coffee +++ b/common/static/coffee/src/discussion/discussion_module_view.coffee @@ -77,7 +77,7 @@ if Backbone? if @$('section.discussion').length @$('section.discussion').replaceWith($discussion) else - $(".discussion-module").append($discussion) + @$el.append($discussion) @newPostForm = $('.new-post-article') @threadviews = @discussion.map (thread) -> new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread diff --git a/lms/templates/annotatable.html b/lms/templates/annotatable.html index d3d19c8636..cad8707ae5 100644 --- a/lms/templates/annotatable.html +++ b/lms/templates/annotatable.html @@ -4,13 +4,40 @@
${display_name}
% endif
- Annotated Reading + Guided Discussion + ${problem_name} Hide Annotations
-
- ${html_content} -
+
${html_content}
+ + % if has_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 +
+
+ +
+ % endfor +
+ % endif diff --git a/lms/templates/annotatable_problem.html b/lms/templates/annotatable_problem.html new file mode 100644 index 0000000000..fe77f15357 --- /dev/null +++ b/lms/templates/annotatable_problem.html @@ -0,0 +1,23 @@ +
+
+ Classification Exercise: + + ${problem_index} / ${total_problems} + +
+
+
${problem_prompt}
+
    + % for tag in problem_tags: +
  • ${tag.name}
  • + % endfor +
+ Explain the rationale for your tag selections:
+ +
+ +