Calling Idris from Javascript and vice-versa

Posted on May 31, 2013 by raichoo

Until now the JavaScript backend for Idris has been a nice little gimmick to play around with, but when it comes to writing real applications it’s pretty limited. The limitations originates from the way how the FFI interacts with JavaScript. Until now it was not possible to call Idris code from within JavaScript which is essential when you want to register callbacks. In this blogpost I present my current work on the Idris FFI and JavaScript backend which enables this functionality. Let’s write a very simple piece of code that just changes a text label when it gets clicked.

To do this we will need to define our onclick function. It’ll look like this:

setOnClick : HTMLElement -> (() -> IO ()) -> IO ()
setOnClick (Elem p) f =
  mkForeign (FFun ".onclick=" [FPtr, FFunction FUnit (FAny (IO ()))] FUnit) p f

Let’s break down what happens here. We are defining a function that takes an HTMLElement and a function that performs a side effect that should get executed whenever we click on the element. I’m using the FPtr type to represent a element. The name of the function tells the FFI that “onclick” is a field of an element. Whenever a name starts with a dot the FFI concludes that the first argument has to be an object and the name of the function is one of its fields. When the name ends with an equals sign it concludes that we are dealing with an assignment. In my previous blogposts I’m explaning how these mechanisms are used.

The second argument of our onclick function is of type “FFunction” takes an FUnit and returns a FAny (IO ()). mkForeign translates this into a function of type () -> IO (). For more information about the Idris FFI take a look at the tutorial.

Lets take a look at the FFI in action:

module Main

data HTMLElement : Type where
  Elem : Ptr -> HTMLElement

data NodeList : Type where
  Nodes : Ptr -> NodeList

query : String -> IO NodeList
query q = do
  e <- mkForeign (FFun "document.querySelectorAll" [FString] FPtr) q
  return (Nodes e)

item : NodeList -> Int -> IO HTMLElement
item (Nodes p) i = do
  i <- mkForeign (FFun ".item" [FPtr, FInt] FPtr) p i
  return (Elem i)

setOnClick : HTMLElement -> (() -> IO()) -> IO ()
setOnClick (Elem p) f =
  mkForeign (FFun "['onclick']=" [FPtr, FFunction FUnit (FAny (IO ()))] FUnit) p f

setText : HTMLElement -> String -> IO ()
setText (Elem p) s =
  mkForeign (FFun ".textContent=" [FPtr, FString] FUnit) p s

test : HTMLElement -> () -> IO ()
test e i = do
  setText e "SUPERFOO"
  putStrLn "testfoo"

main : IO ()
main = do
  e <- query "#test"
  i <- item e 0
  setOnClick i (test i)

Have fun with calling functions back and forth :3

comments powered by Disqus