Adding basic annotatable module and related files.
This commit is contained in:
@@ -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"
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
128
common/lib/xmodule/xmodule/annotatable_module.py
Normal file
128
common/lib/xmodule/xmodule/annotatable_module.py
Normal file
@@ -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"
|
||||
63
common/lib/xmodule/xmodule/css/annotatable/display.scss
Normal file
63
common/lib/xmodule/xmodule/css/annotatable/display.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
23
lms/templates/annotatable.html
Normal file
23
lms/templates/annotatable.html
Normal file
@@ -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>
|
||||
Reference in New Issue
Block a user