From aa8d22267851f0d5ab25cf86d63074143ff98266 Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 6 Dec 2016 15:38:34 -0500 Subject: [PATCH] New example jsinput problem. TNL-5893 --- .../templates/problem/jsinput_response.yaml | 55 ++++++------ .../js/capa/jsinput/jsinput_example.css | 9 ++ .../js/capa/jsinput/jsinput_example.html | 15 ++++ .../static/js/capa/jsinput/jsinput_example.js | 86 +++++++++++++++++++ 4 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 common/static/js/capa/jsinput/jsinput_example.css create mode 100644 common/static/js/capa/jsinput/jsinput_example.html create mode 100644 common/static/js/capa/jsinput/jsinput_example.js diff --git a/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml b/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml index 5a37b6ede3..20e7874695 100644 --- a/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml +++ b/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml @@ -1,6 +1,6 @@ --- metadata: - display_name: Custom Javascript Display and Grading + display_name: Custom JavaScript Display and Grading markdown: !!null showanswer: never data: | @@ -8,8 +8,8 @@ data: |

In these problems (also called custom JavaScript problems or JS Input problems), you add a problem or tool that uses JavaScript in Studio. - Studio embeds the problem in an IFrame so that your students can - interact with it in the LMS. You can grade your students' work using + Studio embeds the problem in an IFrame so that your learners can + interact with it in the LMS. You can grade your learners' work using JavaScript and some basic Python, and the grading is integrated into the edX grading system.

@@ -31,42 +31,47 @@ data: |

When you add the problem, be sure to select Settings to specify a Display Name and other values that apply. + Also, be sure to specify a title attribute on the jsinput tag; + this title is used for the title attribute on the generated IFrame. Generally, + the title attribute on the IFrame should match the title tag of the HTML hosted + within the IFrame, which is specified by the html_file attribute.

You can use the following example problem as a model.

- + -

In the following image, click the objects until the cone is yellow and the cube is blue.

- This is paragraph text displayed before the IFrame.

+
diff --git a/common/static/js/capa/jsinput/jsinput_example.css b/common/static/js/capa/jsinput/jsinput_example.css new file mode 100644 index 0000000000..8f1144ac40 --- /dev/null +++ b/common/static/js/capa/jsinput/jsinput_example.css @@ -0,0 +1,9 @@ +.directions { + font-size: large +} + +.feedback { + font-size: medium; + border: 2px solid cornflowerblue; + padding: 5px; +} diff --git a/common/static/js/capa/jsinput/jsinput_example.html b/common/static/js/capa/jsinput/jsinput_example.html new file mode 100644 index 0000000000..caa034fff9 --- /dev/null +++ b/common/static/js/capa/jsinput/jsinput_example.html @@ -0,0 +1,15 @@ + + + + Dropdown with Dynamic Text + + + + + + + + + diff --git a/common/static/js/capa/jsinput/jsinput_example.js b/common/static/js/capa/jsinput/jsinput_example.js new file mode 100644 index 0000000000..6aa125fd69 --- /dev/null +++ b/common/static/js/capa/jsinput/jsinput_example.js @@ -0,0 +1,86 @@ +/* globals Channel */ + +(function() { + 'use strict'; + + // state will be populated via initial_state via the `setState` method. Defining dummy values here + // to make the expected structure clear. + var state = { + availableChoices: [], + selectedChoice: '' + }, + channel, + select = document.getElementsByClassName('choices')[0], + feedback = document.getElementsByClassName('feedback')[0]; + + function populateSelect() { + // Populate the select from `state.availableChoices`. + var i, option; + + // Clear out any pre-existing options. + while (select.firstChild) { + select.removeChild(select.firstChild); + } + + // Populate the select with the available choices. + for (i = 0; i < state.availableChoices.length; i++) { + option = document.createElement('option'); + option.value = i; + option.innerHTML = state.availableChoices[i]; + if (state.availableChoices[i] === state.selectedChoice) { + option.selected = true; + } + select.appendChild(option); + } + feedback.innerText = "The currently selected answer is '" + state.selectedChoice + "'."; + } + + function getGrade() { + // The following return value may or may not be used to grade server-side. + // If getState and setState are used, then the Python grader also gets access + // to the return value of getState and can choose it instead to grade. + return JSON.stringify(state.selectedChoice); + } + + function getState() { + // Returns the current state (which can be used for grading). + return JSON.stringify(state); + } + + // This function will be called with 1 argument when JSChannel is not used, + // 2 otherwise. In the latter case, the first argument is a transaction + // object that will not be used here + // (see http://mozilla.github.io/jschannel/docs/) + function setState() { + var stateString = arguments.length === 1 ? arguments[0] : arguments[1]; + state = JSON.parse(stateString); + populateSelect(); + } + + // Establish a channel only if this application is embedded in an iframe. + // This will let the parent window communicate with this application using + // RPC and bypass SOP restrictions. + if (window.parent !== window) { + channel = Channel.build({ + window: window.parent, + origin: '*', + scope: 'JSInput' + }); + + channel.bind('getGrade', getGrade); + channel.bind('getState', getState); + channel.bind('setState', setState); + } + + select.addEventListener('change', function() { + state.selectedChoice = select.options[select.selectedIndex].text; + feedback.innerText = "You have selected '" + state.selectedChoice + + "'. Click Submit to grade your answer."; + }); + + return { + getState: getState, + setState: setState, + getGrade: getGrade + }; +}());