Commit 0dce7efd by Sarina Canelake

Merge pull request #10850 from edx/kill-graphical-slider-tool

Remove the graphical slider tool
parents 283623ab 2bc7b954
......@@ -1374,15 +1374,6 @@ class TestComponentTemplates(CourseTestCase):
self.assertNotEqual(only_template.get('category'), 'video')
self.assertNotEqual(only_template.get('category'), 'openassessment')
def test_advanced_components_without_display_name(self):
"""
Test that advanced components without display names display their category instead.
"""
self.course.advanced_modules.append('graphical_slider_tool')
self.templates = get_component_templates(self.course)
template = self.get_templates_of_type('advanced')[0]
self.assertEqual(template.get('display_name'), 'graphical_slider_tool')
def test_advanced_problems(self):
"""
Test the handling of advanced problem templates.
......
......@@ -997,7 +997,6 @@ ADVANCED_COMPONENT_TYPES = [
'videoannotation', # module for annotating video (with annotation table)
'imageannotation', # module for annotating image (with annotation table)
'word_cloud',
'graphical_slider_tool',
'lti',
'lti_consumer',
'library_content',
......@@ -1115,7 +1114,11 @@ CREDIT_PROVIDER_TIMESTAMP_EXPIRATION = 15 * 60
################################ Deprecated Blocks Info ################################
DEPRECATED_BLOCK_TYPES = ['peergrading', 'combinedopenended']
DEPRECATED_BLOCK_TYPES = [
'peergrading',
'combinedopenended',
'graphical_slider_tool',
]
#### PROCTORING CONFIGURATION DEFAULTS
......
......@@ -28,7 +28,6 @@ XMODULES = [
"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",
"annotatable = xmodule.annotatable_module:AnnotatableDescriptor",
"textannotation = xmodule.textannotation_module:TextAnnotationDescriptor",
"videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor",
......
// In the LMS sliders use built-in styles from jquery-ui-1.8.22.custom.css.
// CMS uses its own sliders styles.
// These styles we use only to sure, that slider in GST module
// will be render correctly (just like a duplication some from jquery-ui-1.8.22.custom.css).
// Cause, for example, CMS overwrites many jquery-ui-1.8.22.custom.css styles,
// and we must overwrite them again.
.ui-widget-content {
border: 1px solid #dddddd;
color: #333333;
}
.ui-widget {
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
font-size: 1.1em;
}
.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl {
-moz-border-radius-topleft: 4px;
-webkit-border-top-left-radius: 4px;
-khtml-border-top-left-radius: 4px;
border-top-left-radius: 4px;
}
.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr {
-moz-border-radius-topright: 4px;
-webkit-border-top-right-radius: 4px;
-khtml-border-top-right-radius: 4px;
border-top-right-radius: 4px;
}
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl {
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
-khtml-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br {
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
-khtml-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
}
\ No newline at end of file
"""
Graphical slider tool module is ungraded xmodule used by students to
understand functional dependencies.
"""
import json
import logging
from lxml import etree
from lxml import html
import xmltodict
from xmodule.editing_module import XMLEditingDescriptor
from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule
from xmodule.stringify import stringify_children
from pkg_resources import resource_string
from xblock.fields import String, Scope
log = logging.getLogger(__name__)
DEFAULT_RENDER = """
<h2>Graphic slider tool: Dynamic range and implicit functions.</h2>
<p>You can make the range of the x axis (but not ticks of x axis) of
functions depend on a parameter value. This can be useful when the
function domain needs to be variable.</p>
<p>Implicit functions like a circle can be plotted as 2 separate
functions of the same color.</p>
<div style="height:50px;">
<slider var='r' style="width:400px;float:left;"/>
<textbox var='r' style="float:left;width:60px;margin-left:15px;"/>
</div>
<plot style="margin-top:15px;margin-bottom:15px;"/>
"""
DEFAULT_CONFIGURATION = """
<parameters>
<param var="r" min="5" max="25" step="0.5" initial="12.5" />
</parameters>
<functions>
<function color="red">Math.sqrt(r * r - x * x)</function>
<function color="red">-Math.sqrt(r * r - x * x)</function>
<function color="red">Math.sqrt(r * r / 20 - Math.pow(x-r/2.5, 2)) + r/8</function>
<function color="red">-Math.sqrt(r * r / 20 - Math.pow(x-r/2.5, 2)) + r/5.5</function>
<function color="red">Math.sqrt(r * r / 20 - Math.pow(x+r/2.5, 2)) + r/8</function>
<function color="red">-Math.sqrt(r * r / 20 - Math.pow(x+r/2.5, 2)) + r/5.5</function>
<function color="red">-Math.sqrt(r * r / 5 - x * x) - r/5.5</function>
</functions>
<plot>
<xrange>
<!-- dynamic range -->
<min>-r</min>
<max>r</max>
</xrange>
<num_points>1000</num_points>
<xticks>-30, 6, 30</xticks>
<yticks>-30, 6, 30</yticks>
</plot>
"""
class GraphicalSliderToolFields(object):
data = String(
help="Html contents to display for this module",
default='<render>{}</render><configuration>{}</configuration>'.format(
DEFAULT_RENDER, DEFAULT_CONFIGURATION),
scope=Scope.content
)
class GraphicalSliderToolModule(GraphicalSliderToolFields, XModule):
''' Graphical-Slider-Tool Module
'''
js = {
'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee')],
'js': [
# 3rd party libraries used by graphic slider tool.
# TODO - where to store them - outside xmodule?
resource_string(__name__, 'js/src/graphical_slider_tool/gst_main.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/state.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/general_methods.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/sliders.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/inputs.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/graph.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/el_output.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/g_label_el_output.js'),
resource_string(__name__, 'js/src/graphical_slider_tool/gst.js')
]
}
css = {'scss': [resource_string(__name__, 'css/gst/display.scss')]}
js_module_name = "GraphicalSliderTool"
@property
def configuration(self):
return stringify_children(
html.fromstring(self.data).xpath('configuration')[0]
)
@property
def render(self):
return stringify_children(
html.fromstring(self.data).xpath('render')[0]
)
def get_html(self):
""" Renders parameters to template. """
# these 3 will be used in class methods
self.html_id = self.location.html_id()
self.html_class = self.location.category
self.configuration_json = self.build_configuration_json()
params = {
'gst_html': self.substitute_controls(self.render),
'element_id': self.html_id,
'element_class': self.html_class,
'configuration_json': self.configuration_json
}
content = self.system.render_template(
'graphical_slider_tool.html', params
)
return content
def substitute_controls(self, html_string):
""" Substitutes control elements (slider, textbox and plot) in
html_string with their divs. Html_string is content of <render> tag
inside <graphical_slider_tool> tag. Documentation on how information in
<render> tag is organized and processed is located in:
edx-platform/docs/build/html/graphical_slider_tool.html.
Args:
html_string: content of <render> tag, with controls as xml tags,
e.g. <slider var="a"/>.
Returns:
html_string with control tags replaced by proper divs
(<slider var="a"/> -> <div class="....slider" > </div>)
"""
xml = html.fromstring(html_string)
# substitute plot, if presented
plot_div = '<div class="{element_class}_plot" id="{element_id}_plot" \
style="{style}"></div>'
plot_el = xml.xpath('//plot')
if plot_el:
plot_el = plot_el[0]
plot_el.getparent().replace(plot_el, html.fromstring(
plot_div.format(
element_class=self.html_class,
element_id=self.html_id,
style=plot_el.get('style', ""))))
# substitute sliders
slider_div = '<div class="{element_class}_slider" \
id="{element_id}_slider_{var}" \
data-var="{var}" \
style="{style}">\
</div>'
slider_els = xml.xpath('//slider')
for slider_el in slider_els:
slider_el.getparent().replace(slider_el, html.fromstring(
slider_div.format(
element_class=self.html_class,
element_id=self.html_id,
var=slider_el.get('var', ""),
style=slider_el.get('style', ""))))
# substitute inputs aka textboxes
input_div = '<input class="{element_class}_input" \
id="{element_id}_input_{var}_{input_index}" \
data-var="{var}" style="{style}"/>'
input_els = xml.xpath('//textbox')
for input_index, input_el in enumerate(input_els):
input_el.getparent().replace(input_el, html.fromstring(
input_div.format(
element_class=self.html_class,
element_id=self.html_id,
var=input_el.get('var', ""),
style=input_el.get('style', ""),
input_index=input_index)))
return html.tostring(xml)
def build_configuration_json(self):
"""Creates json element from xml element (with aim to transfer later
directly to javascript via hidden field in template). Steps:
1. Convert xml tree to python dict.
2. Dump dict to json.
"""
# <root> added for interface compatibility with xmltodict.parse
# class added for javascript's part purposes
root = '<root class="{}">{}</root>'.format(
self.html_class,
self.configuration)
return json.dumps(xmltodict.parse(root))
class GraphicalSliderToolDescriptor(GraphicalSliderToolFields, XMLEditingDescriptor, XmlDescriptor):
module_class = GraphicalSliderToolModule
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
Pull out the data into dictionary.
Args:
xml_object: xml from file.
Returns:
dict
"""
# check for presense of required tags in xml
expected_children_level_0 = ['render', 'configuration']
for child in expected_children_level_0:
if len(xml_object.xpath(child)) != 1:
raise ValueError(u"Graphical Slider Tool definition must include \
exactly one '{0}' tag".format(child))
expected_children_level_1 = ['functions']
for child in expected_children_level_1:
if len(xml_object.xpath('configuration')[0].xpath(child)) != 1:
raise ValueError(u"Graphical Slider Tool definition must include \
exactly one '{0}' tag".format(child))
# finished
return {
'data': stringify_children(xml_object)
}, []
def definition_to_xml(self, resource_fs):
'''Return an xml element representing this definition.'''
data = u'<{tag}>{body}</{tag}>'.format(
tag='graphical_slider_tool',
body=self.data)
xml_object = etree.fromstring(data)
return xml_object
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('ElOutput', [], function () {
return ElOutput;
function ElOutput(config, state) {
if ($.isPlainObject(config.functions["function"])) {
processFuncObj(config.functions["function"]);
} else if ($.isArray(config.functions["function"])) {
(function (c1) {
while (c1 < config.functions["function"].length) {
if ($.isPlainObject(config.functions["function"][c1])) {
processFuncObj(config.functions["function"][c1]);
}
c1 += 1;
}
}(0));
}
return;
function processFuncObj(obj) {
var paramNames, funcString, func, el, disableAutoReturn, updateOnEvent;
// We are only interested in functions that are meant for output to an
// element.
if (
(typeof obj['@output'] !== 'string') ||
((obj['@output'].toLowerCase() !== 'element') && (obj['@output'].toLowerCase() !== 'none'))
) {
return;
}
if (typeof obj['@el_id'] !== 'string') {
console.log('ERROR: You specified "output" as "element", but did not spify "el_id".');
return;
}
if (typeof obj['#text'] !== 'string') {
console.log('ERROR: Function body is not defined.');
return;
}
updateOnEvent = 'slide';
if (
(obj.hasOwnProperty('@update_on') === true) &&
(typeof obj['@update_on'] === 'string') &&
((obj['@update_on'].toLowerCase() === 'slide') || (obj['@update_on'].toLowerCase() === 'change'))
) {
updateOnEvent = obj['@update_on'].toLowerCase();
}
disableAutoReturn = obj['@disable_auto_return'];
funcString = obj['#text'];
if (
(disableAutoReturn === undefined) ||
(
(typeof disableAutoReturn === 'string') &&
(disableAutoReturn.toLowerCase() !== 'true')
)
) {
if (funcString.search(/return/i) === -1) {
funcString = 'return ' + funcString;
}
} else {
if (funcString.search(/return/i) === -1) {
console.log(
'ERROR: You have specified a JavaScript ' +
'function without a "return" statemnt. Your ' +
'function will return "undefined" by default.'
);
}
}
// Make sure that all HTML entities are converted to their proper
// ASCII text equivalents.
funcString = $('<div>').html(funcString).text();
paramNames = state.getAllParameterNames();
paramNames.push(funcString);
try {
func = Function.apply(null, paramNames);
} catch (err) {
console.log(
'ERROR: The function body "' +
funcString +
'" was not converted by the Function constructor.'
);
console.log('Error message: "' + err.message + '".');
if (state.showDebugInfo) {
$('#' + gstId).html('<div style="color: red;">' + 'ERROR IN XML: Could not create a function from string "' + funcString + '".' + '</div>');
$('#' + gstId).append('<div style="color: red;">' + 'Error message: "' + err.message + '".' + '</div>');
}
paramNames.pop();
return;
}
paramNames.pop();
if (obj['@output'].toLowerCase() !== 'none') {
el = $('#' + obj['@el_id']);
if (el.length !== 1) {
console.log(
'ERROR: DOM element with ID "' + obj['@el_id'] + '" ' +
'not found. Dynamic element not created.'
);
return;
}
el.html(func.apply(window, state.getAllParameterValues()));
} else {
el = null;
func.apply(window, state.getAllParameterValues());
}
state.addDynamicEl(el, func, obj['@el_id'], updateOnEvent);
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('GLabelElOutput', [], function () {
return GLabelElOutput;
function GLabelElOutput(config, state) {
if ($.isPlainObject(config.functions["function"])) {
processFuncObj(config.functions["function"]);
} else if ($.isArray(config.functions["function"])) {
(function (c1) {
while (c1 < config.functions["function"].length) {
if ($.isPlainObject(config.functions["function"][c1])) {
processFuncObj(config.functions["function"][c1]);
}
c1 += 1;
}
}(0));
}
return;
function processFuncObj(obj) {
var paramNames, funcString, func, disableAutoReturn;
// We are only interested in functions that are meant for output to an
// element.
if (
(typeof obj['@output'] !== 'string') ||
(obj['@output'].toLowerCase() !== 'plot_label')
) {
return;
}
if (typeof obj['@el_id'] !== 'string') {
console.log('ERROR: You specified "output" as "plot_label", but did not spify "el_id".');
return;
}
if (typeof obj['#text'] !== 'string') {
console.log('ERROR: Function body is not defined.');
return;
}
disableAutoReturn = obj['@disable_auto_return'];
funcString = obj['#text'];
if (
(disableAutoReturn === undefined) ||
(
(typeof disableAutoReturn === 'string') &&
(disableAutoReturn.toLowerCase() !== 'true')
)
) {
if (funcString.search(/return/i) === -1) {
funcString = 'return ' + funcString;
}
} else {
if (funcString.search(/return/i) === -1) {
console.log(
'ERROR: You have specified a JavaScript ' +
'function without a "return" statemnt. Your ' +
'function will return "undefined" by default.'
);
}
}
// Make sure that all HTML entities are converted to their proper
// ASCII text equivalents.
funcString = $('<div>').html(funcString).text();
paramNames = state.getAllParameterNames();
paramNames.push(funcString);
try {
func = Function.apply(null, paramNames);
} catch (err) {
console.log(
'ERROR: The function body "' +
funcString +
'" was not converted by the Function constructor.'
);
console.log('Error message: "' + err.message + '".');
if (state.showDebugInfo) {
$('#' + gstId).html('<div style="color: red;">' + 'ERROR IN XML: Could not create a function from string "' + funcString + '".' + '</div>');
$('#' + gstId).append('<div style="color: red;">' + 'Error message: "' + err.message + '".' + '</div>');
}
paramNames.pop();
return;
}
paramNames.pop();
state.plde.push({
'elId': obj['@el_id'],
'func': func
});
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('GeneralMethods', [], function () {
if (!String.prototype.trim) {
// http://blog.stevenlevithan.com/archives/faster-trim-javascript
String.prototype.trim = function trim(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};
}
return {
'module_name': 'GeneralMethods',
'module_status': 'OK'
};
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
/*
* We will add a function that will be called for all GraphicalSliderTool
* xmodule module instances. It must be available globally by design of
* xmodule.
*/
window.GraphicalSliderTool = function (el) {
// All the work will be performed by the GstMain module. We will get access
// to it, and all it's dependencies, via Require JS. Currently Require JS
// is namespaced and is available via a global object RequireJS.
RequireJS.require(['GstMain'], function (GstMain) {
// The GstMain module expects the DOM ID of a Graphical Slider Tool
// element. Since we are given a <section> element which might in
// theory contain multiple graphical_slider_tool <div> elements (each
// with a unique DOM ID), we will iterate over all children, and for
// each match, we will call GstMain module.
$(el).children('.graphical_slider_tool').each(function (index, value) {
JavascriptLoader.executeModuleScripts($(value), function(){
GstMain($(value).attr('id'));
});
});
});
};
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define(
'GstMain',
// Even though it is not explicitly in this module, we have to specify
// 'GeneralMethods' as a dependency. It expands some of the core JS objects
// with additional useful methods that are used in other modules.
['State', 'GeneralMethods', 'Sliders', 'Inputs', 'Graph', 'ElOutput', 'GLabelElOutput'],
function (State, GeneralMethods, Sliders, Inputs, Graph, ElOutput, GLabelElOutput) {
return GstMain;
function GstMain(gstId) {
var config, gstClass, state;
if ($('#' + gstId).attr('data-processed') !== 'processed') {
$('#' + gstId).attr('data-processed', 'processed');
} else {
// console.log('MESSAGE: Already processed GST with ID ' + gstId + '. Skipping.');
return;
}
// Get the JSON configuration, parse it, and store as an object.
try {
config = JSON.parse($('#' + gstId + '_json').html()).root;
} catch (err) {
console.log('ERROR: could not parse config JSON.');
console.log('$("#" + gstId + "_json").html() = ', $('#' + gstId + '_json').html());
console.log('JSON.parse(...) = ', JSON.parse($('#' + gstId + '_json').html()));
console.log('config = ', config);
return;
}
// Get the class name of the GST. All elements are assigned a class
// name that is based on the class name of the GST. For example, inputs
// are assigned a class name '{GST class name}_input'.
if (typeof config['@class'] !== 'string') {
console.log('ERROR: Could not get the class name of GST.');
console.log('config["@class"] = ', config['@class']);
return;
}
gstClass = config['@class'];
// Parse the configuration settings for parameters, and store them in a
// state object.
state = State(gstId, config);
state.showDebugInfo = false;
// It is possible that something goes wrong while extracting parameters
// from the JSON config object. In this case, we will not continue.
if (state === undefined) {
console.log('ERROR: The state object was not initialized properly.');
return;
}
// Create the sliders and the text inputs, attaching them to
// appropriate parameters.
Sliders(gstId, state);
Inputs(gstId, gstClass, state);
// Configure functions that output to an element instead of the graph.
ElOutput(config, state);
// Configure functions that output to an element instead of the graph
// label.
GLabelElOutput(config, state);
// Configure and display the graph. Attach event for the graph to be
// updated on any change of a slider or a text input.
Graph(gstId, config, state);
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('Inputs', [], function () {
return Inputs;
function Inputs(gstId, gstClass, state) {
var c1, paramName, allParamNames;
allParamNames = state.getAllParameterNames();
for (c1 = 0; c1 < allParamNames.length; c1 += 1) {
$('#' + gstId).find('.' + gstClass + '_input').each(function (index, value) {
var inputDiv, paramName;
paramName = allParamNames[c1];
inputDiv = $(value);
if (paramName === inputDiv.data('var')) {
createInput(inputDiv, paramName);
}
});
}
return;
function createInput(inputDiv, paramName) {
var paramObj;
paramObj = state.getParamObj(paramName);
// Check that the retrieval went OK.
if (paramObj === undefined) {
console.log('ERROR: Could not get a paramObj for parameter "' + paramName + '".');
return;
}
// Bind a function to the 'change' event. Whenever the user changes
// the value of this text input, and presses 'enter' (or clicks
// somewhere else on the page), this event will be triggered, and
// our callback will be called.
inputDiv.bind('change', inputOnChange);
inputDiv.val(paramObj.value);
// Lets style the input element nicely. We will use the button()
// widget for this since there is no native widget for the text
// input.
inputDiv.button().css({
'font': 'inherit',
'color': 'inherit',
'text-align': 'left',
'outline': 'none',
'cursor': 'text',
'height': '15px'
});
// Tell the parameter object from state that we are attaching a
// text input to it. Next time the parameter will be updated with
// a new value, tis input will also be updated.
paramObj.inputDivs.push(inputDiv);
return;
// Update the 'state' - i.e. set the value of the parameter this
// input is attached to to a new value.
//
// This will cause the plot to be redrawn each time after the user
// changes the value in the input. Note that he has to either press
// 'Enter', or click somewhere else on the page in order for the
// 'change' event to be tiggered.
function inputOnChange(event) {
var inputDiv;
inputDiv = $(this);
state.setParameterValue(paramName, inputDiv.val(), inputDiv);
}
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('Sliders', [], function () {
return Sliders;
function Sliders(gstId, state) {
var c1, paramName, allParamNames, sliderDiv;
allParamNames = state.getAllParameterNames();
for (c1 = 0; c1 < allParamNames.length; c1 += 1) {
paramName = allParamNames[c1];
sliderDiv = $('#' + gstId + '_slider_' + paramName);
if (sliderDiv.length === 1) {
createSlider(sliderDiv, paramName);
} else if (sliderDiv.length > 1) {
console.log('ERROR: Found more than one slider for the parameter "' + paramName + '".');
console.log('sliderDiv.length = ', sliderDiv.length);
} // else {
// console.log('MESSAGE: Did not find a slider for the parameter "' + paramName + '".');
// }
}
function createSlider(sliderDiv, paramName) {
var paramObj;
paramObj = state.getParamObj(paramName);
// Check that the retrieval went OK.
if (paramObj === undefined) {
console.log('ERROR: Could not get a paramObj for parameter "' + paramName + '".');
return;
}
// Create a jQuery UI slider from the slider DIV. We will set
// starting parameters, and will also attach a handler to update
// the 'state' on the 'slide' event.
sliderDiv.slider({
'min': paramObj.min,
'max': paramObj.max,
'value': paramObj.value,
'step': paramObj.step
});
// Tell the parameter object stored in state that we have a slider
// that is attached to it. Next time when the parameter changes, it
// will also update the value of this slider.
paramObj.sliderDiv = sliderDiv;
// Atach callbacks to update the slider's parameter.
paramObj.sliderDiv.on('slide', sliderOnSlide);
paramObj.sliderDiv.on('slidechange', sliderOnChange);
return;
// Update the 'state' - i.e. set the value of the parameter this
// slider is attached to to a new value.
//
// This will cause the plot to be redrawn each time after the user
// drags the slider handle and releases it.
function sliderOnSlide(event, ui) {
// Last parameter passed to setParameterValue() will be 'true'
// so that the function knows we are a slider, and it can
// change the our value back in the case when the new value is
// invalid for some reason.
if (state.setParameterValue(paramName, ui.value, sliderDiv, true, 'slide') === undefined) {
console.log('ERROR: Could not update the parameter named "' + paramName + '" with the value "' + ui.value + '".');
}
}
function sliderOnChange(event, ui) {
if (state.setParameterValue(paramName, ui.value, sliderDiv, true, 'change') === undefined) {
console.log('ERROR: Could not update the parameter named "' + paramName + '" with the value "' + ui.value + '".');
}
}
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
......@@ -82,7 +82,6 @@ class RoundTripTestCase(unittest.TestCase):
"conditional_and_poll",
"conditional",
"self_assessment",
"graphic_slider_tool",
"test_exam_registration",
"word_cloud",
"pure_xblock",
......
......@@ -614,22 +614,6 @@ class ImportTestCase(BaseCourseTestCase):
self.assertRaises(etree.XMLSyntaxError, system.process_xml, bad_xml)
def test_graphicslidertool_import(self):
'''
Check to see if definition_from_xml in gst_module.py
works properly. Pulls data from the graphic_slider_tool directory
in the test data directory.
'''
modulestore = XMLModuleStore(DATA_DIR, source_dirs=['graphic_slider_tool'])
sa_id = SlashSeparatedCourseKey("edX", "gst_test", "2012_Fall")
location = sa_id.make_usage_key("graphical_slider_tool", "sample_gst")
gst_sample = modulestore.get_item(location)
render_string_from_sample_gst_xml = """
<slider var="a" style="width:400px;float:left;"/>\
<plot style="margin-top:15px;margin-bottom:15px;"/>""".strip()
self.assertIn(render_string_from_sample_gst_xml, gst_sample.data)
def test_word_cloud_import(self):
modulestore = XMLModuleStore(DATA_DIR, source_dirs=['word_cloud'])
......
......@@ -32,7 +32,6 @@ from xmodule.annotatable_module import AnnotatableDescriptor
from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor
from xmodule.discussion_module import DiscussionDescriptor
from xmodule.gst_module import GraphicalSliderToolDescriptor
from xmodule.html_module import HtmlDescriptor
from xmodule.poll_module import PollDescriptor
from xmodule.word_cloud_module import WordCloudDescriptor
......@@ -53,7 +52,6 @@ LEAF_XMODULES = {
AnnotatableDescriptor: [{}],
CapaDescriptor: [{}],
DiscussionDescriptor: [{}],
GraphicalSliderToolDescriptor: [{}],
HtmlDescriptor: [{}],
PollDescriptor: [{'display_name': 'Poll Display Name'}],
WordCloudDescriptor: [{}],
......@@ -75,10 +73,9 @@ CONTAINER_XMODULES = {
WrapperBlock: [{}],
}
# These modules are editable in studio yet
# These modules are not editable in studio yet
NOT_STUDIO_EDITABLE = (
CrowdsourceHinterDescriptor,
GraphicalSliderToolDescriptor,
PollDescriptor
)
......
This is a very very simple course, useful for debugging graphical slider tool
code.
roots/2012_Fall.xml
\ No newline at end of file
<course>
<chapter url_name="Overview">
<graphical_slider_tool url_name="sample_gst"/>
</chapter>
</course>
<graphical_slider_tool>
<render>
<slider var='a' style="width:400px;float:left;"/><plot style="margin-top:15px;margin-bottom:15px;"/>
</render>
<configuration>
<parameters>
<param var="a" min="5" max="25" step="0.5" initial="12.5" />
</parameters>
<functions>
<function color="red">return Math.sqrt(a * a - x * x);</function>
<function color="red">return -Math.sqrt(a * a - x * x);</function>
</functions>
<plot>
<xrange>
<!-- dynamic range -->
<min>
return -a;
</min>
<max>
return a;
</max>
</xrange>
<num_points>1000</num_points>
<xticks>-30, 6, 30</xticks>
<yticks>-30, 6, 30</yticks>
</plot>
</configuration>
</graphical_slider_tool>
{
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
"display_name": "GST Test",
"graded": "false"
},
"chapter/Overview": {
"display_name": "Overview"
},
"graphical_slider_tool/sample_gst": {
"display_name": "Sample GST"
}
}
<course org="edX" course="gst_test" url_name="2012_Fall"/>
@shard_2
Feature: LMS.Graphical Slider Tool Module
As a student, I want to view a Graphical Slider Tool Component
Scenario: The slider changes values on the page
Given that I have a course with a Graphical Slider Tool
When I view the Graphical Slider Tool
Then the displayed value should be 0
And I move the slider to the right
Then the displayed value should be 10
\ No newline at end of file
from lettuce import world, steps
from nose.tools import assert_equals
from common import i_am_registered_for_the_course, visit_scenario_item
DEFAULT_DATA = """\
<render>
<p>Test of the graphical slider tool</p>
<div class='gst-value'>
<span id="value-display" style="width:50px; float:left; margin-left:10px;"/>
</div>
<div class='gst-input'>
<slider var="a" style="width:400px;float:left;margin-left:10px;"/>
</div>
</render>
<configuration>
<parameters>
<param var="a" min="0" max="10" step="1" initial="0"/>
</parameters>
<functions>
<function output="element" el_id="value-display">a</function>
</functions>
</configuration>
"""
@steps
class GraphicalSliderToolSteps(object):
COURSE_NUM = 'test_course'
def setup_gst(self, step):
r'that I have a course with a Graphical Slider Tool$'
i_am_registered_for_the_course(step, self.COURSE_NUM)
world.scenario_dict['GST'] = world.ItemFactory(
parent_location=world.scenario_dict['SECTION'].location,
category='graphical_slider_tool',
display_name="Test GST",
data=DEFAULT_DATA
)
def view_gst(self, step):
r'I view the Graphical Slider Tool$'
visit_scenario_item('GST')
world.wait_for_js_variable_truthy('$(".xblock-student_view[data-type=GraphicalSliderTool]").data("initialized")')
world.wait_for_ajax_complete()
def check_value(self, step, value):
r'the displayed value should be (?P<value>\d+)$'
assert_equals(world.css_text('.gst-value'), value)
def move_slider(self, step):
r'I move the slider to the right$'
handle_selector = '.gst-input .ui-slider-handle'
world.wait_for_visible(handle_selector)
world.wait_for_visible('.gst-value #value-display')
def try_move():
handle = world.css_find(handle_selector).first
slider = world.css_find('.gst-input .ui-slider').first
(handle.action_chains
.click_and_hold(handle._element)
.move_by_offset(
int(handle._element.location['x'] + 400),
0
).release().perform())
world.retry_on_exception(try_move)
GraphicalSliderToolSteps()
<div align="center" id="${element_id}" class="${element_class}">
<!-- hidden field to read configuration json from -->
<div class="${element_class}" id="${element_id}_json" style="display: none;">
${configuration_json}
</div>
<!-- main xml with marked places for sliders, numbers, and plot -->
${gst_html}
</div>
......@@ -6,7 +6,7 @@ from django.template.defaultfilters import escapejs
## The JS for this is defined in xqa_interface.html
${block_content}
%if location.category in ['problem','video','html','combinedopenended','graphical_slider_tool', 'library_content']:
%if location.category in ['problem','video','html','combinedopenended','library_content']:
% if edit_link:
<div>
<a href="${edit_link}">Edit</a>
......
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