Open thealexgraham opened 6 years ago
Maybe you could do something like this:
haskellValidateString("this String may be valid", function (x) {
// Do something with x...
});
The callback function would be passed to Haskell as a JSObject
parameter, and could then be called from Haskell:
haskellValidateString :: Window -> String -> JSObject -> IO ()
haskellValidateString = runUI $ \s callback -> runFunction $ ffi "callback(%1)" $ validate s
-- Then, when you have access to a Window object...
createHaskellFunction "haskellValidateString" $ haskellValidateString window
Hey, thanks for the quick response. I tried that (with the Window
moved to follow the runUI
signature) like so:
haskellValidateString :: Window -> String -> JS.JSObject -> IO ()
haskellValidateString w = runUI w $ \s callback -> runFunction $ ffi "callback(%1)" $ validate s
and got this:
src/View.hs:90:27: error:
• Couldn't match expected type ‘String -> JS.JSObject -> IO ()’
with actual type ‘IO a0’
• In the expression:
runUI w
$ \ s callback -> runFunction $ ffi "callback(%1)" $ validate s
In an equation for ‘haskellValidateString’:
haskellValidateString w
= runUI w
$ \ s callback -> runFunction $ ffi "callback(%1)" $ validate s
src/View.hs:90:37: error:
• Couldn't match expected type ‘UI a0’
with actual type ‘String -> t0 -> UI ()’
• The lambda expression ‘\ s callback
-> runFunction $ ffi "callback(%1)" $ validate s’
has two arguments,
but its type ‘UI a0’ has none
In the second argument of ‘($)’, namely
‘\ s callback -> runFunction $ ffi "callback(%1)" $ validate s’
In the expression:
runUI w
$ \ s callback -> runFunction $ ffi "callback(%1)" $ validate s
I also tried this:
haskellValidateString :: Window -> String -> JS.JSObject -> IO ()
haskellValidateString w s callback = runUI w $ runFunction $ ffi "callback(%1)" $ validate s
which did typecheck and register, but got haskell.js:267 Uncaught ReferenceError: callback is not defined
when the browser tries to run the function.
I think I'm misunderstanding how the JSObject
callback works.
Any further insight would be greatly appreciated.
Oops - maybe I should have tested that function before I posted it...
The correct function is:
haskellValidateString :: Window -> String -> JS.JSObject -> IO ()
haskellValidateString w s callback = runUI w $ runFunction $ ffi "%1(%2)" callback $ validate s
I did actually test this one and it worked: I successfully managed to send a result to JS using this function.
It would be nice to have a less hacky way to do this though: the nicest way to do it would probably be to add an instance ToJS a => IsHandler (IO a)
.
That worked great, thanks.
Unfortunately since the callback is an asynchronous call it can't do anything outside of its scope. Still useful, though. I started experimenting with passing in a variable as a JSObject
which I planned to change/manipulate through the FFI, but it got pretty deep into the FFI's pointer system which I do not yet understand.
At some point when I have some free time I plan to dig a bit deeper into this, I'd love to be able to contribute to the project!
Thanks again, Alex
At the moment, asynchronous calls are the only way to call Haskell from JavaScript. The reason is that I don't know how to handle nested chains like
Haskell → JavaScript → Haskell → JavaScript → …
For this to be possible, the JavaScript runtime would have to be multi-threaded (because a synchronous function has to freeze the program flow until the result is available, but a nested chain requires another program flow to be run.) Of course, that opens another can of worms.
By the way, I have written up some details about the design of the JavaScript FFI.
At the moment, asynchronous calls are the only way to call Haskell from JavaScript. The reason is that I don't know how to handle nested chains like
Haskell → JavaScript → Haskell → JavaScript → …
For this to be possible, the JavaScript runtime would have to be multi-threaded (because a synchronous function has to freeze the program flow until the result is available, but a nested chain requires another program flow to be run.) Of course, that opens another can of worms.
I'm not a JavaScript expert by any stretch, but promises look like they could work. Something like the following API could be used:
newtype Promise = Promise { getPromise :: JSObject } -- Constructor and accessor are kept private; this newtype is for type-safety
toPromise :: ToJS a => a -> IO Promise
instance IsHandler (IO Promise) -- This returns the value as a JavaScript promise
--- then, in the program:
haskellValidateString :: String -> IO Promise
haskellValidateString s = toPromise $ validate s
obj <- exportHandler window haskellValidateString
runFunction $ ffi "window.validate = %1" obj
// In the JavaScript program:
validate("test string").then(function(isValid) {
console.log(isValid);
});
Potentially a type argument could be added to Promise
(so toPromise :: ToJS a => a -> IO (Promise a)
), but I don't see any value in doing this.
Offshoot of https://github.com/HeinrichApfelmus/threepenny-gui/issues/182.
I've been able to create a Haskell function that my JavaScript code can call and send variables back to Haskell:
Then in my JavaScript code I can do this:
and it prints in the REPL.
What I need to do is be able to pass back something from Haskell, so I can then use it in the JavaScript code:
The reason for this is to use Haskell functions to validate text in a prebuilt JavaScript json editing widget (https://github.com/jdorn/json-editor) which do validation inside a JS lambda.
Any insight on this? I've been banging my head on it for a bit...
Thanks!