javascriptxpathxformsxsltforms

XForms Refresh not working? (XSLTForms 1.7)


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.


Solution

  • It seems that XSLTForms has not implemented the refresh method, see here.

    Vacuous setvalue statement

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

    Internal Javascript addChange method

    You 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();.)