Cet article est en cours de rédaction, son contenu peut évoluer sans préavis et les informations qu'il contient peuvent manquer de précisions.
Un des projets sur lesquels on travaille est un client lourd basé sur JavaFX. Pour ce, nous utilisons comme langage principal Clojure et l'excellente librairie cljfx. En cours de route, nous avons vite réalisé l'attrait que constituent les WebViews de JavaFX, nous donnant accès à tout l'écosystème Web.
Dans cet article, nous allons proposer une manière de brancher des WebViews écrites en ClojureScript sur une application cljfx. L'objectif étant de profiter du merveilleux workflow de dev apporté par ClojureScript + shadow-cljs au sein d'une application cljfx.
Vous trouverez le projet correspondant à cet article ici :

cljfx
Tout d'abord, voyons à quoi peut ressembler une application cljfx minimale.
deps.edn
(déclaration des dépendances)
src/article/ex_01.clj
(exemple de code)

Pour plus de détails, je vous invite à regarder directement le repo github de cljfx. Ce dernier regorge d'exemples permettant de rapidement se faire une idée de ce qu'il est possible d'en tirer, et ce même sans expérience antérieure avec JavaFX.
Pour l'heure nous allons essayer d'afficher une simple vue Web.
src/article/ex_02.clj

ClojureScript
Nous allons maintenant tenter d'utiliser ClojureScript pour définir une vue Web.
Tout d'abord, créons un namespace ClojureScript contenant un composant minimal.
src/article/ex_03/vue.cljs
Ensuite, utilisons shadow-cljs pour compiler notre composant et enveloppons le code JavaScript résultant dans une page HTML avec l'aide de hiccup. Enfin, affichons la page en question à l'aide d'une vue Web.
src/article/ex_03.clj

Hot reloading?
Une des motivations premières de cette expérience était d'avoir accès aux commodités du développement interactif qu'offrent ClojureScript avec shadow-cljs.
Setup minimal shadow-cljs
shadow-cljs.edn
src/app/main.cljs
target/index.html
Avec ces trois fichiers, vous pouvez exécuter la commande suivante depuis la racine du projet :
shadow-cljs watch app
Puis ouvrir http://localhost:8080
.
Si maintenant vous éditez main.cljs
, il y a recompilation et rafraichissement en hot reloading de votre code.
Intégration cljfx
Pour embarquer ce mécanisme au sein d'une application cljfx, c'est simple : il suffit de passer cette URL (http://localhost:8080
) à une Web view comme ci-dessous :
Cet exemple est quasiment identique à l'exemple 2, sauf que l'on passe une URL au lieu d'une string HTML.
Si vous rééditez le code de main.cljs
, la mécanique de recompilation/hot-reloading est bien propagée à l'intérieur de notre WebView.
Communication
Nous allons maintenant tenter de mettre en place un moyen de communication entre l'application et notre WebView. De chaque côté, nous devrons définir un handler que le côté opposé pourra appeler.
Des informations nécessaires à ce branchement se trouvent dans la documentation du WebEngine. Pour avoir accès au WebEngine de notre WebView nous utiliserons le mécanisme d'extension de cljfx.
Pour pouvoir envoyer des messages de la WebView vers notre application, nous allons attacher un object global au DOM de notre WebView. La documentation du WebEngine donne cet exemple :
Dans le cas présent, l'object que nous passons à window.setMember
devra posséder une méthode send
que le client pourra utiliser pour communiquer avec l'application. En Clojure, on peut faire ça de cette manière :
Depuis cljfx, nous pourrons avoir accès au WebEngine via une extension cljfx, en nous basant sur l'extension de base de la librairie.
Cette extension pourra être utilisée ainsi :

