Commit 48bb447f by Arthur Barrett

Adding basic annotatable module and related files.

parent 7a8e87c7
......@@ -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"
]
}
)
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"
.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;
}
}
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
<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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment