Commit e21d750d by Chris Dodge Committed by Xavier Antoviaque

ImageExplorer MVP implementation & fix under https

update hotspot reveal styling

address some PR feedback. Start to build out some Jasmine tests

address PR comments

add some default content

fix when running under https
parent eb4096f1
...@@ -62,6 +62,7 @@ else: ...@@ -62,6 +62,7 @@ else:
'lti', 'lti',
'concept', 'concept',
'openassessment', # edx-ora2 'openassessment', # edx-ora2
'image_explorer',
] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_CATEGORY = 'advanced' ADVANCED_COMPONENT_CATEGORY = 'advanced'
......
...@@ -42,6 +42,7 @@ XMODULES = [ ...@@ -42,6 +42,7 @@ XMODULES = [
"raw = xmodule.raw_module:RawDescriptor", "raw = xmodule.raw_module:RawDescriptor",
"crowdsource_hinter = xmodule.crowdsource_hinter:CrowdsourceHinterDescriptor", "crowdsource_hinter = xmodule.crowdsource_hinter:CrowdsourceHinterDescriptor",
"lti = xmodule.lti_module:LTIDescriptor", "lti = xmodule.lti_module:LTIDescriptor",
"image_explorer = xmodule.image_explorer:ImageExplorerDescriptor",
] ]
setup( setup(
......
.image-explorer-description {
padding-top: 10px;
padding-bottom: 10px;
}
.image-explorer-wrapper {
position: relative;
}
.image-explorer-hotspot-reveal {
background-color: rgba(0, 101, 189, 0.80);
color: #000000;
left: 100%;
position: absolute;
top: 25%;
width: 300px;
z-index: 1;
display: none;
}
.image-explorer-hotspot-content-wrapper {
margin: 10px 20px 10px 10px;
}
.image-explorer-hotspot-reveal.active {
display: block;
opacity: 1;
}
.image-explorer-hotspot-reveal-header {
font-family: "Open Sans Regular";
font-size: 16px;
}
.image-explorer-wrapper a.image-explorer-hotspot {
background: url("../images/image-explorer-hotspot-sprite.png") no-repeat scroll 0 0 rgba(0, 0, 0, 0);
width: 41px;
height: 41px;
display: block;
text-decoration: none;
-webkit-transition: all 0.0s linear 0s;
-moz-transition: all 0.0s linear 0s;
transition: all 0.0s linear 0s;
}
.image-explorer-wrapper a.image-explorer-hotspot:hover {
background-position: 0px -43px
}
a.image-explorer-hotspot .image-explorer-hotspot-reveal .image-explorer-hotspot-reveal-header p {
margin: 10px 0px 10px 20px;
}
.image-explorer-hotspot-reveal-body {
font-family: "Open Sans Regular";
font-size: 14px;
}
a.image-explorer-hotspot .image-explorer-hotspot-reveal .image-explorer-hotspot-reveal-body ul {
list-style-type: disc;
}
a.image-explorer-hotspot .image-explorer-hotspot-reveal .image-explorer-hotspot-reveal-body ul li {
margin: 0 0 24px 17px;
}
.image-explorer-close-reveal {
background: url("../images/image-explorer-reveal-close.png") no-repeat scroll 0 0 rgba(0, 0, 0, 0);
float: right;
width: 20px;
height: 20px;
margin-right: 5px;
margin-top: 5px;
}
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from lxml import etree
from xml.etree import ElementTree as ET
from xblock.fields import Scope, String
import textwrap
from pkg_resources import resource_string
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
class ImageExplorerFields(object):
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
default="Image Explorer"
)
data = String(help="XML contents to display for this module", scope=Scope.content, default=textwrap.dedent("""\
<image_explorer schema_version='1'>
<background src="//upload.wikimedia.org/wikipedia/commons/thumb/a/ac/MIT_Dome_night1_Edit.jpg/800px-MIT_Dome_night1_Edit.jpg" />
<description>
<p>
Enjoy using the Image Explorer. Click around the MIT Dome and see what you find!
</p>
</description>
<hotspots>
<hotspot x='370' y='20'>
<feedback width='300' height='170'>
<header>
<p>
This is where many pranks take place. Below are some of the highlights:
</p>
</header>
<body>
<ul>
<li>Once there was a police car up here</li>
<li>Also there was a Fire Truck put up there</li>
</ul>
</body>
</feedback>
</hotspot>
<hotspot x='250' y='70'>
<feedback width='420' height='360'>
<header>
<p>
Watch the Red Line subway go around the dome
</p>
</header>
<youtube video_id='dmoZXcuozFQ' width='400' height='300' />
</feedback>
</hotspot>
</hotspots>
</image_explorer>
"""))
class ImageExplorerModule(ImageExplorerFields, XModule):
"""
The xModule to render the Image Explorer
"""
css = {
'scss': [resource_string(__name__, 'css/image_explorer/display.scss')],
}
js = {
'coffee': [resource_string(__name__, 'js/src/image_explorer/display.coffee')],
}
js_module_name = "ImageExplorer"
def __init__(self, *args, **kwargs):
super(ImageExplorerModule, self).__init__(*args, **kwargs)
xmltree = etree.fromstring(self.data)
self.description = self._get_description(xmltree)
self.hotspots = self._get_hotspots(xmltree)
self.background = self._get_background(xmltree)
def get_html(self):
"""
Implementation of the XModule API entry point
"""
context = {
'title': self.display_name_with_default,
'description_html': self.description,
'hotspots': self.hotspots,
'background': self.background,
}
return self.system.render_template('image_explorer.html', context)
def _get_background(self, xmltree):
"""
Parse the XML to get the information about the background image
"""
background = xmltree.find('background')
return AttrDict({
'src': background.get('src'),
'width': background.get('width'),
'height': background.get('height')
})
def _inner_content(self, tag):
"""
Helper met
"""
if tag is not None:
return u''.join(ET.tostring(e) for e in tag)
return None
def _get_description(self, xmltree):
"""
Parse the XML to get the description information
"""
description = xmltree.find('description')
if description is not None:
return self._inner_content(description)
return None
def _get_hotspots(self, xmltree):
"""
Parse the XML to get the hotspot information
"""
hotspots_element= xmltree.find('hotspots')
hotspot_elements = hotspots_element.findall('hotspot')
hotspots = []
for hotspot_element in hotspot_elements:
feedback_element = hotspot_element.find('feedback')
feedback = AttrDict()
feedback.width = feedback_element.get('width')
feedback.height = feedback_element.get('height')
feedback.header = self._inner_content(feedback_element.find('header'))
feedback.body = None
body_element = feedback_element.find('body')
if body_element is not None:
feedback.type = 'text'
feedback.body = self._inner_content(body_element)
feedback.youtube = None
youtube_element = feedback_element.find('youtube')
if youtube_element is not None:
feedback.type = 'youtube'
feedback.youtube = AttrDict()
feedback.youtube.video_id = youtube_element.get('video_id')
feedback.youtube.width = youtube_element.get('width')
feedback.youtube.height = youtube_element.get('height')
hotspot = AttrDict()
hotspot.feedback = feedback
hotspot.x = hotspot_element.get('x')
hotspot.y = hotspot_element.get('y')
hotspots.append(hotspot)
return hotspots
class ImageExplorerDescriptor(ImageExplorerFields, RawDescriptor):
""" Descriptor for custom tags. Loads the template when created."""
module_class = ImageExplorerModule
template_dir_name = 'image_explorer'
def export_to_file(self):
"""
Custom tags are special: since they're already pointers, we don't want
to export them in a file with yet another layer of indirection.
"""
return False
<section class="xblock xblock-student_view xmodule_display xmodule_ImageExplorerModule" data-runtime-version="1" data-init="XBlockToXModuleShim" data-handler-prefix="/courses/IE2/IE2/IE2/xblock/i4x:;_;_IE2;_IE2;_vertical;_86850dab863840d7b2b5154ef4324382/handler" data-type="None" data-block-type="vertical"> <ol class="vert-mod"> <li id="vert-0" data-id="i4x://IE2/IE2/image_explorer/b54768e8201c4df997e6d3e846d7e37d"> <section class="xblock xblock-student_view xmodule_display xmodule_ImageExplorerModule" data-runtime-version="1" data-init="XBlockToXModuleShim" data-handler-prefix="/courses/IE2/IE2/IE2/xblock/i4x:;_;_IE2;_IE2;_image_explorer;_b54768e8201c4df997e6d3e846d7e37d/handler" data-type="ImageExplorer" data-block-type="image_explorer"> <div class="image-explorer-xmodule-wrapper"> <h2>This is a Title</h2> <div class="image-explorer-description"> <p> Enjoy using the Image Explorer. Click around and see what you find! </p> </div> <div class="image-explorer-wrapper"> <img src="/c4x/IE2/IE2/asset/image-explorer-demo.jpg" class="image-explorer-background" /> <a class="image-explorer-hotspot" style="position: absolute; top: 50px; left: 50px;" href="#"> <div class="image-explorer-hotspot-reveal" style="width: 400px;height: 300px;"> <div class="image-explorer-close-reveal"></div> <div class="image-explorer-hotspot-content-wrapper"> <span class="image-explorer-hotspot-reveal-header"> <p> Some description text that renders at the top </p> </span> <span class="image-explorer-hotspot-reveal-body"> <ul><li>This is the first bullet point</li><li>This is the second bullet point</li></ul> </span> </div> </div> </a> <a class="image-explorer-hotspot" style="position: absolute; top: 150px; left: 150px;" href="#"> <div class="image-explorer-hotspot-reveal" style="width: 100px;height: 80px;"> <div class="image-explorer-close-reveal"></div> <div class="image-explorer-hotspot-content-wrapper"> <span class="image-explorer-hotspot-reveal-header"> <p> Some description text that renders at the top </p> </span> <div class="image-explorer-hotspot-reveal-youtube"> <iframe id="player" type="text/html" width="100" height="80" src="http://www.youtube.com/embed/OEoXaMPEzfM?enablejsapi=1" frameborder="0"></iframe> </div> </div> </div> </a> </div> </div> </section> </li> </ol> </section>
describe 'ImageExplorer', ->
beforeEach ->
loadFixtures 'image_explorer.html'
describe 'constructor', ->
el = $('.xblock-student_view.xmodule_ImageExplorerModule')
beforeEach ->
@image_explorer = new ImageExplorer(el)
@image_explorer.close_hotspots()
it 'no hotspot should be visible on load', ->
# disable test for now as we figure out the Jasmine/css relationship
return
visible_counter = 0
$('.image-explorer-hotspot-reveal').each(
(index, el)->
if $(el).css('display') == 'block'
visible_counter = visible_counter + 1
)
expect(visible_counter).toBe(0)
it 'clicking on a hotspot will reveal an overlay and hide all others', ->
# disable test for now as we figure out the Jasmine/css relationship
return
$('.image-explorer-hotspot').each(
(index, el)->
el.click()
reveal = $(el).find('.image-explorer-hotspot-reveal')
expect(reveal.css('display')).toBe('block');
visible_counter = 0
$('.image-explorer-hotspot-reveal').each(
(index, el)->
if $(el).css('display') == 'block'
visible_counter = visible_counter + 1
)
expect(visible_counter).toBe(1)
)
class @ImageExplorer
# The client side code for ImageExplorer xModule
constructor: (element) ->
@el = $(element).find('.image-explorer-xmodule-wrapper')
@bind()
$: (selector) ->
$(selector, @el)
bind: =>
@$('.image-explorer-hotspot').click @hotspot_clicked
@$('.image-explorer-close-reveal').click @hotspot_closed_clicked
@el.click @close_hotspots
close_hotspots: =>
@$('.image-explorer-hotspot-reveal').css('display', 'none')
return
hotspot_clicked: (eventObj) =>
eventObj.preventDefault()
eventObj.stopPropagation()
@close_hotspots()
target = @$(eventObj.currentTarget)
target_position_left = target.position().left
hotspot_image_width = target.outerWidth()
reveal = target.find('.image-explorer-hotspot-reveal')
# see the width of the hotspot to show, see if it goes too far to the right
# if so then have the reveal go to the left of the hotspot icon
reveal_width = reveal.outerWidth()
parent_wrapper = reveal.parents('.image-explorer-hotspot')
image_element = parent_wrapper.siblings('.image-explorer-background')
image_width = image_element.outerWidth()
if (target_position_left + reveal_width > image_width) and (target_position_left - reveal_width - hotspot_image_width > 0)
reveal.css('margin-left', '-' + (reveal_width + hotspot_image_width) + 'px')
# show the reveal
reveal.css('display', 'block')
return
hotspot_closed_clicked: (eventObj) =>
eventObj.preventDefault()
eventObj.stopPropagation()
@close_hotspots()
return
<%! from django.utils.translation import ugettext as _ %>
<div class="image-explorer-xmodule-wrapper">
<h2>${title}</h2>
% if description_html:
<div class="image-explorer-description">
${description_html}
</div>
% endif
<div class="image-explorer-wrapper">
<img src="${background.src}" class="image-explorer-background" />
% for hotspot in hotspots:
<%
hotspot_reveal_style = 'style="{0}{1}"'.format(
'width: ' + hotspot.feedback.width + 'px;' if hotspot.feedback.width else '',
'height: ' + hotspot.feedback.height + 'px;' if hotspot.feedback.height else ''
)
hotspot_style = 'style="position: absolute; top: {0}px; left: {1}px;"'.format(hotspot.y, hotspot.x)
%>
<a class="image-explorer-hotspot" ${hotspot_style} href="#">
<div class="image-explorer-hotspot-reveal" ${hotspot_reveal_style}>
<div class="image-explorer-close-reveal"></div>
<div class="image-explorer-hotspot-content-wrapper">
% if hotspot.feedback.header:
<span class="image-explorer-hotspot-reveal-header">
${hotspot.feedback.header}
</span>
% endif
% if hotspot.feedback.body:
<span class="image-explorer-hotspot-reveal-body">
${hotspot.feedback.body}
</span>
% endif
% if hotspot.feedback.youtube:
<div class="image-explorer-hotspot-reveal-youtube">
<iframe id="player" type="text/html" width="${hotspot.feedback.youtube.width}" height="${hotspot.feedback.youtube.height}" src="//www.youtube.com/embed/${hotspot.feedback.youtube.video_id}?enablejsapi=1" frameborder="0"></iframe>
</div>
% endif
</div>
</div>
</a>
% endfor
</div>
</div>
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