+
+
+
+
+
The main goal of this exercise is to start practicing the art of slow reading.
+
+
+
+
+ |87 No, those who are really responsible are Zeus and Fate [Moira] and the Fury [Erinys] who roams in the mist.
+ |88 They are the ones who
+ |100 He [= Zeus], making a formal declaration [eukhesthai], spoke up at a meeting of all the gods and said:
+ |101 “hear me, all gods and all goddesses,
+ |113 but he swore a great oath.
+ And right then and there
+
+
+
+
+
+
+
+
diff --git a/common/lib/xmodule/xmodule/js/spec/annotatable/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/annotatable/display_spec.coffee
new file mode 100644
index 0000000000..3adb028f97
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/spec/annotatable/display_spec.coffee
@@ -0,0 +1,9 @@
+describe 'Annotatable', ->
+ beforeEach ->
+ loadFixtures 'annotatable.html'
+ describe 'constructor', ->
+ el = $('.xmodule_display.xmodule_AnnotatableModule')
+ beforeEach ->
+ @annotatable = new Annotatable(el)
+ it 'works', ->
+ expect(1).toBe(1)
\ No newline at end of file
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 0000000000..2ad49ae6d7
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee
@@ -0,0 +1,197 @@
+class @Annotatable
+ _debug: false
+
+ # selectors for the annotatable xmodule
+ toggleAnnotationsSelector: '.annotatable-toggle-annotations'
+ toggleInstructionsSelector: '.annotatable-toggle-instructions'
+ instructionsSelector: '.annotatable-instructions'
+ sectionSelector: '.annotatable-section'
+ spanSelector: '.annotatable-span'
+ replySelector: '.annotatable-reply'
+
+ # these selectors are for responding to events from the annotation capa problem type
+ problemXModuleSelector: '.xmodule_CapaModule'
+ problemSelector: 'section.problem'
+ problemInputSelector: 'section.problem .annotation-input'
+ problemReturnSelector: 'section.problem .annotation-return'
+
+ constructor: (el) ->
+ console.log 'loaded Annotatable' if @_debug
+ @el = el
+ @$el = $(el)
+ @init()
+
+ $: (selector) ->
+ $(selector, @el)
+
+ init: () ->
+ @initEvents()
+ @initTips()
+
+ initEvents: () ->
+ # Initialize toggle handlers for the instructions and annotations sections
+ [@annotationsHidden, @instructionsHidden] = [false, false]
+ @$(@toggleAnnotationsSelector).bind 'click', @onClickToggleAnnotations
+ @$(@toggleInstructionsSelector).bind 'click', @onClickToggleInstructions
+
+ # Initialize handler for 'reply to annotation' events that scroll to
+ # the associated problem. The reply buttons are part of the tooltip
+ # content. It's important that the tooltips be configured to render
+ # as descendants of the annotation module and *not* the document.body.
+ @$el.delegate @replySelector, 'click', @onClickReply
+
+ # Initialize handler for 'return to annotation' events triggered from problems.
+ # 1) There are annotationinput capa problems rendered on the page
+ # 2) Each one has an embedded return link (see annotation capa problem template).
+ # Since the capa problem injects HTML content via AJAX, the best we can do is
+ # is let the click events bubble up to the body and handle them there.
+ $('body').delegate @problemReturnSelector, 'click', @onClickReturn
+
+ initTips: () ->
+ # tooltips are used to display annotations for highlighted text spans
+ @$(@spanSelector).each (index, el) =>
+ $(el).qtip(@getSpanTipOptions el)
+
+ getSpanTipOptions: (el) ->
+ content:
+ title:
+ text: @makeTipTitle(el)
+ text: @makeTipContent(el)
+ position:
+ my: 'bottom center' # of tooltip
+ at: 'top center' # of target
+ target: $(el) # where the tooltip was triggered (i.e. the annotation span)
+ container: @$el
+ adjust:
+ y: -5
+ show:
+ event: 'click mouseenter'
+ solo: true
+ hide:
+ event: 'click mouseleave'
+ delay: 500,
+ fixed: true # don't hide the tooltip if it is moused over
+ style:
+ classes: 'ui-tooltip-annotatable'
+ events:
+ show: @onShowTip
+
+ onClickToggleAnnotations: (e) => @toggleAnnotations()
+
+ onClickToggleInstructions: (e) => @toggleInstructions()
+
+ onClickReply: (e) => @replyTo(e.currentTarget)
+
+ onClickReturn: (e) => @returnFrom(e.currentTarget)
+
+ onShowTip: (event, api) =>
+ event.preventDefault() if @annotationsHidden
+
+ getSpanForProblemReturn: (el) ->
+ problem_id = $(@problemReturnSelector).index(el)
+ @$(@spanSelector).filter("[data-problem-id='#{problem_id}']")
+
+ getProblem: (el) ->
+ problem_id = @getProblemId(el)
+ $(@problemSelector).has(@problemInputSelector).eq(problem_id)
+
+ getProblemId: (el) ->
+ $(el).data('problem-id')
+
+ toggleAnnotations: () ->
+ hide = (@annotationsHidden = not @annotationsHidden)
+ @toggleAnnotationButtonText hide
+ @toggleSpans hide
+ @toggleTips hide
+
+ toggleTips: (hide) ->
+ visible = @findVisibleTips()
+ @hideTips visible
+
+ toggleAnnotationButtonText: (hide) ->
+ buttonText = (if hide then 'Show' else 'Hide')+' Annotations'
+ @$(@toggleAnnotationsSelector).text(buttonText)
+
+ toggleInstructions: () ->
+ hide = (@instructionsHidden = not @instructionsHidden)
+ @toggleInstructionsButton hide
+ @toggleInstructionsText hide
+
+ toggleInstructionsButton: (hide) ->
+ txt = (if hide then 'Expand' else 'Collapse')+' Instructions'
+ cls = (if hide then ['expanded', 'collapsed'] else ['collapsed','expanded'])
+ @$(@toggleInstructionsSelector).text(txt).removeClass(cls[0]).addClass(cls[1])
+
+ toggleInstructionsText: (hide) ->
+ slideMethod = (if hide then 'slideUp' else 'slideDown')
+ @$(@instructionsSelector)[slideMethod]()
+
+ toggleSpans: (hide) ->
+ @$(@spanSelector).toggleClass 'hide', hide, 250
+
+ replyTo: (buttonEl) ->
+ offset = -20
+ el = @getProblem buttonEl
+ if el.length > 0
+ @scrollTo(el, @afterScrollToProblem, offset)
+ else
+ console.log('problem not found. event: ', e) if @_debug
+
+ returnFrom: (buttonEl) ->
+ offset = -200
+ el = @getSpanForProblemReturn buttonEl
+ if el.length > 0
+ @scrollTo(el, @afterScrollToSpan, offset)
+ else
+ console.log('span not found. event:', e) if @_debug
+
+ scrollTo: (el, after, offset = -20) ->
+ $('html,body').scrollTo(el, {
+ duration: 500
+ onAfter: @_once => after?.call this, el
+ offset: offset
+ }) if $(el).length > 0
+
+ afterScrollToProblem: (problem_el) ->
+ problem_el.effect 'highlight', {}, 500
+
+ afterScrollToSpan: (span_el) ->
+ span_el.addClass 'selected', 400, 'swing', ->
+ span_el.removeClass 'selected', 400, 'swing'
+
+ makeTipContent: (el) ->
+ (api) =>
+ text = $(el).data('comment-body')
+ comment = @createComment(text)
+ problem_id = @getProblemId(el)
+ reply = @createReplyLink(problem_id)
+ $(comment).add(reply)
+
+ makeTipTitle: (el) ->
+ (api) =>
+ title = $(el).data('comment-title')
+ (if title then title else 'Commentary')
+
+ createComment: (text) ->
+ $("")
+
+ createReplyLink: (problem_id) ->
+ $("
Reply to Annotation")
+
+ findVisibleTips: () ->
+ visible = []
+ @$(@spanSelector).each (index, el) ->
+ api = $(el).qtip('api')
+ tip = $(api?.elements.tooltip)
+ if tip.is(':visible')
+ visible.push el
+ visible
+
+ hideTips: (elements) ->
+ $(elements).qtip('hide')
+
+ _once: (fn) ->
+ done = false
+ return =>
+ fn.call this unless done
+ done = true
diff --git a/common/lib/xmodule/xmodule/templates/annotatable/default.yaml b/common/lib/xmodule/xmodule/templates/annotatable/default.yaml
new file mode 100644
index 0000000000..31dd489fb4
--- /dev/null
+++ b/common/lib/xmodule/xmodule/templates/annotatable/default.yaml
@@ -0,0 +1,20 @@
+---
+metadata:
+ display_name: 'Annotation'
+data: |
+
+
+ Enter your (optional) instructions for the exercise in HTML format.
+ Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+ title (optional). Title of the annotation. Defaults to Commentary if omitted.
+ body (required). Text of the annotation.
+ problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+ highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
+
+
+ Add your HTML with annotation spans here.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+ Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
+
+children: []
diff --git a/common/lib/xmodule/xmodule/tests/test_annotatable_module.py b/common/lib/xmodule/xmodule/tests/test_annotatable_module.py
new file mode 100644
index 0000000000..30f9c9ff92
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_annotatable_module.py
@@ -0,0 +1,129 @@
+"""Module annotatable tests"""
+
+import unittest
+
+from lxml import etree
+from mock import Mock
+
+from xmodule.annotatable_module import AnnotatableModule
+from xmodule.modulestore import Location
+
+from . import test_system
+
+class AnnotatableModuleTestCase(unittest.TestCase):
+ location = Location(["i4x", "edX", "toy", "annotatable", "guided_discussion"])
+ sample_xml = '''
+
+ Read the text.
+
+ Sing,
+ O goddess,
+ the anger of Achilles son of Peleus,
+ that brought countless ills upon the Achaeans. Many a brave soul did it send
+ hurrying down to Hades, and many a hero did it yield a prey to dogs and
+
vultures, for so were the counsels
+ of Jove fulfilled from the day on which the son of Atreus, king of men, and great
+ Achilles, first fell out with one another.
+
+ The Iliad of Homer by Samuel Butler
+
+ '''
+ definition = { 'data': sample_xml }
+ descriptor = Mock()
+ instance_state = None
+ shared_state = None
+
+ def setUp(self):
+ self.annotatable = AnnotatableModule(test_system(), self.location, self.definition, self.descriptor, self.instance_state, self.shared_state)
+
+ def test_annotation_data_attr(self):
+ el = etree.fromstring('
test')
+
+ expected_attr = {
+ 'data-comment-body': {'value': 'foo', '_delete': 'body' },
+ 'data-comment-title': {'value': 'bar', '_delete': 'title'},
+ 'data-problem-id': {'value': '0', '_delete': 'problem'}
+ }
+
+ actual_attr = self.annotatable._get_annotation_data_attr(0, el)
+
+ self.assertTrue(type(actual_attr) is dict)
+ self.assertDictEqual(expected_attr, actual_attr)
+
+ def test_annotation_class_attr_default(self):
+ xml = '
test'
+ el = etree.fromstring(xml)
+
+ expected_attr = { 'class': { 'value': 'annotatable-span highlight' } }
+ actual_attr = self.annotatable._get_annotation_class_attr(0, el)
+
+ self.assertTrue(type(actual_attr) is dict)
+ self.assertDictEqual(expected_attr, actual_attr)
+
+ def test_annotation_class_attr_with_valid_highlight(self):
+ xml = '
test'
+
+ for color in self.annotatable.highlight_colors:
+ el = etree.fromstring(xml.format(highlight=color))
+ value = 'annotatable-span highlight highlight-{highlight}'.format(highlight=color)
+
+ expected_attr = { 'class': {
+ 'value': value,
+ '_delete': 'highlight' }
+ }
+ actual_attr = self.annotatable._get_annotation_class_attr(0, el)
+
+ self.assertTrue(type(actual_attr) is dict)
+ self.assertDictEqual(expected_attr, actual_attr)
+
+ def test_annotation_class_attr_with_invalid_highlight(self):
+ xml = '
test'
+
+ for invalid_color in ['rainbow', 'blink', 'invisible', '', None]:
+ el = etree.fromstring(xml.format(highlight=invalid_color))
+ expected_attr = { 'class': {
+ 'value': 'annotatable-span highlight',
+ '_delete': 'highlight' }
+ }
+ actual_attr = self.annotatable._get_annotation_class_attr(0, el)
+
+ self.assertTrue(type(actual_attr) is dict)
+ self.assertDictEqual(expected_attr, actual_attr)
+
+ def test_render_annotation(self):
+ expected_html = '
z'
+ expected_el = etree.fromstring(expected_html)
+
+ actual_el = etree.fromstring('
z')
+ self.annotatable._render_annotation(0, actual_el)
+
+ self.assertEqual(expected_el.tag, actual_el.tag)
+ self.assertEqual(expected_el.text, actual_el.text)
+ self.assertDictEqual(dict(expected_el.attrib), dict(actual_el.attrib))
+
+ def test_render_content(self):
+ content = self.annotatable._render_content()
+ el = etree.fromstring(content)
+
+ self.assertEqual('div', el.tag, 'root tag is a div')
+
+ expected_num_annotations = 5
+ actual_num_annotations = el.xpath('count(//span[contains(@class,"annotatable-span")])')
+ self.assertEqual(expected_num_annotations, actual_num_annotations, 'check number of annotations')
+
+ def test_get_html(self):
+ context = self.annotatable.get_html()
+ for key in ['display_name', 'element_id', 'content_html', 'instructions_html']:
+ self.assertIn(key, context)
+
+ def test_extract_instructions(self):
+ xmltree = etree.fromstring(self.sample_xml)
+
+ expected_xml = u"
Read the text.
"
+ actual_xml = self.annotatable._extract_instructions(xmltree)
+ self.assertIsNotNone(actual_xml)
+ self.assertEqual(expected_xml.strip(), actual_xml.strip())
+
+ xmltree = etree.fromstring('
foo')
+ actual = self.annotatable._extract_instructions(xmltree)
+ self.assertIsNone(actual)
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index a1e3d31d76..6330511fc5 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -44,7 +44,7 @@ class CapaFactory(object):
@staticmethod
def answer_key():
""" Return the key stored in the capa problem answer dict """
- return ("-".join(['i4x', 'edX', 'capa_test', 'problem',
+ return ("-".join(['i4x', 'edX', 'capa_test', 'problem',
'SampleProblem%d' % CapaFactory.num]) +
"_2_1")
@@ -144,6 +144,8 @@ class CapaModuleTest(unittest.TestCase):
"Factory should be creating unique names for each problem")
+
+
def test_correct(self):
"""
Check that the factory creates correct and incorrect problems properly.
@@ -332,7 +334,7 @@ class CapaModuleTest(unittest.TestCase):
'input_4': None,
'input_5': [],
'input_6': 5}
-
+
result = CapaModule.make_dict_of_responses(valid_get_dict)
# Expect that we get a dict with "input" stripped from key names
@@ -475,7 +477,7 @@ class CapaModuleTest(unittest.TestCase):
mock_is_queued.return_value = True
mock_get_queuetime.return_value = datetime.datetime.now()
-
+
get_request_dict = { CapaFactory.input_key(): '3.14' }
result = module.check_problem(get_request_dict)
@@ -506,7 +508,7 @@ class CapaModuleTest(unittest.TestCase):
def test_reset_problem(self):
module = CapaFactory.create()
- # Mock the module's capa problem
+ # Mock the module's capa problem
# to simulate that the problem is done
mock_problem = MagicMock(capa.capa_problem.LoncapaProblem)
mock_problem.done = True
@@ -668,7 +670,7 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(max_attempts=0)
self.assertFalse(module.should_show_check_button())
- # If user submitted a problem but hasn't reset,
+ # If user submitted a problem but hasn't reset,
# do NOT show the check button
# Note: we can only reset when rerandomize="always"
module = CapaFactory.create(rerandomize="always")
@@ -707,7 +709,7 @@ class CapaModuleTest(unittest.TestCase):
module.lcp.done = True
self.assertFalse(module.should_show_reset_button())
- # If the user hasn't submitted an answer yet,
+ # If the user hasn't submitted an answer yet,
# then do NOT show the reset button
module = CapaFactory.create()
module.lcp.done = False
@@ -770,7 +772,7 @@ class CapaModuleTest(unittest.TestCase):
# If the user is out of attempts, do NOT show the save button
attempts = random.randint(1,10)
- module = CapaFactory.create(attempts=attempts,
+ module = CapaFactory.create(attempts=attempts,
max_attempts=attempts,
force_save_button="true")
module.lcp.done = True
@@ -784,6 +786,12 @@ class CapaModuleTest(unittest.TestCase):
module.lcp.done = True
self.assertTrue(module.should_show_save_button())
+ def test_no_max_attempts(self):
+ module = CapaFactory.create(max_attempts='')
+ html = module.get_problem_html()
+ # assert that we got here without exploding
+
+
def test_get_problem_html(self):
module = CapaFactory.create()
@@ -797,7 +805,7 @@ class CapaModuleTest(unittest.TestCase):
module.should_show_reset_button = Mock(return_value=show_reset_button)
module.should_show_save_button = Mock(return_value=show_save_button)
- # Mock the system rendering function
+ # Mock the system rendering function
module.system.render_template = Mock(return_value="
Test Template HTML
")
# Patch the capa problem's HTML rendering
@@ -809,7 +817,7 @@ class CapaModuleTest(unittest.TestCase):
# Also render the problem encapsulated in a
html_encapsulated = module.get_problem_html(encapsulate=True)
-
+
# Expect that we get the rendered template back
self.assertEqual(html, "
Test Template HTML
")
@@ -831,7 +839,7 @@ class CapaModuleTest(unittest.TestCase):
def test_get_problem_html_error(self):
- """
+ """
In production, when an error occurs with the problem HTML
rendering, a "dummy" problem is created with an error
message to display to the user.
@@ -845,10 +853,10 @@ class CapaModuleTest(unittest.TestCase):
# is asked to render itself as HTML
module.lcp.get_html = Mock(side_effect=Exception("Test"))
- # Stub out the test_system rendering function
+ # Stub out the test_system rendering function
module.system.render_template = Mock(return_value="
Test Template HTML
")
- # Turn off DEBUG
+ # Turn off DEBUG
module.system.DEBUG = False
# Try to render the module with DEBUG turned off
@@ -860,4 +868,4 @@ class CapaModuleTest(unittest.TestCase):
self.assertTrue("error" in context['problem']['html'])
# Expect that the module has created a new dummy problem with the error
- self.assertNotEqual(original_problem, module.lcp)
+ self.assertNotEqual(original_problem, module.lcp)
diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py
index 773531c528..7087a03759 100644
--- a/common/lib/xmodule/xmodule/xml_module.py
+++ b/common/lib/xmodule/xmodule/xml_module.py
@@ -379,7 +379,11 @@ class XmlDescriptor(XModuleDescriptor):
if attr not in self.metadata_to_strip and attr not in self.metadata_to_export_to_policy:
val = val_for_xml(attr)
#logging.debug('location.category = {0}, attr = {1}'.format(self.location.category, attr))
- xml_object.set(attr, val)
+ try:
+ xml_object.set(attr, val)
+ except Exception, e:
+ logging.exception('Failed to serialize metadata attribute {0} with value {1}. This could mean data loss!!! Exception: {2}'.format(attr, val, e))
+ pass
if self.export_to_file():
# Write the definition to a file
diff --git a/common/static/coffee/src/discussion/discussion_module_view.coffee b/common/static/coffee/src/discussion/discussion_module_view.coffee
index 2e58b2c0b8..3dde9bf950 100644
--- a/common/static/coffee/src/discussion/discussion_module_view.coffee
+++ b/common/static/coffee/src/discussion/discussion_module_view.coffee
@@ -88,7 +88,7 @@ if Backbone?
if @$('section.discussion').length
@$('section.discussion').replaceWith($discussion)
else
- $(".discussion-module").append($discussion)
+ @$el.append($discussion)
@newPostForm = $('.new-post-article')
@threadviews = @discussion.map (thread) ->
new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
diff --git a/common/static/images/partially-correct-icon.png b/common/static/images/partially-correct-icon.png
new file mode 100644
index 0000000000..9ac0fd32f7
Binary files /dev/null and b/common/static/images/partially-correct-icon.png differ
diff --git a/common/static/js/capa/annotationinput.js b/common/static/js/capa/annotationinput.js
new file mode 100644
index 0000000000..4353fd262a
--- /dev/null
+++ b/common/static/js/capa/annotationinput.js
@@ -0,0 +1,97 @@
+(function () {
+ var debug = false;
+
+ var module = {
+ debug: debug,
+ inputSelector: '.annotation-input',
+ tagSelector: '.tag',
+ tagsSelector: '.tags',
+ commentSelector: 'textarea.comment',
+ valueSelector: 'input.value', // stash tag selections and comment here as a JSON string...
+
+ singleSelect: true,
+
+ init: function() {
+ var that = this;
+
+ if(this.debug) { console.log('annotation input loaded: '); }
+
+ $(this.inputSelector).each(function(index, el) {
+ if(!$(el).data('listening')) {
+ $(el).delegate(that.tagSelector, 'click', $.proxy(that.onClickTag, that));
+ $(el).delegate(that.commentSelector, 'change', $.proxy(that.onChangeComment, that));
+ $(el).data('listening', 'yes');
+ }
+ });
+ },
+ onChangeComment: function(e) {
+ var value_el = this.findValueEl(e.target);
+ var current_value = this.loadValue(value_el);
+ var target_value = $(e.target).val();
+
+ current_value.comment = target_value;
+ this.storeValue(value_el, current_value);
+ },
+ onClickTag: function(e) {
+ var target_el = e.target, target_value, target_index;
+ var value_el, current_value;
+
+ value_el = this.findValueEl(e.target);
+ current_value = this.loadValue(value_el);
+ target_value = $(e.target).data('id');
+
+ if(!$(target_el).hasClass('selected')) {
+ if(this.singleSelect) {
+ current_value.options = [target_value]
+ } else {
+ current_value.options.push(target_value);
+ }
+ } else {
+ if(this.singleSelect) {
+ current_value.options = []
+ } else {
+ target_index = current_value.options.indexOf(target_value);
+ if(target_index !== -1) {
+ current_value.options.splice(target_index, 1);
+ }
+ }
+ }
+
+ this.storeValue(value_el, current_value);
+
+ if(this.singleSelect) {
+ $(target_el).closest(this.tagsSelector)
+ .find(this.tagSelector)
+ .not(target_el)
+ .removeClass('selected')
+ }
+ $(target_el).toggleClass('selected');
+ },
+ findValueEl: function(target_el) {
+ var input_el = $(target_el).closest(this.inputSelector);
+ return $(this.valueSelector, input_el);
+ },
+ loadValue: function(value_el) {
+ var json = $(value_el).val();
+
+ var result = JSON.parse(json);
+ if(result === null) {
+ result = {};
+ }
+ if(!result.hasOwnProperty('options')) {
+ result.options = [];
+ }
+ if(!result.hasOwnProperty('comment')) {
+ result.comment = '';
+ }
+
+ return result;
+ },
+ storeValue: function(value_el, new_value) {
+ var json = JSON.stringify(new_value);
+ $(value_el).val(json);
+ }
+ }
+
+ module.init();
+}).call(this);
diff --git a/common/static/js/capa/chemical_equation_preview.js b/common/static/js/capa/chemical_equation_preview.js
index 90ce27ad11..10a6b54655 100644
--- a/common/static/js/capa/chemical_equation_preview.js
+++ b/common/static/js/capa/chemical_equation_preview.js
@@ -11,9 +11,14 @@
}
prev_id = "#" + this.id + "_preview";
- preview_div = $(prev_id)
+ preview_div = $(prev_id);
- $.get("/preview/chemcalc/", {"formula" : this.value}, create_handler(preview_div));
+ // find the closest parent problems-wrapper and use that url
+ url = $(this).closest('.problems-wrapper').data('url');
+ // grab the input id from the input
+ input_id = $(this).data('input-id')
+
+ Problem.inputAjax(url, input_id, 'preview_chemcalc', {"formula" : this.value}, create_handler(preview_div));
}
inputs = $('.chemicalequationinput input');
diff --git a/common/static/js/capa/edit-a-gene.js b/common/static/js/capa/edit-a-gene.js
index 48753e507d..bd6d10cc64 100644
--- a/common/static/js/capa/edit-a-gene.js
+++ b/common/static/js/capa/edit-a-gene.js
@@ -1,27 +1,44 @@
(function () {
var timeout = 1000;
- function initializeApplet(applet) {
- console.log("Initializing " + applet);
- waitForApplet(applet);
- }
+ waitForGenex();
- function waitForApplet(applet) {
- if (applet.isActive && applet.isActive()) {
- console.log("Applet is ready.");
- var answerStr = applet.checkAnswer();
- console.log(answerStr);
- var input = $('.editageneinput input');
- console.log(input);
- input.val(answerStr);
- } else if (timeout > 30 * 1000) {
- console.error("Applet did not load on time.");
- } else {
- console.log("Waiting for applet...");
- setTimeout(function() { waitForApplet(applet); }, timeout);
+ function waitForGenex() {
+ if (typeof(genex) !== "undefined" && genex) {
+ genex.onInjectionDone("genex");
+ }
+ else {
+ setTimeout(function() { waitForGenex(); }, timeout);
}
}
- var applets = $('.editageneinput object');
- applets.each(function(i, el) { initializeApplet(el); });
+ //NOTE:
+ // Genex uses six global functions:
+ // genexSetDNASequence (exported from GWT)
+ // genexSetClickEvent (exported from GWT)
+ // genexSetKeyEvent (exported from GWT)
+ // genexSetProblemNumber (exported from GWT)
+ //
+ // It calls genexIsReady with a deferred command when it has finished
+ // initialization and has drawn itself
+ // genexStoreAnswer(answer) is called when the GWT [Store Answer] button
+ // is clicked
+
+ genexIsReady = function() {
+ //Load DNA sequence
+ var dna_sequence = $('#dna_sequence').val();
+ genexSetDNASequence(dna_sequence);
+ //Now load mouse and keyboard handlers
+ genexSetClickEvent();
+ genexSetKeyEvent();
+ //Now load problem
+ var genex_problem_number = $('#genex_problem_number').val();
+ genexSetProblemNumber(genex_problem_number);
+ };
+ genexStoreAnswer = function(ans) {
+ var problem = $('#genex_container').parents('.problem');
+ var input_field = problem.find('input[type="hidden"][name!="dna_sequence"][name!="genex_problem_number"]');
+ input_field.val(ans);
+ };
}).call(this);
+
diff --git a/common/static/js/capa/genex/026A6180B5959B8660E084245FEE5E9E.cache.html b/common/static/js/capa/genex/026A6180B5959B8660E084245FEE5E9E.cache.html
new file mode 100644
index 0000000000..13f25ec581
--- /dev/null
+++ b/common/static/js/capa/genex/026A6180B5959B8660E084245FEE5E9E.cache.html
@@ -0,0 +1,649 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/1F433010E1134C95BF6CB43F552F3019.cache.html b/common/static/js/capa/genex/1F433010E1134C95BF6CB43F552F3019.cache.html
new file mode 100644
index 0000000000..1e99fe0f19
--- /dev/null
+++ b/common/static/js/capa/genex/1F433010E1134C95BF6CB43F552F3019.cache.html
@@ -0,0 +1,649 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/2DDA730EDABB80B88A6B0DFA3AFEACA2.cache.html b/common/static/js/capa/genex/2DDA730EDABB80B88A6B0DFA3AFEACA2.cache.html
new file mode 100644
index 0000000000..743492768b
--- /dev/null
+++ b/common/static/js/capa/genex/2DDA730EDABB80B88A6B0DFA3AFEACA2.cache.html
@@ -0,0 +1,639 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/4EEB1DCF4B30D366C27968D1B5C0BD04.cache.html b/common/static/js/capa/genex/4EEB1DCF4B30D366C27968D1B5C0BD04.cache.html
new file mode 100644
index 0000000000..4aa12e55d4
--- /dev/null
+++ b/common/static/js/capa/genex/4EEB1DCF4B30D366C27968D1B5C0BD04.cache.html
@@ -0,0 +1,651 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/5033ABB047340FB9346B622E2CC7107D.cache.html b/common/static/js/capa/genex/5033ABB047340FB9346B622E2CC7107D.cache.html
new file mode 100644
index 0000000000..167a193adb
--- /dev/null
+++ b/common/static/js/capa/genex/5033ABB047340FB9346B622E2CC7107D.cache.html
@@ -0,0 +1,625 @@
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/DF3D3A7FAEE63D711CF2D95BDB3F538C.cache.html b/common/static/js/capa/genex/DF3D3A7FAEE63D711CF2D95BDB3F538C.cache.html
new file mode 100644
index 0000000000..913b90be20
--- /dev/null
+++ b/common/static/js/capa/genex/DF3D3A7FAEE63D711CF2D95BDB3F538C.cache.html
@@ -0,0 +1,639 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/static/js/capa/genex/clear.cache.gif b/common/static/js/capa/genex/clear.cache.gif
new file mode 100644
index 0000000000..e565824aaf
Binary files /dev/null and b/common/static/js/capa/genex/clear.cache.gif differ
diff --git a/common/static/js/capa/genex/genex.css b/common/static/js/capa/genex/genex.css
new file mode 100644
index 0000000000..a05f31110b
--- /dev/null
+++ b/common/static/js/capa/genex/genex.css
@@ -0,0 +1,109 @@
+.genex-button {
+ margin-right: -8px;
+ height: 40px !important;
+}
+
+.genex-label {
+ /*font: normal normal normal 10pt/normal 'Open Sans', Verdana, Geneva, sans-serif !important;*/
+ /*padding: 4px 0px 0px 10px !important;*/
+ font-family: sans-serif !important;
+ font-size: 13px !important;
+ font-style: normal !important;
+ font-variant: normal !important;
+ font-weight: bold !important;
+ padding-top: 6px !important;
+ margin-left: 18px;
+}
+
+.gwt-HTML {
+ cursor: default;
+ overflow-x: auto !important;
+ overflow-y: auto !important;
+ background-color: rgb(248, 248, 248) !important;
+}
+
+.genex-scrollpanel {
+ word-wrap: normal !important;
+ white-space: pre !important;
+}
+
+pre, #dna-strand {
+ font-family: 'courier new', courier !important;
+ font-size: 13px !important;
+ font-style: normal !important;
+ font-variant: normal !important;
+ font-weight: normal !important;
+ border-style: none !important;
+ background-color: rgb(248, 248, 248) !important;
+ word-wrap: normal !important;
+ white-space: pre !important;
+ overflow-x: visible !important;
+ overflow-y: visible !important;
+}
+
+.gwt-DialogBox .Caption {
+ background: #F1F1F1;
+ padding: 4px 8px 4px 4px;
+ cursor: default;
+ font-family: Arial Unicode MS, Arial, sans-serif;
+ font-weight: bold;
+ border-bottom: 1px solid #bbbbbb;
+ border-top: 1px solid #D2D2D2;
+}
+.gwt-DialogBox .dialogContent {
+}
+.gwt-DialogBox .dialogMiddleCenter {
+ padding: 3px;
+ background: white;
+}
+.gwt-DialogBox .dialogBottomCenter {
+}
+.gwt-DialogBox .dialogMiddleLeft {
+}
+.gwt-DialogBox .dialogMiddleRight {
+}
+.gwt-DialogBox .dialogTopLeftInner {
+ width: 10px;
+ height: 8px;
+ zoom: 1;
+}
+.gwt-DialogBox .dialogTopRightInner {
+ width: 12px;
+ zoom: 1;
+}
+.gwt-DialogBox .dialogBottomLeftInner {
+ width: 10px;
+ height: 12px;
+ zoom: 1;
+}
+.gwt-DialogBox .dialogBottomRightInner {
+ width: 12px;
+ height: 12px;
+ zoom: 1;
+}
+.gwt-DialogBox .dialogTopLeft {
+}
+.gwt-DialogBox .dialogTopRight {
+}
+.gwt-DialogBox .dialogBottomLeft {
+}
+.gwt-DialogBox .dialogBottomRight {
+}
+* html .gwt-DialogBox .dialogTopLeftInner {
+ width: 10px;
+ overflow: hidden;
+}
+* html .gwt-DialogBox .dialogTopRightInner {
+ width: 12px;
+ overflow: hidden;
+}
+* html .gwt-DialogBox .dialogBottomLeftInner {
+ width: 10px;
+ height: 12px;
+ overflow: hidden;
+}
+* html .gwt-DialogBox .dialogBottomRightInner {
+ width: 12px;
+ height: 12px;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/common/static/js/capa/genex/genex.nocache.js b/common/static/js/capa/genex/genex.nocache.js
new file mode 100644
index 0000000000..07da038234
--- /dev/null
+++ b/common/static/js/capa/genex/genex.nocache.js
@@ -0,0 +1,18 @@
+function genex(){var P='',xb='" for "gwt:onLoadErrorFn"',vb='" for "gwt:onPropertyErrorFn"',ib='"><\/script>',Z='#',Xb='.cache.html',_='/',lb='//',Qb='026A6180B5959B8660E084245FEE5E9E',Rb='1F433010E1134C95BF6CB43F552F3019',Sb='2DDA730EDABB80B88A6B0DFA3AFEACA2',Tb='4EEB1DCF4B30D366C27968D1B5C0BD04',Ub='5033ABB047340FB9346B622E2CC7107D',Wb=':',pb='::',dc='
+
+
This html file is for Development Mode support.
+