diff --git a/common/lib/xmodule/xmodule/gst_module.py b/common/lib/xmodule/xmodule/gst_module.py index a6b0fb516b..c29e4e6a04 100644 --- a/common/lib/xmodule/xmodule/gst_module.py +++ b/common/lib/xmodule/xmodule/gst_module.py @@ -37,6 +37,7 @@ class GraphicalSliderToolModule(XModule): 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') ] diff --git a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/g_label_el_output.js b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/g_label_el_output.js new file mode 100644 index 0000000000..feac031aee --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/g_label_el_output.js @@ -0,0 +1,91 @@ +// 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', ['logme'], function (logme) { + 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; + + // 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') { + logme('ERROR: You specified "output" as "plot_label", but did not spify "el_id".'); + + return; + } + + if (typeof obj['#text'] !== 'string') { + logme('ERROR: Function body is not defined.'); + + return; + } + + funcString = obj['#text']; + + // Make sure that all HTML entities are converted to their proper + // ASCII text equivalents. + funcString = $('
').html(funcString).text(); + + paramNames = state.getAllParameterNames(); + paramNames.push(funcString); + + try { + func = Function.apply(null, paramNames); + } catch (err) { + logme( + 'ERROR: The function body "' + + funcString + + '" was not converted by the Function constructor.' + ); + logme('Error message: "' + err.message + '".'); + + $('#' + gstId).html('
' + 'ERROR IN XML: Could not create a function from string "' + funcString + '".' + '
'); + $('#' + gstId).append('
' + 'Error message: "' + err.message + '".' + '
'); + + 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) diff --git a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/graph.js b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/graph.js index f35f92aa98..50d8d7bcd4 100644 --- a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/graph.js +++ b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/graph.js @@ -155,7 +155,10 @@ define('Graph', ['logme'], function (logme) { } newAsyObj.label = false; - if (typeof asyObj['@label'] === 'string') { + if ( + (asyObj.hasOwnProperty('@label') === true) && + (typeof asyObj['@label'] === 'string') + ) { newAsyObj.label = asyObj['@label']; } @@ -492,7 +495,10 @@ define('Graph', ['logme'], function (logme) { // properties as parameters. Rather than writing them out every // time, we will have a single place where it is done. function callAddFunction(obj) { - if (typeof obj['@output'] === 'string') { + if ( + (obj.hasOwnProperty('@output')) && + (typeof obj['@output'] === 'string') + ) { // If this function is meant to be calculated for an // element then skip it. @@ -500,13 +506,24 @@ define('Graph', ['logme'], function (logme) { return; } - // It is an error if "output" is not "element" or "graph". - // Though you can ommit the "output" attribute. + // If this function is meant to be calculated for a + // dynamic element in a label then skip it. + else if (obj['@output'].toLowerCase() === 'plot_label') { + return; + } + + // It is an error if '@output' is not 'element', + // 'plot_label', or 'graph'. However, if the '@output' + // attribute is omitted, we will not have reached this. else if (obj['@output'].toLowerCase() !== 'graph') { - logme('ERROR: Function "output" attribute can be either "div" or "graph".'); + logme( + 'ERROR: Function "output" attribute can be ' + + 'either "element", "plot_label", or "graph".' + ); return; } + } // The user did not specify an "output" attribute, or it is @@ -525,8 +542,9 @@ define('Graph', ['logme'], function (logme) { } function addFunction(funcString, color, line, dot, label, - pointSize, fillArea, bar, disableAutoReturn) { - var newFunctionObject, func, paramNames, matches; + pointSize, fillArea, bar, disableAutoReturn) { + + var newFunctionObject, func, paramNames, c1, rgxp; // The main requirement is function string. Without it we can't // create a function, and the series cannot be calculated. @@ -678,18 +696,27 @@ define('Graph', ['logme'], function (logme) { } if (typeof label === 'string') { - matches = label.match(/%%_[^%]*%%/g); - if ( - ($.isArray(matches) === true) && - (matches.length > 0) - ) { - newFunctionObject['specialLabel'] = true; - } else { - newFunctionObject['specialLabel'] = false; + newFunctionObject.specialLabel = false; + newFunctionObject.pldeHash = []; + + // Let's check the label against all of the plde objects. + // plde is an abbreviation for Plot Label Dynamic Elements. + for (c1 = 0; c1 < state.plde.length; c1 += 1) { + rgxp = new RegExp(state.plde[c1].elId, 'g'); + + // If we find a dynamic element in the label, we will + // hash the current plde object, and indicate that this + // is a special label. + if (rgxp.test(label) === true) { + newFunctionObject.specialLabel = true; + newFunctionObject.pldeHash.push(state.plde[c1]); + } } - newFunctionObject['label'] = label; + newFunctionObject.label = label; + } else { + newFunctionObject.label = false; } functions.push(newFunctionObject); @@ -706,7 +733,7 @@ define('Graph', ['logme'], function (logme) { function generateData() { var c0, c1, functionObj, seriesObj, dataPoints, paramValues, x, y, - start, end, step, tempX, matches; + start, end, step, tempX; paramValues = state.getAllParameterValues(); @@ -818,33 +845,27 @@ define('Graph', ['logme'], function (logme) { } // See if a user defined a label for this function. - if (functionObj.hasOwnProperty('label') === true) { + if (functionObj.label !== false) { if (functionObj.specialLabel === true) { - matches = functionObj.label.match(/%%_[^%]*%%/g); + (function (c1) { + var tempLabel; - if ($.isArray(matches) === true) { - (function (c1) { - var el_id, func, tempLabel; + tempLabel = functionObj.label; - tempLabel = functionObj.label; + while (c1 < functionObj.pldeHash.length) { + tempLabel = tempLabel.replace( + functionObj.pldeHash[c1].elId, + functionObj.pldeHash[c1].func.apply( + window, + state.getAllParameterValues() + ) + ); - while (c1 < matches.length) { - el_id = matches[c1].replace(/%/g, ''); - func = state.getFuncForSpecialLabel(el_id); + c1 += 1; + } - if (func !== null) { - tempLabel = tempLabel.replace( - matches[c1], - func.apply(window, state.getAllParameterValues()) - ); - } - - c1 += 1; - } - - seriesObj.label = tempLabel; - }(0)); - } + seriesObj.label = tempLabel; + }(0)); } else { seriesObj.label = functionObj.label; } @@ -880,13 +901,22 @@ define('Graph', ['logme'], function (logme) { } for (c0 = 0; c0 < asymptotes.length; c0 += 1) { - if (typeof asymptotes[c0].label === 'string') { + + // If the user defined a label for this asympote, then the + // property 'label' will be a string (in the other case it is + // a boolean value 'false'). We will create an empty data set, + // and add to it a label. This solution is a bit _wrong_ , but + // it will have to do for now. Flot JS does not provide a way + // to add labels to markings, and we use markings to generate + // asymptotes. + if (asymptotes[c0].label !== false) { dataSeries.push({ 'data': [], 'label': asymptotes[c0].label, 'color': asymptotes[c0].color }); } + } return true; diff --git a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/gst_main.js b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/gst_main.js index 71f946e312..34b54b4216 100644 --- a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/gst_main.js +++ b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/gst_main.js @@ -8,8 +8,8 @@ define( // 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', 'logme'], - function (State, GeneralMethods, Sliders, Inputs, Graph, ElOutput, logme) { + ['State', 'GeneralMethods', 'Sliders', 'Inputs', 'Graph', 'ElOutput', 'GLabelElOutput', 'logme'], + function (State, GeneralMethods, Sliders, Inputs, Graph, ElOutput, GLabelElOutput, logme) { return GstMain; @@ -67,6 +67,10 @@ define( // 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); diff --git a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/state.js b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/state.js index 3aea34ceb5..c1cfcd1b04 100644 --- a/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/state.js +++ b/common/lib/xmodule/xmodule/js/src/graphical_slider_tool/state.js @@ -98,7 +98,10 @@ define('State', ['logme'], function (logme) { 'bindUpdatePlotEvent': bindUpdatePlotEvent, 'addDynamicEl': addDynamicEl, - 'getFuncForSpecialLabel': getFuncForSpecialLabel + 'getFuncForSpecialLabel': getFuncForSpecialLabel, + + // plde is an abbreviation for Plot Label Dynamic Elements. + plde: [] }; function getAllParameterNames() {