Support #4732
closedUpgrading AtomGraph application from Saxon-CE to Saxon-JS 2
0%
Description
As explained on the mailing list, I am trying to replicate the behavior of Saxon-CE's updateHTMLDocument()
with Saxon-JS 2. This code is invoked from a JS onsubmit
handler in order to replace the content of the submitted form:
SaxonJS.transform({
"stylesheetLocation":
"https://localhost:4443/static/com/atomgraph/linkeddatahub/xsl/client.xsl.sef.json",
"initialMode":
"Q{https://w3id.org/atomgraph/linkeddatahub/domain#}ConstructViolation",
"logLevel": 10,
"sourceNode": html, // HTML document node from a jqXHR response
"templateParams": {
"submitted-form": form // the onsubmit target
},
"destination": "raw",
"nonInteractive": true
}, "async");
<xsl:template match="html" mode="apl:ConstructViolation">
<xsl:param name="submitted-form" as="element()"/>
<xsl:variable name="violation-form" as="element()">
...
</xsl:variable>
<xsl:result-document href="#{$submitted-form/@id}" method="ixsl:replace-content">
<xsl:copy-of select="$violation-form/*"/>
</xsl:result-document>
</xsl:template>
However with each invocation it looks like the ixsl:
event listeners are being registered again. I don't need this since this code only handles onsubmit
(since Saxon-JS cannot handle it natively), and there's another "main" SaxonJS.transform()
that already has registered the listeners. I thought I could switch this off using "nonInteractive": true
, but it does not seem to have the desired effect.
Updated by Michael Kay about 4 years ago
- Category set to Internals
- Assignee set to Debbie Lockett
- Priority changed from Low to Normal
Updated by Debbie Lockett about 4 years ago
Thanks for raising here, rather than just on the saxon-help list. There is obviously quite a lot going on in your application, so there is a lot to understand about what you are doing, and what you are trying to do. This makes it hard to pin down the separate issues.
Certainly you don't want events registered multiple times. Indeed "nonInteractive": true
is supposed to switch off registering the event listeners, so we'll need to investigate why that's not working. (Perhaps it is because you have multiple SaxonJS.transform()
calls?)
I'm not sure that the second call to SaxonJS.transform()
is really the right approach.
You say that onsubmit
is not handled by Saxon-JS, you may be right, but I'm not sure. Do you mean the actual form submission? In which case I assume your form submits with an HTTP POST? So then the problem is with capturing the response? Hmm, yes I'm not sure how you would do that...
You can use ixl:schedule-action/@http
for HTTP, but there you are directly specifying the request and the response handling; whereas I guess with an HTML form the request is internal, so managing the response becomes awkward...?
I may need to review more of your application (as well as refer back to previous work on Saxon-Forms, our partial Saxon-JS implementation of XForms, to remind myself of similar work) to help. The work of upgrading your Saxon-CE application to Saxon-JS 2 is clearly quite an undertaking, and needs time and effort. (Upgrading our Saxon documentation applications has similarly involved work and effort; but spread over a much longer period of development!) It sounds like with Saxon-CE you already had to do quite a bit of juggling IXSL and pure JavaScript. Hopefully there is now more you can do directly in IXSL with Saxon-JS 2, but working that out might not be immediate.
Updated by Martynas Jusevicius about 4 years ago
Hi Debbie. The project in question is this (the develop branch): https://github.com/AtomGraph/LinkedDataHub/ It's deployed here (but you would need to sign up): https://linkeddatahub.com:4443/
Let me try to explain the use case bit better.
The main SaxonJS.transform()
is in the HTML document <head>
. It registers the events that all works fine.
Now, when a form is POST
ed, it is validated on the server, and another version is rendered with validation errors. The idea is to take the new form with errors and replace the contents of the submitted form with it, so that from the user perspective the validation would look seamless.
One problem was that onsubmit
was not handled by Saxon-CE. My new test shows that Saxon-JS does indeed catch it: https://namedgraph.github.io/saxon-js2-test/
Source: https://github.com/namedgraph/saxon-js2-test/blob/gh-pages/client.xsl
I've added <xsl:sequence select="ixsl:call(ixsl:event(), 'preventDefault', [])"/>
to prevent the actual request. Is this the right approach?
Given that Saxon-JS can handle onsubmit
, I'll try a native solution and get rid of the JS altogether.
Updated by Martynas Jusevicius about 4 years ago
Ah, but how do I get POST
request body, i.e. the application/x-www-form-urlencoded
or multipart/form-data
encoded data of the submitted form? The relevant JS code looks like this:
if (this.enctype === "multipart/form-data")
settings =
{
"method": this.method,
"data": new FormData(this),
"processData": false,
"contentType": false,
"headers": { "Accept": "text/html" }
} ;
else settings =
{
"method": this.method,
"data": $(this).serialize(), // jQuery method
"contentType": "application/x-www-form-urlencoded", // RDF/POST
"headers": { "Accept": "text/html" }
} ;
$.ajax(this.action, settings)....
Maybe I could use FormData
somehow... Need to think about this.
Updated by Martynas Jusevicius about 4 years ago
Updated by Martynas Jusevicius about 4 years ago
I've made good progress implementing the same logic in XSLT, but got stuck on multipart requests.
I can successfully construct a FormData
instance from the form and use it as 'body'
in http-request
.
However, I cannot specify 'media-type': 'multipart/form-data'
because the browser adds boundary
to Content-Type
.
The solution seems to be to not set Content-Type
explicitly and let the browser do it. That worked in my JS code, but Saxon-JS complains if media-type
is not set:
code: "SXJS0006"
message: "No content type specified in HTTP request to: https://localhost:4443/files/?forClass=https%3A%2F%2Flocalho…mode=https%3A%2F%2Fw3id.org%2Fatomgraph%2Fclient%23ModalMode"
name: "XError"
I realize this is now a completely different issue than the original one :) Should I open another one or should we change the title?
Updated by Martynas Jusevicius about 4 years ago
I tried specifying an empty sequence (<xsl:map-entry key="'media-type'" select="()"/>
) but got the same error...
Updated by Debbie Lockett about 4 years ago
- Tracker changed from Bug to Support
- Subject changed from Events registered multiple times to Upgrading AtomGraph application from Saxon-CE to Saxon-JS 2
- Category deleted (
Internals) - Status changed from New to In Progress
Glad you've been making progress!
Ah sorry, this is a current limitation of the Saxon-JS HTTP interface: multipart HTTP requests are not currently implemented. I guess we haven't been aware of anyone needing them so far; but we did know that this should be provided one day...
Can you come up with another work around (perhaps the FormData be serialized, send as text, and then parsed again server side? Or is that too messy?); or is this a blocker?
Regarding the bug issue title etc. - I've made some changes. Let's treat this one as a "Support" issue where we can discuss anything relating to the work to upgrade your application from Saxon-CE to Saxon-JS 2. We can spin off specific bugs for issues you find, as and when they crop up (and try to keep them focussed!)
Updated by Martynas Jusevicius about 4 years ago
I noticed the multipart-
keys but I don't need to use them - FormData
works fine.
I'm not going to implement any specific server-side processing for this. If anything, I'll need to revert back to JS for multipart requests.
How about making media-type
optional in http-request
? Unsetting Content-Type
and letting XMLHttpRequest
handle it should solve this issue, please see here: https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post
Updated by Debbie Lockett about 4 years ago
Oh, sorry perhaps I've jumped to the wrong thing there. I was assuming that with 'media-type': 'multipart/form-data'
this meant that you'd be using multipart bodies. I haven't yet looked closely at what a FormData
object really looks like; and hadn't clicked when you said you'd use FormData
for the request body.
But yes, it looks like there is some bug here which means sending FormData
doesn't work... I'll investigate.
Updated by Debbie Lockett about 4 years ago
By the way, I've raised the following related issues [so far ;-)] :
Updated by Martynas Jusevicius about 4 years ago
Here's the relevant code: https://github.com/AtomGraph/LinkedDataHub/blob/develop/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl#L1121
I'm using either URLSearchParams
or FormData
as the request body
, depending on the form's enctype
.
For multipart/form-data
, the JS solution is to not set the Content-Type
header so that the browser can do it with the correct boundary
etc. That is what the StackOverflow answer explains.
However I cannot do that with <ixsl:schedule-action http-request="">
because media-type
is mandatory.
Updated by Martynas Jusevicius about 4 years ago
Any news? Multipart forms is one of the few remaining blockers for a new release of LinkedDataHub which uses Saxon-JS instead of Saxon-CE.
Updated by Debbie Lockett about 4 years ago
Sorry, other issues have occupied my time recently, so I have not looked much further.
Making media-type
optional rather than mandatory would involve changes to the Saxon-JS HTTP client, so this would only be available in a new release. And we don't know how soon such a release will be. Anyway, I'd need to look into it more to see whether that is what we should do. The design of the HTTP client was based on the EXPath HTTP module, where the media-type
of the request body is mandatory.
I actually assumed that you would revert back to using JS for the multipart forms for now, to work around this?
Updated by Martynas Jusevicius about 4 years ago
Well on a second thought I don't know if I have a fallback JS solution for this. Say I invoke fetch()
and pass FormData
in the case of a multipart request. How do I handle the callback? With Saxon-CE I did updateHTMLDocument()
but if I use SaxonJS.transform()
a second time, I'll get events registered twice.
Saxon-CE also allowed defining callbacks as XSLT templates. For example, I could call <xsl:sequence select="ac:fetch($select-uri, 'application/rdf+xml', 'onContainerQueryLoad')"/>
where <xsl:template match="ixsl:window()" mode="ixsl:onContainerQueryLoad">
would be the callback.
However my test shows that this pattern is not available in Saxon-JS. I tried the following:
<xsl:template match="button[@id = 'custom-handler']" mode="ixsl:onclick">
<xsl:message>CUSTOM HANDLER BUTTON</xsl:message>
<xsl:sequence select="ixsl:call(js:fetch('test.xml'), 'then', [ ixsl:get(ixsl:window(), 'oncustomHandler') ])"/>
</xsl:template>
<xsl:template match="." mode="ixsl:oncustomHandler">
<xsl:message>CUSTOM HANDLER CALLBACK</xsl:message>
</xsl:template>
but got Warning ixsl:get: object property 'oncustomHandler' not found
. Unless the test is wrong?
So I'm running out of ideas :/
As for media-type
, unsetting Content-Type
is a peculiar trick to get the browsers to set the correct multipart headers themselves. In theory it shouldn't work but it does... Because there doesn't seem to be another way to calculate the boundary
in code.
How about passing ()
as media-type
to unset the header?
Updated by Debbie Lockett about 4 years ago
Yes, I think there are some issues with your test. I'd propose something like the following:
<xsl:template match="button[@id = 'custom-handler']" mode="ixsl:onclick">
<xsl:message>CUSTOM HANDLER BUTTON</xsl:message>
<xsl:sequence select="js:customFetch('test.xml')"/>
</xsl:template>
<xsl:template match="." mode="ixsl:oncustomEvent">
<xsl:message>CUSTOM HANDLER CALLBACK</xsl:message>
</xsl:template>
Where customFetch
is a custom global JavaScript function, which invokes fetch()
as required, and its callback triggers the customEvent
event. (I would write this callback using then
directly in JavaScript, there is no benefit to doing it using IXSL, and in fact I think that just confuses things.)
The match="." mode="ixsl:oncustomEvent"
template will handle the customEvent
, and you can use ixsl:event()
to access customEvent information (e.g. the response details).
(FYI A couple of the issues with ixsl:call(js:fetch('test.xml'), 'then', [ ixsl:get(ixsl:window(), 'oncustomHandler') ])
:
-
it should be
ixsl:call(fetch('test.xml'), ...
-
This is trying to call a global function called
oncustomHandler
, but you want to be triggering acustomHandler
event to be handled by themode="ixsl:oncustomHandler"
template. So instead you should call a custom JS global function which triggers the event.)
Updated by Martynas Jusevicius about 4 years ago
Thanks! I'll give it a try.
"Trigger an event" -- do you mean using dispatchEvent()
here?
Updated by Debbie Lockett about 4 years ago
Yes, dispatchEvent()
. So something like:
const event = document.createEvent('Event');
event.initEvent('customEvent', true, true);
// no need to add event listeners here, that is done by IXSL
document.dispatchEvent(event);
(I'm referring to https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events.)
Updated by Martynas Jusevicius about 4 years ago
Thanks. I've implemented multipart form support using this approach, so I guess we can close this issue.
On the other hand, it pretty much duplicates the native <ixsl:schedule-action http-request="">
logic and keeps me from getting rid of the supporting JS code, so multipart support would be much welcome.
Updated by Debbie Lockett about 4 years ago
- Status changed from In Progress to Closed
Glad to hear you've got the upgrade to Saxon-JS 2 pretty much done now! And that the custom handling for sending FormData
with JavaScript is working. I'm now closing this support issue.
I've added more detail in issue #4735 about the comments and suggestions you've raised here about sending FormData
with ixl:schedule-action/@http-request
; so that's where that will get followed up.
Updated by Community Admin almost 4 years ago
- Applies to JS Branch 2 added
- Applies to JS Branch deleted (
2.0)
Please register to edit this issue
Also available in: Atom PDF Tracking page