diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index a821ec9c12..95eb2c996c 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -76,9 +76,13 @@ class CapaModule(XModule): ''' icon_class = 'problem' - js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee')], + js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/javascript_loader.coffee'), + ], 'js': [resource_string(__name__, 'js/src/capa/imageinput.js'), resource_string(__name__, 'js/src/capa/schematic.js')]} + js_module_name = "Problem" css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]} diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 0ed64897e2..2023ac7017 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -21,7 +21,11 @@ log = logging.getLogger("mitx.courseware") class HtmlModule(XModule): - js = {'coffee': [resource_string(__name__, 'js/src/html/display.coffee')]} + 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') + ] + } js_module_name = "HTMLModule" def get_html(self): diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 3b45723ae5..123f68145a 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -27,11 +27,7 @@ class @Problem @$('section.action input.save').click @save # Collapsibles - @$('.longform').hide(); - @$('.shortform').append('See full output'); - @$('.collapsible section').hide(); - @$('.full').click @toggleFull - @$('.collapsible header a').click @toggleHint + Collapsible.setCollapsibles(@el) # Dynamath @$('input.math').keyup(@refreshMath) @@ -67,7 +63,7 @@ class @Problem @new_queued_items = $(response.html).find(".xqueue") if @new_queued_items.length isnt @num_queued_items @el.html(response.html) - @executeProblemScripts () => + JavascriptLoader.executeModuleScripts @el, () => @setupInputTypes() @bind() @@ -81,18 +77,19 @@ class @Problem render: (content) -> if content @el.html(content) - @executeProblemScripts () => + JavascriptLoader.executeModuleScripts @el, () => @setupInputTypes() @bind() @queueing() else $.postWithPrefix "#{@url}/problem_get", (response) => @el.html(response.html) - @executeProblemScripts () => + JavascriptLoader.executeModuleScripts @el, () => @setupInputTypes() @bind() @queueing() + # TODO add hooks for problem types here by inspecting response.html and doing # stuff if a div w a class is found @@ -106,50 +103,6 @@ class @Problem if setupMethod? @inputtypeDisplays[id] = setupMethod(inputtype) - executeProblemScripts: (callback=null) -> - - placeholders = @el.find(".script_placeholder") - - if placeholders.length == 0 - callback() - return - - completed = (false for i in [1..placeholders.length]) - callbackCalled = false - - # This is required for IE8 support. - completionHandlerGeneratorIE = (index) => - return () -> - if (this.readyState == 'complete' || this.readyState == 'loaded') - #completionHandlerGenerator.call(self, index)() - completionHandlerGenerator(index)() - - completionHandlerGenerator = (index) => - return () => - allComplete = true - completed[index] = true - for flag in completed - if not flag - allComplete = false - break - if allComplete and not callbackCalled - callbackCalled = true - callback() if callback? - - placeholders.each (index, placeholder) -> - s = document.createElement('script') - s.setAttribute('src', $(placeholder).attr("data-src")) - s.setAttribute('type', "text/javascript") - - s.onload = completionHandlerGenerator(index) - - # s.onload does not fire in IE8; this does. - s.onreadystatechange = completionHandlerGeneratorIE(index) - - # Need to use the DOM elements directly or the scripts won't execute - # properly. - $('head')[0].appendChild(s) - $(placeholder).remove() ### # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch, @@ -340,17 +293,6 @@ class @Problem element.CodeMirror.save() if element.CodeMirror.save @answers = @inputs.serialize() - toggleFull: (event) => - $(event.target).parent().siblings().slideToggle() - $(event.target).parent().parent().toggleClass('open') - text = $(event.target).text() == 'See full output' ? 'Hide output' : 'See full output' - $(this).text(text) - - toggleHint: (event) => - event.preventDefault() - $(event.target).parent().siblings().slideToggle() - $(event.target).parent().parent().toggleClass('open') - inputtypeSetupMethods: 'text-input-dynamath': (element) => diff --git a/common/lib/xmodule/xmodule/js/src/collapsible.coffee b/common/lib/xmodule/xmodule/js/src/collapsible.coffee new file mode 100644 index 0000000000..6e469d3704 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/collapsible.coffee @@ -0,0 +1,27 @@ +class @Collapsible + + # Set of library functions that provide a simple way to add collapsible + # functionality to elements. + + # setCollapsibles: + # Scan element's content for generic collapsible containers + @setCollapsibles: (el) => + ### + el: container + ### + el.find('.longform').hide() + el.find('.shortform').append('See full output') + el.find('.collapsible section').hide() + el.find('.full').click @toggleFull + el.find('.collapsible header a').click @toggleHint + + @toggleFull: (event) => + $(event.target).parent().siblings().slideToggle() + $(event.target).parent().parent().toggleClass('open') + text = $(event.target).text() == 'See full output' ? 'Hide output' : 'See full output' + $(this).text(text) + + @toggleHint: (event) => + event.preventDefault() + $(event.target).parent().siblings().slideToggle() + $(event.target).parent().parent().toggleClass('open') diff --git a/common/lib/xmodule/xmodule/js/src/html/display.coffee b/common/lib/xmodule/xmodule/js/src/html/display.coffee index 05e2ddab28..d39bacc8d0 100644 --- a/common/lib/xmodule/xmodule/js/src/html/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/html/display.coffee @@ -1,26 +1,9 @@ class @HTMLModule constructor: (@element) -> - @el = $(@element) - @setCollapsibles() + @el = $(@element) + JavascriptLoader.executeModuleScripts(@el) + Collapsible.setCollapsibles(@el) $: (selector) -> $(selector, @el) - - setCollapsibles: => - $('.longform').hide(); - $('.shortform').append('See full output'); - $('.collapsible section').hide(); - $('.full').click @toggleFull - $('.collapsible header a').click @toggleHint - - toggleFull: (event) => - $(event.target).parent().siblings().slideToggle() - $(event.target).parent().parent().toggleClass('open') - text = $(event.target).text() == 'See full output' ? 'Hide output' : 'See full output' - $(this).text(text) - - toggleHint: (event) => - event.preventDefault() - $(event.target).parent().siblings().slideToggle() - $(event.target).parent().parent().toggleClass('open') \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/javascript_loader.coffee b/common/lib/xmodule/xmodule/js/src/javascript_loader.coffee new file mode 100644 index 0000000000..543aec8edc --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/javascript_loader.coffee @@ -0,0 +1,60 @@ +class @JavascriptLoader + + # Set of library functions that provide common interface for javascript loading + # for all module types. All functionality provided by JavascriptLoader should take + # place at module scope, i.e. don't run jQuery over entire page + + # executeModuleScripts: + # Scan the module ('el') for "script_placeholder"s, then: + # 1) Fetch each script from server + # 2) Explicitly attach the script to the of document + # 3) Explicitly wait for each script to be loaded + # 4) Return to callback function when all scripts loaded + @executeModuleScripts: (el, callback=null) -> + + placeholders = el.find(".script_placeholder") + + if placeholders.length == 0 + callback() + return + + # TODO: Verify the execution order of multiple placeholders + completed = (false for i in [1..placeholders.length]) + callbackCalled = false + + # This is required for IE8 support. + completionHandlerGeneratorIE = (index) => + return () -> + if (this.readyState == 'complete' || this.readyState == 'loaded') + #completionHandlerGenerator.call(self, index)() + completionHandlerGenerator(index)() + + completionHandlerGenerator = (index) => + return () => + allComplete = true + completed[index] = true + for flag in completed + if not flag + allComplete = false + break + if allComplete and not callbackCalled + callbackCalled = true + callback() if callback? + + placeholders.each (index, placeholder) -> + # TODO: Check if the script already exists in DOM. If so, (1) copy it + # into memory; (2) delete the DOM script element; (3) reappend it. + # This would prevent memory bloat and save a network request. + s = document.createElement('script') + s.setAttribute('src', $(placeholder).attr("data-src")) + s.setAttribute('type', "text/javascript") + + s.onload = completionHandlerGenerator(index) + + # s.onload does not fire in IE8; this does. + s.onreadystatechange = completionHandlerGeneratorIE(index) + + # Need to use the DOM elements directly or the scripts won't execute + # properly. + $('head')[0].appendChild(s) + $(placeholder).remove()