From 48bb447fbde50916c14bc3179e368c191b5d0f33 Mon Sep 17 00:00:00 2001 From: Arthur Barrett <abarrett@Arthurs-MacBook-Pro.local> Date: Thu, 31 Jan 2013 18:42:11 -0500 Subject: [PATCH] Adding basic annotatable module and related files. --- common/lib/xmodule/setup.py | 3 +- .../lib/xmodule/xmodule/annotatable_module.py | 128 ++++++++++++++++++ .../xmodule/css/annotatable/display.scss | 63 +++++++++ .../xmodule/js/src/annotatable/display.coffee | 9 ++ lms/templates/annotatable.html | 23 ++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 common/lib/xmodule/xmodule/annotatable_module.py create mode 100644 common/lib/xmodule/xmodule/css/annotatable/display.scss create mode 100644 common/lib/xmodule/xmodule/js/src/annotatable/display.coffee create mode 100644 lms/templates/annotatable.html diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 29227c31884..a2d9b3e4df3 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -40,7 +40,8 @@ setup( "static_tab = xmodule.html_module:StaticTabDescriptor", "custom_tag_template = xmodule.raw_module:RawDescriptor", "about = xmodule.html_module:AboutDescriptor", - "graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor" + "graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor", + "annotatable = xmodule.annotatable_module:AnnotatableDescriptor" ] } ) diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py new file mode 100644 index 00000000000..bf76d7fc8c4 --- /dev/null +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -0,0 +1,128 @@ +import json +import logging +import re + +from lxml import etree +from pkg_resources import resource_string, resource_listdir + +from xmodule.x_module import XModule +from xmodule.raw_module import RawDescriptor +from xmodule.modulestore.mongo import MongoModuleStore +from xmodule.modulestore.django import modulestore +from xmodule.contentstore.content import StaticContent + +import datetime +import time + +log = logging.getLogger(__name__) + +class AnnotatableModule(XModule): + # Note: js and css in common/lib/xmodule/xmodule + js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/html/display.coffee'), + resource_string(__name__, 'js/src/annotatable/display.coffee')], + 'js': [] + } + js_module_name = "Annotatable" + css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]} + + def _is_span(self, element): + """ Returns true if the element is a valid annotation span, false otherwise. """ + return element.tag == 'span' and element.get('class') == 'annotatable' + + def _is_span_container(self, element): + """ Returns true if the element is a valid span contanier, false otherwise. """ + return element.tag == 'p' # Assume content is in paragraph form (for now...) + + def _iterspans(self, xmltree, callbacks): + """ Iterates over span elements and invokes each callback on the span. """ + + index = 0 + for element in xmltree.iter('span'): + if self._is_span(element): + for callback in callbacks: + callback(element, index, xmltree) + index += 1 + + def _get_span_container(self, span): + """ Returns the first container element of the span. + The intent is to add the discussion widgets at the + end of the container, not interspersed with the text. """ + + container = None + for parent in span.iterancestors(): + if self._is_span_container(parent): + container = parent + break + + if container is None: + return parent + return container + + def _attach_discussion(self, span, index, xmltree): + """ Attaches a discussion thread to the annotation span. """ + + tpl = u'<div class="annotatable-discussion" data-discussion-id="{0}">' + tpl += '<div class="annotatable-icon"> </div>' + tpl += '<span class="annotatable-discussion-label">Guided Discussion: </span>' + tpl += '<span class="annotatable-discussion-thread">{1}</span>' + tpl += '<a class="annotatable-show-discussion" href="javascript:void(0);">Show Discussion</a>' + tpl += '</div>' + + span_id = 'span-{0}'.format(index) # How should we anchor spans? + span.set('data-span-id', span_id) + + discussion_id = 'discussion-{0}'.format(index) # How do we get a real discussion ID? + discussion_title = 'Thread Title {0}'.format(index) # How do we get the discussion Title? + discussion_html = tpl.format(discussion_id, discussion_title) + discussion = etree.fromstring(discussion_html) + + span_container = self._get_span_container(span) + span_container.append(discussion) + + self.discussion_for[span_id] = discussion_id + + def _add_icon(self, span, index, xmltree): + """ Adds an icon to the annotation span. """ + + span_icon = etree.Element('span', { 'class': 'annotatable-icon'} ) + span_icon.text = ''; + span_icon.tail = span.text + span.text = '' + span.insert(0, span_icon) + + def _render(self): + """ Renders annotatable content by transforming spans and adding discussions. """ + + xmltree = etree.fromstring(self.content) + self._iterspans(xmltree, [ self._add_icon, self._attach_discussion ]) + return etree.tostring(xmltree) + + def get_html(self): + """ Renders parameters to template. """ + + context = { + 'display_name': self.display_name, + 'element_id': self.element_id, + 'html_content': self._render(), + 'json_discussion_for': json.dumps(self.discussion_for) + } + + # 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) + + self.element_id = self.location.html_id(); + self.content = self.definition['data'] + self.discussion_for = {} # Maps spans to discussions by id (for JS) + + +class AnnotatableDescriptor(RawDescriptor): + module_class = AnnotatableModule + stores_state = True + template_dir_name = "annotatable" diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss new file mode 100644 index 00000000000..9b6404ceb82 --- /dev/null +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -0,0 +1,63 @@ +.annotatable-header { + border: 1px solid $border-color; + border-radius: 3px; + margin-bottom: 1em; + padding: 2px 4px; + position: relative; + + .annotatable-title { + font-size: em(18); + text-transform: uppercase; + } + .annotatable-description { + font-size: $body-font-size; + } +} + + +span.annotatable { + color: $blue; + .annotatable-icon { + margin: auto 2px auto 4px; + } +} + +.annotatable-icon { + display: inline-block; + vertical-align: middle; + width: 16px; + height: 17px; + background: url(../images/link-icon.png) no-repeat; +} + +.help-icon { + display: block; + position: absolute; + right: 0; + top: 33%; + width: 16px; + height: 17px; + margin: 0 7px 0 0; + background: url(../images/info-icon.png) no-repeat; +} + +.annotatable-discussion { + display: block; + border: 1px solid $border-color; + border-radius: 3px; + margin: 1em 0; + position: relative; + padding: 4px; + + .annotatable-discussion-label { + font-weight: bold; + } + .annotatable-icon { + margin: auto 4px auto 0px; + } + .annotatable-show-discussion { + position: absolute; + right: 8px; + margin-top: 4px; + } +} diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee new file mode 100644 index 00000000000..1db6ac2f6b2 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -0,0 +1,9 @@ +class @Annotatable + constructor: (el) -> + console.log "loaded Annotatable" + $(el).find(".annotatable").on "click", (e) -> + data = $(".annotatable-wrapper", el).data("spans") + span_id = e.target.getAttribute("data-span-id") + msg = "annotatable span clicked. discuss span [" + span_id + "] in discussion [" + data[span_id] + "]" + console.log data + window.alert msg diff --git a/lms/templates/annotatable.html b/lms/templates/annotatable.html new file mode 100644 index 00000000000..3df0a599219 --- /dev/null +++ b/lms/templates/annotatable.html @@ -0,0 +1,23 @@ +<div class="annotatable-wrapper" id="${element_id}"> + +<div class="annotatable-header"> + <div class="help-icon"></div> + % if display_name is not UNDEFINED and display_name is not None: + <div class="annotatable-title">${display_name} </div> + % endif + <div class="annotatable-description">Annotated Reading + Guided Discussion</div> +</div> + +<div class="annotatable-content"> + ${html_content} +</div> + +<script> +$(function() { + // TODO pass spans to module directly + var el = $('#${element_id}.annotatable-wrapper'); + el.data('spans', ${json_discussion_for}); +}); +</script> + +</div> \ No newline at end of file -- GitLab