带有两个板的 Moodle 公式中的 JSXGraph:绑定到输入字段不起作用

JSXGraph in Moodle Formulas with two boards: Binding to input fields not working

我用两个板创建了运动学领域的 Moodle 公式问题。虽然我只用一块板就可以完美地解决更简单的问题,但这个问题的问题是绑定值没有插入到公式的输入输入字段中。因此,学生无法提交答案,因为实际上没有填写任何内容。问题的其余部分仍然有效,正如在问题预览中填写正确答案时所看到的那样。

我提供了一个 Moodle XML 文件,以便更容易重现问题:questions_formulas_JSXGraph_2boards.xml
您需要安装 JSXGraph 过滤器和问题类型 Formulas 的当前版本的 Moodle。

主要的 JSXGraph 代码是这样的:

<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>

有没有可能是板子id没有交好导致的问题

new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);

除了这个问题,我想更好地理解 JSXGraph:

  1. 是否有可能以某种方式相互安排多个板?即上方、下方、右对齐、居中等
  2. 将板初始化为“const”或“var”是否有区别?
  3. 是否necessary/beneficial/wrong在上面的例子中暂停和取消暂停板更新?
  4. 代码中有手动更新命令吗necessary/beneficial/useless?
  5. 我在编码或使用 JSXGraph 时是否有任何明显的错误?

事实上,我们的过滤器与公式结合使用时无法在多个板上正常工作是正确的。目前,只有一个板 ID 被传输到 JSXQuestion object,因此它(和公式)对第二个板一无所知。这也是您的示例提出的问题之一。

此外,为了使 bindInput() 方法起作用,实际上必须使用 JSXQuestion.initBoard() 方法对电路板进行初始化。最后,这就是您的示例不起作用的根本问题。

圣诞假期后我将致力于此问题,并将在 1 月份发布新版本的 Moodle 过滤器。也许那时 JSXGraph 也会有一些新东西。

很遗憾,到那时我无法为您提供肮脏的技巧,因为它需要对过滤器进行一些基本更改。

我希望能在一月份告诉你更多。 圣诞快乐,身体健康!

安德烈亚斯

我现在有时间查看您的问题,并且能够扩展 Moodle 过滤器。从新版本v1.1.0-for3.10开始,公式中也支持了几个板子。您可以在此处找到有关如何使用它以及注意事项的详细说明 on GitHub

新版插件可以在Plugins Directory下载。

我冒昧地修改了上面的示例,它对我有用:

<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>

我已经回答了代码中的其他问题。

希望能帮到你!

你好,安德烈亚斯

为了完整起见,我 post 我的最终版本的 JSXGraph 代码用于基于 Andreas 解决方案的公式问题。我最后的润色是

  1. 使轴标签使用 LaTeX,
  2. 使用事件处理程序 .on('drag', ...) 在图表之间进行双向更新,而不是使用 question.addChildsAsc()

这是最终代码:

<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;
  
  // 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};


  // Create boards
  var brds = question.initBoards( [
  { // attribs for BOARDID0 
    boundingbox: [-1, 12, 13, -11], axis:true,
    defaultAxes: {
      x: {withLabel: true, name: '$$t\;\mathrm{(s)}$$',
          label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
      y: {withLabel:true, name: '$$x\;\mathrm{(m)}$$',
          label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
      zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
      showCopyright: false, showNavigation: false 
    },
    { // attribs for BOARDID1 
    boundingbox: [-1, 3.8, 13, -3.5], axis:true,
    defaultAxes: {
      x: {withLabel: true, name: '$$t\;\mathrm{(s)}$$',
          label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
      y: {withLabel:true, name: '$$v_x\;\mathrm{(m/s)}$$',
          label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
      zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
      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();
  

  // Define lines and points on brd1
  var pV1 = brd1.create('point',  [t1, v1], attrPmov({name: "pV1"}) ),
      pV2 = brd1.create('point',  [t2, v2], attrPmov({name: "pV2"}) ),
      pV3 = brd1.create('point',  [t3, v3], attrPmov({name: "pV3"}) ),
      pV01 = brd1.create('point', [0, "Y(pV1)"], attrPsma() ),
      pV12 = brd1.create('point', ["X(pV1)", "Y(pV2)"], attrPsma() ),
      pV23 = brd1.create('point', ["X(pV2)", "Y(pV3)"], attrPsma() )    ;
      pV34 = brd1.create('point', ["X(pV3)", 0], attrPsma() )    ;
  brd1.create('polygonalchain', [ pV01, pV1, pV12, pV2, pV23, pV3, pV34 ], attrLine);
  

  // Define lines and points on brd0
  var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
      pX1 = brd0.create('point', [t1, x1], attrPmov({face: 'diamond'}) ),
      pX2 = brd0.create('point', [t2, x2], attrPmov({face: 'diamond'}) ),
      pX3 = brd0.create('point', [t3, x3], attrPmov({face: 'diamond'}) );
  brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
  

  // Define dependencies
  pV1.on('drag', function() { pX1.moveTo([this.X(), pX1.Y()], 0); });
  pV2.on('drag', function() { pX2.moveTo([this.X(), pX2.Y()], 0); });
  pV3.on('drag', function() { pX3.moveTo([this.X(), pX3.Y()], 0); });
  pX1.on('drag', function() { pV1.moveTo([this.X(), pV1.Y()], 0); });
  pX2.on('drag', function() { pV2.moveTo([this.X(), pV2.Y()], 0); });
  pX3.on('drag', function() { pV3.moveTo([this.X(), pV3.Y()], 0); });
  

  // 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(BOARDIDS, jsxCode, allowInputEntry=false); // use BOARDIDS here!!
  
</jsxgraph>