Skip to content

Commit

Permalink
Only warn about interactive HTML nodes; document HTML interactivity.
Browse files Browse the repository at this point in the history
  • Loading branch information
christianp committed Nov 8, 2024
1 parent bf2a0f3 commit fef503b
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 5 deletions.
6 changes: 6 additions & 0 deletions docs/jme-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,12 @@ Some extensions add new data types.

See functions related to :ref:`jme-fns-html`.

.. warning::

Interactive HTML nodes can not be safely copied, so each HTML value should only be used once in a question.
You can mark an HTML node as non-interactive by adding the attribute ``data-interactive="false"`` to it.
Elements created using the built-in HTML functions are automatically marked as non-interactive.

.. data:: expression

A JME sub-expression.
Expand Down
20 changes: 19 additions & 1 deletion editor/static/js/numbas/numbas-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -3564,6 +3564,9 @@ var TBool = types.TBool = function(b) {
jme.registerType(TBool,'boolean');

/** HTML DOM element.
*
* If the element has the attribute `data-interactive="false"` then it can be safely copied and embedded multiple times.
* If the attribute is not present or has any other value, then it's assumed that it can't be safely copied.
*
* @memberof Numbas.jme.types
* @augments Numbas.jme.token
Expand All @@ -3589,6 +3592,11 @@ var THTML = types.THTML = function(html) {
this.value = Array.from(elem.childNodes);
this.html = elem.innerHTML;
}
THTML.prototype = {
isInteractive: function() {
return this.value.some(e => e.getAttribute('data-interactive') !== 'false');
}
}
jme.registerType(THTML,'html');

/** List of elements of any data type.
Expand Down Expand Up @@ -6314,7 +6322,9 @@ newBuiltin('html',[TString],THTML,null, {
container.innerHTML = args[0].value;
var subber = new jme.variables.DOMcontentsubber(scope);
subber.subvars(container);
return new THTML(Array.from(container.childNodes));
var nodes = Array.from(container.childNodes);
nodes.forEach(node => node.setAttribute('data-interactive', 'false'));
return new THTML(nodes);
}
});
newBuiltin('isnonemptyhtml',[TString],TBool,function(html) {
Expand All @@ -6335,6 +6345,7 @@ newBuiltin('image',[TString, '[number]', '[number]'],THTML,null, {
}
var subber = new jme.variables.DOMcontentsubber(scope);
var element = subber.subvars(img);
element.setAttribute('data-interactive', 'false');
return new THTML(element);
}
});
Expand Down Expand Up @@ -6810,6 +6821,7 @@ newBuiltin('scientificnumberhtml', [TDecimal], THTML, function(n) {
var bits = math.parseScientific(n.re.toExponential());
var s = document.createElement('span');
s.innerHTML = math.niceRealNumber(bits.significand)+' × 10<sup>'+bits.exponent+'</sup>';
s.setAttribute('data-interactive', 'false');
return s;
});
newBuiltin('scientificnumberhtml', [TNum], THTML, function(n) {
Expand All @@ -6819,6 +6831,7 @@ newBuiltin('scientificnumberhtml', [TNum], THTML, function(n) {
var bits = math.parseScientific(math.niceRealNumber(n,{style:'scientific', scientificStyle:'plain'}));
var s = document.createElement('span');
s.innerHTML = math.niceRealNumber(bits.significand)+' × 10<sup>'+bits.exponent+'</sup>';
s.setAttribute('data-interactive', 'false');
return s;
});

Expand Down Expand Up @@ -8378,6 +8391,7 @@ newBuiltin('table',[TList,TList],THTML, null, {
row.appendChild(td);
}
}
table.setAttribute('data-interactive','false');
return new THTML(table);
}
});
Expand All @@ -8396,6 +8410,7 @@ newBuiltin('table',[TList],THTML, null, {
row.appendChild(td);
}
}
table.setAttribute('data-interactive','false');
return new THTML(table);
}
});
Expand Down Expand Up @@ -14110,6 +14125,9 @@ jme.variables = /** @lends Numbas.jme.variables */ {
function doToken(token) {
if(jme.isType(token,'html')) {
token = jme.castToType(token,'html');
if(!token.isInteractive()) {
return token.value.map(e => e.cloneNode(true));
}
if(token.value.numbas_embedded) {
throw(new Numbas.Error('jme.subvars.html inserted twice'))
}
Expand Down
4 changes: 2 additions & 2 deletions editor/static/js/question/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2431,12 +2431,12 @@ $(document).ready(function() {
return val.type;
},this);

this.isHTML = ko.pureComputed(function() {
this.isInteractiveHTML = ko.pureComputed(function() {
var val = this.value();
if(!val || this.error()) {
return false;
}
return Numbas.jme.isType(val,'html');
return Numbas.jme.isType(val,'html') && val.isInteractive();
},this);

this.thisLocked = ko.observable(false);
Expand Down
4 changes: 2 additions & 2 deletions editor/templates/question/tabs/variables.html
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ <h5>Suggestions</h5>
</li>
</ul>
</div>
<div class="html-type-warning value-error alert alert-danger" data-bind="visible: isHTML">
<p>This variable is an HTML node. HTML nodes can not be relied upon to work correctly when resuming a session - for example, attached event callbacks will be lost, and mathematical notation will likely also break.</p>
<div class="html-type-warning value-error alert alert-danger" data-bind="visible: isInteractiveHTML">
<p>This variable is an interactive HTML node. Interactive HTML nodes can not be relied upon to work correctly when resuming a session - for example, attached event callbacks will be lost, and mathematical notation will likely also break.</p>
<p>If this causes problems, try to create HTML nodes where you use them in content areas, instead of storing them in variables.</p>
</div>
</div>
Expand Down

0 comments on commit fef503b

Please sign in to comment.