I need to alter XForms instance data from inside Javascript. The actual modification to the XML seems to work - but the UI doesn't update - despite me explicitly refreshing XForms - I tried both the 'xf:refresh' element and the manual JS to do this - neither result in the UI being updated - this is in despite that fact that I can see changes have taken place on the model.
<?xml
version="1.0"
encoding="UTF-8"?>
<?xml-stylesheet
href="xsltforms-1.7/xsltforms.xsl"
type="text/xsl"?>
<?xsltforms-options
debug="no"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ht="http://www.w3.org/1999/xhtml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
lang="en">
<head>
<script type="text/javascript">
<![CDATA[
function js_xforms() {
console.log("js_xforms called");
var xmodel=document.querySelector('#model');
var person=xmodel.getInstanceDocument();
var fname=person.querySelector('fname');
console.log(fname.textContent);
fname.textContent='Fred';
fname.setAttribute("js", "value");
xmodel.rebuild();
xmodel.recalculate();
xmodel.revalidate();
xmodel.refresh();
}
]]>
</script>
<xf:model id="model">
<xf:instance id="person">
<person xmlns="">
<fname js="initial">Joe</fname>
<lname>Bloggs</lname>
<tel>1234-5678</tel>
</person>
</xf:instance>
</xf:model>
</head>
<body>
<xf:input ref="fname"/>
<xf:input ref="lname"/>
<xf:input ref="tel"/>
<xf:trigger>
<xf:label>JS</xf:label>
<xf:action ev:event="DOMActivate">
<xf:load resource="javascript:js_xforms()"/>
<xf:refresh model="model"/>
</xf:action>
</xf:trigger>
</body>
</html>
Neither the 'xf:refresh' nor the explicit JS call 'xmodel.refresh()' (etc) appear to work? What am I doing wrong here?
I am 99% sure this is just an issue with the UI not refreshing - since if I click the 'JS' button twice (or use fleur("instance('person')")
from the console) - I can see the modification to the Xf:model has happened.
Same result on both Chromium and Firefox.
It seems that XSLTForms has not implemented the refresh method, see here.
setvalue
statementYou can achieve the desired UI update if you follow the Javascript call with a (seemingly vacuous) setvalue
statement:
<xf:load resource="javascript:js_xforms()" />
<xf:setvalue ref="instance()/fname" value="." />
But this approach has the limitation that a setvalue
is needed for every value that the Javascript code may change, so another
<xf:setvalue ref="instance()/fname/@js" value="." />
is needed.
<xf:setvalue ref="instance()//*|instance()//@*" value="." />
works as well in XSLTForms, although it is against the XForms 1.1 specification, which says that setvalue
has a single node binding only.
addChange
methodYou can also program in Javascript directly what the XSLTForms implementation of setvalue
does internally. After changing textContent
and setAttribute
, you must call the XSLTForms-internal addChange
method:
fname.textContent='Fred';
fname.setAttribute("js", "value");
xmodel.xfElement.addChange(fname);
xmodel.rebuild();
The change of the attribute fname/@js
need not be added separately, xmodel.rebuild()
takes care of that. You could be even more coarse-grained and only add the entire instance as changed:
xmodel.xfElement.addChange(person);
xmodel.rebuild();
But this imposes additional unnecessary work on XSLTForms.
(Side remark: You can omit the
xmodel.recalculate();
xmodel.revalidate();
xmodel.refresh();
because they are already triggered by the xmodel.rebuild();
.)