JSXGraph в формулах Moodle с двумя досками: привязка к полям ввода не работает

#moodle #jsxgraph

#moodle #jsxgraph

Вопрос:

Я создал Moodle Formulas Questions в области кинематики с двумя досками. Хотя мне удалось заставить более простые вопросы только с одной доской работать безупречно, проблема с этим вопросом заключается в том, что связанные значения не вставляются в поля ввода формулы. Следовательно, учащийся не может отправить ответ, потому что, по сути, ничего не было заполнено. Тем не менее, остальная часть вопроса работает, как видно, когда правильные ответы заполнены в предварительном просмотре вопроса.

Я предоставляю XML-файл Moodle, чтобы упростить воспроизведение проблемы: questions_formulas_JSXGraph_2boards.xml
Вам нужна текущая версия Moodle с JSXGraph установленным фильтром и типом вопроса Formulas .

Основной код 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>
 

Возможно ли, что проблема вызвана неправильной передачей идентификаторов платы в

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

Помимо этой проблемы, я хотел бы немного лучше понять JSXGraph:

  1. Возможно ли каким-то образом расположить несколько плат относительно друг друга? То есть выше, ниже, выровнено по правому краю, по центру и т.д.
  2. Имеет ли значение, инициализируются ли платы как ‘const’ или ‘var’?
  3. Необходимо / полезно / неправильно приостанавливать и не приостанавливать обновления платы в приведенном выше примере?
  4. Являются ли команды ручного обновления в коде необходимыми / полезными / бесполезными?
  5. Есть ли какие-либо очевидные ошибки в моем кодировании или использовании JSXGraph?

Ответ №1:

На самом деле, правильно, что наш фильтр в сочетании с формулами некорректно работает с несколькими досками. На данный момент объекту JSXQuestion передается только один идентификатор платы, и, таким образом, и, таким образом, он (и формулы) ничего не знают о второй плате. Это также одна из проблем, возникающих в вашем примере.

Кроме того, платы фактически должны быть инициализированы методом JSXQuestion.initBoard(), чтобы метод bindInput() работал. В конце концов, это основная проблема, почему ваш пример не работает.

Я посвящу себя этой проблеме после рождественских праздников и выпущу новую версию фильтра Moodle в январе. Возможно, к тому времени тоже появится что-то новое из JSXGraph.

К сожалению, до тех пор я не могу предложить вам грязный взлом, поскольку для этого требуется несколько основных изменений в фильтре.

Я надеюсь, что смогу рассказать вам больше в январе. Хорошего Рождества и будьте здоровы!

Андреас

Комментарии:

1. Большое спасибо за ответ! Я с нетерпением ждал, когда этот вопрос заработает. Было бы здорово иметь возможность использовать несколько досок в формулах. Кстати, весь проект потрясающий, и я в восторге от его использования и повышения полезности формирующих и суммирующих действий Moodle для моих студентов. Счастливого Рождества вам и будьте здоровы!

Ответ №2:

Теперь у меня было время рассмотреть вашу проблему, и я смог расширить фильтр Moodle. Начиная с новой версии v1.1.0-for3.10, в формулах также поддерживается несколько плат. Вы можете найти подробные инструкции о том, как его использовать и что учитывать здесь, на GitHub.

Новую версию плагина можно загрузить в каталоге плагинов.

Я взял на себя смелость изменить ваш пример из приведенного выше, и он работает для меня:

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

Я уже ответил на другие вопросы в коде.

Я надеюсь, что смогу вам помочь!

Приветствую, Андреас

Комментарии:

1. P.S. Ошибки предварительной проверки кода будут исправлены в следующей версии фильтра, когда мы выпустим новую версию JSXGraph.

2. Извините, что ответил так поздно… Большое спасибо, это фантастика, что вам удалось так быстро расширить фильтр. Также большое спасибо за прямое применение изменений к моему коду. Еще два вопроса: 1) Можно ли как-то расположить несколько досок относительно друг друга? То есть выше, ниже, выровнено по правому краю, по центру и т.д. 2) Имеет ли значение, инициализируются ли платы как ‘const’ или ‘var’?

3. Привет! 1) Можно управлять размером досок по атрибутам в теге. Например, спецификация width =" 500,200 "height =" 500,200 " гарантирует, что первая доска имеет размер 500x500px, а вторая 200x200px. Если задано меньше значений, чем количество досок, первое значение используется как стандартное. В качестве единиц здесь можно использовать все, что также возможно в css (rem,%, …). По умолчанию все доски окружены классом css .jsxgraph-boards . Установленные здесь стандарты могут быть перезаписаны простым css.

4. Смотрите github.com/jsxgraph /…

5. 2) Посмотрите главу «Постоянные объекты могут изменяться» на w3schools.com/js/js_const.asp

Ответ №3:

Просто для полноты картины я публикую свою окончательную версию кода JSXGraph для вопроса о формулах на основе решения Андреаса. Мои последние штрихи заключались в

  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>
 

Комментарии:

1. Очень приятно! Только один вопрос: можем ли мы в Исследовательском центре мобильного обучения с использованием цифровых медиа продолжать использовать ваш пример для дальнейшего обучения, презентаций и публикаций?

2. Да, с удовольствием. Я очень благодарен за предоставленную вами помощь и был бы рад каким-то образом вернуть. Я думаю, что я также задал несколько других приятных вопросов по формулам. Если вам интересно, просто скажите мне, как лучше всего поделиться ими с вами.

3. Это было бы очень хорошо! Вы можете найти мою контактную информацию почти в каждом файле в фильтре под информацией об авторских правах.