I have created a Moodle Formulas questions in the field of kinematics with two boards. While I managed to get simpler questions with only one board to work flawlessly, the problem with this question is that the bound values are not inserted into formula's input entry fields. Consequently, the student cannot submit an answer because, effectively, nothing has been filled out. The rest of the question works though, as can be seen when the correct answers are filled in the question's preview.
I provide a Moodle XML file to make it easier to reproduce the problem: questions_formulas_JSXGraph_2boards.xml
You need a current version of Moodle with JSXGraph
filter and question type Formulas
installed.
The main JSXGraph code is this:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brd0 = JXG.JSXGraph.initBoard(BOARDID0, {
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
});
var brd1 = JXG.JSXGraph.initBoard(BOARDID1, {
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
});
// Board brd0 needs to be updated when changes in brd1 occur
brd1.addChild(brd0);
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
brd0.update();
brd1.update();
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return PV2.X(); });
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return PV3.Y(); });
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
</jsxgraph>
Is it possible that the problem is caused because the board ids are not properly handed over in
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
Besides this problem, I would like to understand JSXGraph a bit better:
I now had the time to look at your problem and I was able to expand the Moodle filter. As of the new version v1.1.0-for3.10, several boards are also supported in formulas. You can find detailed instructions on how to use it and what to consider here on GitHub.
The new version of the plugin can be downloaded in the Plugins Directory.
I took the liberty of modifying your example from above and it works for me:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brds = question.initBoards( [
{ // attribs for BOARDID0
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
},
{ // attribs for BOARDID1
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
}
] );
var brd0 = brds[0];
var brd1 = brds[1];
console.log(brd0, brd1);
// Board brd0 needs to be updated when changes in brd1 occur
question.addChildsAsc();
/* not needed anymore
brd1.addChild(brd0);
*/
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
// A: It can be beneficial if you use a lot of objects. In this case the benefit is not worth mentioning, I think.
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
/* not with the new version
brd0.update();
brd1.update();
*/
/* not necessary anymore
question.board = brd0;
*/
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return pV2.X(); }); // typo here
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return pV3.Y(); }); // typo here
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDIDS, jsxCode, allowInputEntry=true); // use BOARDIDS here!!
</jsxgraph>
I've already answered the other questions in the code.
I hope I could help you!
Greetings, Andreas