I'm trying to implement a custom xbl component to submit the form to an external service, perform validation and handle the validation results. The version of orbeon is 4.4-CE deployed on JBoss 7.1.1.Final with MySQL persistence layer.
<xbl:binding element="fr|custom-submit" id="fr-custom-submit" xxbl:mode="lhha binding value">
<xbl:implementation>
<xf:model id="custom-submit-model">
<xf:instance id="validation-res">
<dummy/>
</xf:instance>
<!-- External validation submission -->
<xf:submission id="form-submission" ref="instance('fr-form-instance')"
action="http://localhost:8080/webapp/services/task/submitData" method="post"
replace="instance" instance="validation-res">
<xf:delete ev:event="xforms-submit" ref="//#v:*"/>
<xf:action ev:event="xforms-submit-done">
<!-- Insert external validation results when done -->
<xf:insert ref="." origin="instance('validation-res')/v:data/*"/>
<!-- Handle the valid/invalid result -->
</xf:action>
</xf:submission>
</xf:model>
</xbl:implementation>
<xbl:template>
<fr:button ref="xxf:binding('fr-custom-submit')">
<xf:label>
<xh:img src="/apps/fr/style/images/silk/disk.png"/>
<xh:span>Custom save</xh:span>
</xf:label>
<xf:send ev:event="DOMActivate" submission="form-submission"/>
</fr:button>
</xbl:template>
</xbl:binding>
Upon submitting the form the following exception occurs in the log files:
Empty single-node binding on xf:submission for submission id: form-submission |
I cannot figure out, what the exception means and if the cause of the problem is the strong encapsulation as described here.
Is it in general possible to write a custom xbl component for submitting a form? How can I overcome the above mentioned problem?
Regards
It is indeed an issue related to encapsulation, and using xxf:instance() allows you to break the encapsulation. So in your case, the submission would do:
ref="xxf:instance('fr-form-instance')"
Related
I am creating forms in Orbeon 2021.1.2 PE and I have a problem with handling error calls. I'm creating HTTP service and Action via form builder. I hope to work it out and be able to stay with Builder.
I call API witch works similarly to Twitter, so it returns Error HTTP Status Codes also for business errors (see doc https://developer.twitter.com/ja/docs/basics/response-codes and https://developer.twitter.com/en/docs/twitter-ads-api/response-codes).
For easiest example: if I try to find something a if its not in database, I get code 404 with a detailed error in response body.
I need to get a status code + full body (headers will be good too) to the form. I don't want to use modal windows (witch is default in Orbeon) to handle this call. It disturb users + calls often changes the entire workflow (what is visible, etc.). Some calls are even asynchronous.
What is the best solution to this problem in Orbeon? I tried a few things and find block in several places.
1] Solution through property oxf.fr.detail.process.action-service-error.*.*
Here is set default error modal window. I deleted it and found that I can get some values and write them in the form, for example:
xf:setvalue(ref="//control-1", value="event('response-status-code')")
Specifically, these are the values:
event('error-type')
event('response-status-code')
event('resource-uri')
event('response-headers') - get specific via event('response-headers')[lower-case(name) = 'content-lenght']/value
event('response-body') - This not working, it is still supported?
Because it is a global configuration, I start to working where to put these values. I tried to create my own instance but ended up with the fr:insert() function.
I wanted to create a new instance for each submission and then enter separate values (event ('response-status-code'), ...). However, process with fr:insert() does not work and the documentation has poor example (https://doc.orbeon.com/form-runner/advanced/buttons-and-processes/actions-xforms#xf-insert)
This do what i want, but in form:
<xf:insert context="xxf:instance('HTTP-ERROR-RESULT')" ref="responses" origin="xxf:instance('HTTP-ERROR-TEMPLATE')"/>
I tried to transform it to process, but with no luck:
xf:insert(into="xxf:instance('HTTP-ERROR-RESULT')/reponses", origin="xxf:instance('HTTP-ERROR-TEMPLATE')")
If this is the right way? How to fix my semantic error in xf:insert() and how do I get response-body (event ('response-body') doesn't work...)? And is some way to get name of submission or action in process (I need some id for find in instance).
2] Another way I tried is through xforms-submit-error in submit but it didn't work too.
Under the structure of what the builder creates:
<xf:action event="xforms-submit-done" ev:observer="echo-submission">
<xf:action class="fr-set-control-value-action">
<xf:var name="control-name" value="'control-2'"/>
<xf:var name="control-value" value="/*"/>
</xf:action>
</xf:action>
I tried to insert the something similar but with xforms-submit-error:
<xf:action event="xforms-submit-error" ev:observer="echo-submission">
<xf:action class="fr-set-control-value-action">
<xf:var name="control-name" value="'control-2'"/>
<xf:var name="control-value" value="/*"/>
</xf:action>
</xf:action>
I found that when using class="fr-service" in submission, I cant catch xforms-submit-error. So is the right solution to write the whole submission yourself outside the builder and work with xforms-submit-error?
Or is there an another elegant solution in Orbeon? Thanks for any reply!
Writing your own XForms will give you the most flexibility. You can put that XForms either directly in the form definition, using Edit Source in Form Builder, or in a custom model, which is a file on disk on the server, which I would recommend, see doc.
You seem to have found all the pieces (you did some good research there!), but here is a summary. If you have a service my-service, you can listen to xforms-submit-error, and store the value of event attributes in your own instance. If using a custom model, that file would have the following content, and you can also put the content of the <xf:model> below directly inside the <xf:model> of the form definition through Edit Source in Form Builder. Here the logic just saves the status code in an instance my-error-instance, and then uses an <xf:message> to show if for debugging.
<xf:model xmlns:xf="http://www.w3.org/2002/xforms">
<xf:instance id="my-error-instance">
<_>
<status-code/>
</_>
</xf:instance>
<xf:action observer="my-service-submission" event="xforms-submit-error">
<xf:setvalue
ref="instance('my-error-instance')/status-code"
value="event('response-status-code')"/>
<xf:message value="instance('my-error-instance')/status-code"/>
</xf:action>
</xf:model>
You'll also want to disable the default dialog on errors by setting the following property.
<property as="xs:string" name="oxf.fr.detail.process.action-service-error.*.*"/>
Finally, I put below the full source of a form definition that you can use to test this, with a my-service HTTP service, making a request to http://httpbin.org/status/404, and called for form load, which I used to test the above custom model logic.
<xh:html xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:exf="http://www.exforms.org/exf/1-0"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:sql="http://orbeon.org/oxf/xml/sql"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:fb="http://orbeon.org/oxf/xml/form-builder">
<xh:head>
<xh:title>Calling service returning a 404 on form load</xh:title>
<xf:model id="fr-form-model" xxf:expose-xpath-types="true" xxf:analysis.calculate="true">
<!-- Main instance -->
<xf:instance id="fr-form-instance" xxf:exclude-result-prefixes="#all" xxf:index="id">
<form>
<section-1>
<grid-1>
<control-1/>
</grid-1>
</section-1>
</form>
</xf:instance>
<!-- Bindings -->
<xf:bind id="fr-form-binds" ref="instance('fr-form-instance')">
<xf:bind id="section-1-bind" name="section-1" ref="section-1">
<xf:bind id="grid-1-bind" ref="grid-1" name="grid-1">
<xf:bind id="control-1-bind" name="control-1" ref="control-1" xxf:whitespace="trim"/>
</xf:bind>
</xf:bind>
</xf:bind>
<!-- Metadata -->
<xf:instance id="fr-form-metadata" xxf:readonly="true" xxf:exclude-result-prefixes="#all">
<metadata>
<application-name>a</application-name>
<form-name>a</form-name>
<title xml:lang="en">Calling service returning a 404 on form load</title>
<description xml:lang="en"/>
<created-with-version>2021.1-SNAPSHOT PE</created-with-version>
<library-versions>
<app>4</app>
</library-versions>
</metadata>
</xf:instance>
<!-- Attachments -->
<xf:instance id="fr-form-attachments" xxf:exclude-result-prefixes="#all">
<attachments/>
</xf:instance>
<!-- All form resources -->
<xf:instance xxf:readonly="true" id="fr-form-resources" xxf:exclude-result-prefixes="#all">
<resources>
<resource xml:lang="en">
<section-1>
<label>Untitled Section</label>
</section-1>
<control-1>
<label/>
<hint/>
<alert/>
</control-1>
</resource>
</resources>
</xf:instance>
<xf:instance id="my-service-instance" class="fr-service" xxf:exclude-result-prefixes="#all">
<body xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:fbf="java:org.orbeon.oxf.fb.FormBuilderXPathApi"><params/></body>
</xf:instance>
<xf:submission id="my-service-submission" class="fr-service"
resource="http://httpbin.org/status/404"
method="get"
serialization="none"
mediatype=""/>
<xf:action id="my-action-binding">
<xf:action event="fr-run-form-load-action-after-controls" ev:observer="fr-form-model"
if="true()">
<xf:send submission="my-service-submission"/>
</xf:action>
<xf:action event="xforms-submit" ev:observer="my-service-submission">
<xf:var name="request-instance-name" value="'my-service-instance'"/>
<xf:action/>
</xf:action>
<xf:action event="xforms-submit-done" ev:observer="my-service-submission"/>
</xf:action>
</xf:model>
</xh:head>
<xh:body>
<fr:view>
<fr:body xmlns:p="http://www.orbeon.com/oxf/pipeline" xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:oxf="http://www.orbeon.com/oxf/processors">
<fr:section id="section-1-section" bind="section-1-bind">
<xf:label ref="$form-resources/section-1/label"/>
<fr:grid id="grid-1-grid" bind="grid-1-bind">
<fr:c y="1" x="1" w="6">
<xf:input id="control-1-control" bind="control-1-bind">
<xf:label ref="$form-resources/control-1/label"/>
<xf:hint ref="$form-resources/control-1/hint"/>
<xf:alert ref="$fr-resources/detail/labels/alert"/>
</xf:input>
</fr:c>
<fr:c y="1" x="7" w="6"/>
</fr:grid>
</fr:section>
</fr:body>
</fr:view>
</xh:body>
</xh:html>
Based on answers from #avernet I make generic handler for xforms-submit-error. First, as was said above, I disable the default dialog by set this property.
<property as="xs:string" name="oxf.fr.detail.process.action-service-error.*.*"/>
Then I put my own custom model to WEB-INF/resources/forms/resources. It's whole look this:
<xf:model xmlns:xf="http://www.w3.org/2002/xforms" id="my-model">
<xf:instance id="my-error-instance">
<reponseList>
</reponseList>
</xf:instance>
<xf:instance id="my-error-template-instance">
<submission name="">
<status-code/>
<uri/>
<date/>
</submission>
</xf:instance>
<xf:action observer="fr-form-model" event="xforms-submit-error">
<xf:insert if="not(exists(xxf:instance('my-error-instance')//submission[#name=event('target')]))" context="xxf:instance('my-error-instance')" ref="reponseList" origin="xxf:instance('my-error-template-instance')"/>
<xf:setvalue if="not(exists(xxf:instance('my-error-instance')//submission[#name=event('target')]))" ref="xxf:instance('my-error-instance')//submission[1]/#name" value="event('target')"/>
<xf:setvalue ref="xxf:instance('my-error-instance')//submission[#name=event('target')]/status-code" value="event('response-status-code')"/>
<xf:setvalue ref="xxf:instance('my-error-instance')//submission[#name=event('target')]/uri" value="event('resource-uri')"/>
<xf:setvalue ref="instance('my-error-instance')//submission[#name=event('target')]/date" value="event('response-headers')[lower-case(name) = 'date']/value"/>
</xf:action>
</xf:model>
Took attention to two conditions (in insert and first setvalue). Because of them model save only last error of one submission. If you want for some reason history, you can delete them.
How can I disable a custom submit button after submit?
Below you can find my part of code to send:
<xf:trigger bind="booking-bind" id="booking">
<xf:label>
<xh:span>
<xf:output value="'Send'"/>
</xh:span>
</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue ref="instance('fr-form-instance')/submission_type">SendBooking</xf:setvalue>
<xf:setvalue ref="instance('fr-form-instance')/submission_extra_info">//booking[1]</xf:setvalue>
<xf:action type="xpath" xmlns:process="java:org.orbeon.oxf.fr.process.SimpleProcess">
process:runProcessByName('oxf.fr.detail.process', 'send')
</xf:action>
</xf:action>
</xf:trigger>
I am using this solution: Orbeon upgrade from 3.9 to 4.5 : Customized submit for saving forms
I see your trigger has a booking-bind bind. You could have a readonly MIP saying whether the button should be readonly. For example:
<bind id="booking-bind" readonly=". = 'sent'"/>
Then, in your action, do:
<xf:setvalue ref="xxf:bind('booking-bind')" value="'sent'"/>
This will cause the trigger to become readonly.
By the way we have an issue to tackle this better.
In my form file I have a submission load-data-submission which fetches some data from database, it is called on xforms-ready :
<xf:model>
...
<xf:action ev:event="xforms-ready" ev:observer="fr-form-model" if="true()">
<xf:send submission="load-data-submission"/>
</xf:action>
...
</xf:model>
Now, I have an XBL controll which is used in this very same form. There is another submission which also fetches data etc, let's call it rest-submission. Now, I would like the rest-submission (the one inside XBL) to be called right after my load-data-submission (the one inside form file) would fetch data.
How would I do that ? I've tried put inside XBL
<xf:action ev:observer="load-data-submission" ev:event="xforms-submit-done">
<xf:send submission="rest-submission"/>
</xf:action>
with no luck.
Thanks in advance.
To avoid id clashes and enable encapsulation, XBL defines a new lexical scope for ids and a new XPath context. So if from inside the XBL you refer to id load-data-submission, this refers to a load-data-submission defined within the XBL, which is most likely non-existent in your case. To reference ids outside of the XBL, you need to change the scope with the xxbl:scope="outer" attribute. The following example illustrates how to do that:
<xh:html xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:example="http://example.com/">
<xh:head>
<xf:model>
<xf:submission
id="get-states"
method="get"
resource="/xforms-sandbox/service/zip-states"
replace="instance"
instance="states"/>
<xf:instance id="states">
<empty/>
</xf:instance>
</xf:model>
<xbl:xbl>
<xbl:binding element="example|simple">
<xbl:implementation>
<xf:model id="simple-model">
<xf:instance>
<internal/>
</xf:instance>
</xf:model>
</xbl:implementation>
<xbl:template>
<xf:group>
<xf:message ev:observer="get-states"
ev:event="xforms-submit-done"
xxbl:scope="outer"
value="'Got event'"/>
</xf:group>
</xbl:template>
</xbl:binding>
</xbl:xbl>
</xh:head>
<xh:body>
<example:simple/>
<xf:trigger>
<xf:label>Get states</xf:label>
<xf:send submission="get-states" ev:event="DOMActivate"/>
</xf:trigger>
</xh:body>
</xh:html>
I would like to send submission after form has been saved in Form Builder, I was trying something like this at first (in my XBL file):
<xf:action ev:event="fr-data-save-done" ev:observer="fr-form-model">
<xf:message event="#all" level="modal">Saved</xf:message>
<xf:send submission="my-submission" ev:event="#all"/>
</xf:action>
The code above is placed in XBL file between xbl:template, outside xbl:model (though I tried to put it inside xbl:model with no luck).
Unfortunately it's not working, after I save my form in Form Builder message is not shown.
Anyone got idea why it's not working?
You could place by hand an event handler like this:
<foo:bar id="my-component-id" bind="my-bind">
<xf:dispatch
event="fr-data-save-done"
observer="fr-form-model"
name="my-custom-event"
targetid="my-component-id"/>
</foo:bar>
The handler doesn't have to be within the element:
<foo:bar id="my-component-id" bind="my-bind"/>
<xf:dispatch
event="fr-data-save-done"
observer="fr-form-model"
name="my-custom-event"
targetid="my-component-id"/>
And inside the XBL component:
<xbl:binding id="my-binding-id" element="foo:bar">
<xbl:handlers>
<xbl:handler event="my-custom-event" phase="target">
... XForms actions here ...
</xbl:handler>
</xbl:handlers>
...
</xbl:binding>
I have made a page that is suppose to be a common exit point (success page) for all Orbeon applications made both manually creating XFroms and by Orbeon Builder. The page is really simple as it is only supposed to show a generic message that is specified in a form that gets redirected to the page after submission. This feels like a trivial task, yet I have struggled a lot with it.
The documentation says:
The "POST" solution
If your XForms page responds to an HTTP POST containing XML, then it can access the content of the POST data with a special URL called input:instance:
<xforms:instance id="user-data" src="input:instance"/>
This results in the user-data instance being populated with the XML data posted to the XForms page. It's as easy as this!
NOTE: Nothing prevents you to combine this method with getting data from the request or a service.
My page-flow.xml
<config xmlns="http://www.orbeon.com/oxf/controller"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oxf="http://www.orbeon.com/oxf/processors">
<page id="main" path-info="/thankyou/" view="thankyou.xhtml"/>
<page id="test" path-info="/thankyou/test" view="test.xhtml">
<action when="/form/fromPage = 'test'">
<result page="main"/>
</action>
</page>
<epilogue url="oxf:/config/epilogue.xpl"/>
</config>
thankyou.xhtml
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xxforms="http://orbeon.org/oxf/xml/xforms">
<xhtml:head>
<xhtml:title>Thank you</xhtml:title>
<xforms:model xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:instance id="form-instance" src="input:instance"/>
<xforms:bind id="form-binds" nodeset="instance('form-instance')">
<xforms:bind id="success-bind" ref="/form/success/successPageText"/>
</xforms:bind>
</xforms:model>
</xhtml:head>
<xhtml:body>
<xhtml:p>
<xhtml:output ref="instance('form-instance')/form/success/successPageText"/>
</xhtml:p>
<xhtml:p>
<xhtml:output bind="success-bind"/>
</xhtml:p>
<xhtml:p>
<xhtml:a href="http://www.google.com">Away from here</xhtml:a>
</xhtml:p>
</xhtml:body>
</xhtml:html>
test.xhtml
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:xxforms="http://orbeon.org/oxf/xml/xforms">
<xhtml:head>
<xhtml:title>Thankyou page test</xhtml:title>
<xforms:model id="form-model">
<xforms:instance id="form-instance">
<form>
<input>text here</input>
<fromPage>test</fromPage>
<success>
<successPageText>
This is a test.
</successPageText>
</success>
</form>
</xforms:instance>
<xforms:bind id="form-binds" nodeset="instance('form-instance')">
<xforms:bind id="input-bind" ref="/form/input" type="xs:string" required="true()"/>
</xforms:bind>
<xforms:submission id="main-submission" method="post" action="/thankyou/test"
ref="instance('form-instance')" validate="false" replace="all"/>
</xforms:model>
</xhtml:head>
<xhtml:body>
<xhtml:table>
<xhtml:tr>
<xhtml:td>
<xforms:input bind="input-bind" id="input-control">
<xforms:label>Type something here </xforms:label>
</xforms:input>
</xhtml:td>
</xhtml:tr>
<xhtml:tr>
<xhtml:td>
<xforms:submit submission="main-submission" id="submit-control">
<xforms:label>Test</xforms:label>
<xforms:setvalue ev:event="DOMActivate">submit</xforms:setvalue>
</xforms:submit>
</xhtml:td>
</xhtml:tr>
</xhtml:table>
</xhtml:body>
</xhtml:html>
The thankyou page gets loaded when submitting from the test page, but the value set for the 'successPageText' is not shown.
I have tested with a Java servlet that when submitting from other pages/forms the xml data is included with the post, but I am unable to figure how to use it on the thankyou page.
The main reason your example doesn't work is that in test.xhtml you are posting to yourself. Instead, you should post to the target page. So write the submission as:
<xforms:submission id="main-submission" method="post"
action="/20110920-so-thankyou/"
ref="instance('form-instance')" validate="false" replace="all"/>
The pattern of posting to yourself, and then determining in page-flow.xml what the next page should be, is considered to be deprecated. It still works, there are still situations where it makes sense, but in the majority of the cases, the cost of the added complexity is higher than the benefits you get from this decoupling.
Finally, a couple of things very specific to your example:
In thankyou.xhtml, you were using <xhtml:output>, which you'll want to change to <xforms:output>.
The XPath expression in the first output should be instance('form-instance')/success/successPageText without form. Remember: instance() returns the root element, not the document element.