Lorsque l'on clique sur le bouton "anyone here ?"
le message Client sent: anyone here ?
est affiché côté application.
Pour que l'application puisse envoyer des messages à la WebView, il va nous falloir définir un handler coté WebView et être en mesure de l'appeler côté application.
Pour la définition, nous modifions simplement notre html-content
en lui ajoutant un script déclarant une variable webView
contenant un objet disposant d'une méthode send
. Dans cet exemple, cette méthode remplacera le contenu de la div #app
par le message qu'elle reçoit.
Pour évaluer du code JavaScript dans le contexte de notre WebView depuis l'application, nous utiliserons la méthode executeScript
du WebEngine de notre WebView. Il va donc nous falloir capturer une référence vers ce WebEngine.
Dans cljfx, chaque composant est représenté à l'aide d'une abstraction portant le nom de Lifecycle
et dont voici la définition :
Il serait donc possible d'envelopper le lifecycle représentant notre WebView, de manière à capturer le WebEngine lorsque la méthode create
est appelée.
Pour ça on peut introduire la fonction wrap-instance
:
Cette fonction pourra être appelée avec notre extension (qui est vous l'aurez compris une instance de Lifecycle)
À la suite de quoi, nous pouvons définir une méthode send!
qui utilisera cette référence pour envoyer un message à la WebView.
Voici le code complet :
Avec ClojureScript
Dans l'exemple précédent, nous avons écarté ClojureScript de l'équation pour ne pas compliquer les choses outre mesure. Nous allons le rebrancher maintenant.
Nous en profiterons pour extraire l'implémentation de la communication application/WebView vers un namespace dédié, exposant un moyen de créer des WebView et de communiquer avec elles.
Ce namespace pourra être utilisé comme une librarie exposant deux fonctions :
- web-view
- send!
Nous allons donc maintenant l'utiliser sur notre exemple précédent (celui utilisant ClojureScript).
shadow_cljfx/example/view.cljs
shadow_cljfx/example/core.clj
Et voilà !
Quelques commodités supplémentaires
Dans notre précédent exemple, nous avons fait fi du hot-reloading et laissé à la charge du développeur pas mal de boilerplate.
shadow-cljfx expose le namespace shadow-cljfx.repl
pour vous aider avec tout ça.
Ce namespace vous permet d'initialiser de nouvelles WebViews facilement. Dans votre projet, depuis un REPL, vous pouvez essayer ceci :
Cela créera pour vous les fichiers nécessaires à l'utilisation et au développement d'une nouvelle WebView. Vous pouvez maintenant ouvrir le fichier src/shadow_cljfx/example2/core.clj
fraîchement créé et charger ce namespace dans votre REPL. À la fin du fichier, vous trouverez quelques commandes intéressantes.
(repl/dev! ID)
lance le mode dev pour votre WebView. Concrètement cela démarre un serveur shadow-cljs, un watcher et tout le tintouin. Après cela, vous serez en mesure d'évaluer du code ClojureScript dans le contexte de votre WebView via la macro e>
comme le fait justement la ligne suivante : (e> (js/console.log "hey"))
.
Ce moyen de communication avec la WebView est réservé à la phase de développement et repose sur shadow-cljs.
Après cela, nous avons un exemple d'envoi de message via la fonction send!
, dont nous avons parlé précédemment. Si vous exécutez cette forme, le message d'accueil sera remplacé par "hello". Pour comprendre pourquoi, vous pouvez maintenant jeter un œil au fichier src/shadow_cljfx/example2/view.cljs
. Cela devrait vous rappeler le code des exemples discutés plus haut.
Enfin, la forme (repl/compile-inline-index ID))
vous permet de compiler le HTML définitif de cette WebView vers target/shadowcljfx/example2/cljfx.html
. À la suite de quoi notre WebView pourra être utilisée sans shadow-cljs.
Le namespace shadow-cljfx.repl
expose également d'autre fonctionnalités dont voici un aperçu :
Conclusion
Dans cet article, nous avons vu comment utiliser des WebViews développées avec ClojureScript au sein d'une application cljfx (JavaFX). Nous avons ensuite vu comment établir une communication entre ces vues et notre application et comment exploiter les commodités de développement offertes par shadow-cljs.
Dans l'article suivant nous explorerons une méthode de communication alternative utilisant les WebSockets.