Files
edx-platform/common/lib/xmodule/xmodule/annotatable_module.py

134 lines
5.1 KiB
Python

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 _get_discussion_html(self, discussion_id, discussion_title):
""" Returns html to display the discussion thread """
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>'
return tpl.format(discussion_id, discussion_title)
def _attach_discussion(self, span, index, xmltree):
""" Attaches a discussion thread to the annotation span. """
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 = self._get_discussion_html(discussion_id, discussion_title)
discussion_xmltree = etree.fromstring(discussion_html)
span_container = self._get_span_container(span)
span_container.append(discussion_xmltree)
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"