Hi, I'm trying to get started with cljfx, tried both with lein and cli but I'm unable to load cljfx.api for some reason
(require '[cljfx.api :as fx])
Execution error (FileNotFoundException) at user/eval14091 (REPL:1).
Could not locate cljfx/api__init.class, cljfx/api.clj or cljfx/api.cljc on classpath.
These are the deps I tried with
{:deps
{org.clojure/clojure {:mvn/version "1.10.1"}}
{commons-io/commons-io {:mvn/version "2.8.0"}}
{cljfx {:git/url "<https://github.com/cljfx/cljfx>" :sha "LATEST"}}
:paths ["src"]}
Using java 11 on arm64
Weirdly enough, I am able to find and load other namespaces when using lein:
(require '[cljfx.fx :as fx])
works just fine
Please advise?:sha "LATEST"
?
I also tried with a real sha
{cljfx {:git/url "<https://github.com/cljfx/cljfx>" :sha "955514a7968677d674ecc79126af428a9f21c91c"}}
there is something wrong with your deps.edn
oh
{commons-io/commons-io {:mvn/version "2.8.0"}}
is a key
I see it!
damn
still had the issue with lein, though
with these deps:
:dependencies [[org.clojure/clojure "1.10.1"]
[commons-io/commons-io "2.8.0"]
[cljfx "1.7.10"]]
is error the same?
(require '[cljfx.api :as fx])
Syntax error compiling at (*cider-repl Projects/clj-dedupe-fs:localhost:36281(clj)*:1:21).
namespace 'cljfx.api' not found
yeahwhat’s in *e
?
#error {
:cause "namespace 'cljfx.api' not found"
:via
[{:type clojure.lang.Compiler$CompilerException
:message "Syntax error compiling at (*cider-repl Projects/clj-dedupe-fs:localhost:36281(clj)*:1:21)."
:data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 21, :source "*cider-repl Projects/clj-dedupe-fs:localhost:36281(clj)*"}
:at [clojure.core$throw_if invokeStatic "core.clj" 5867]}
{:type java.lang.Exception
:message "namespace 'cljfx.api' not found"
:at [clojure.core$apply invokeStatic "core.clj" 667]}]
is this all? no stacktrace?
there is a trace, was wondering if you wanted it
[[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$load_libs invokeStatic "core.clj" 5985]
[clojure.core$load_libs doInvoke "core.clj" 5969]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$require invokeStatic "core.clj" 6007]
[clojure.core$require doInvoke "core.clj" 6007]
[clojure.lang.RestFn invoke "RestFn.java" 408]
[clj_dedupe_fs.core$eval15884 invokeStatic "form-init3277701471284635884.clj" 1]
[clj_dedupe_fs.core$eval15884 invoke "form-init3277701471284635884.clj" 1]
[clojure.lang.Compiler eval "Compiler.java" 7177]
[clojure.lang.Compiler eval "Compiler.java" 7132]
[clojure.core$eval invokeStatic "core.clj" 3214]
[clojure.core$eval invoke "core.clj" 3210]
[nrepl.middleware.interruptible_eval$evaluate$fn__2542$fn__2543 invoke "interruptible_eval.clj" 87]
[clojure.lang.AFn applyToHelper "AFn.java" 152]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1973]
[clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1973]
[clojure.lang.RestFn invoke "RestFn.java" 425]
[nrepl.middleware.interruptible_eval$evaluate$fn__2542 invoke "interruptible_eval.clj" 87]
[clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
[clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
[clojure.main$repl$fn__9095 invoke "main.clj" 458]
[clojure.main$repl invokeStatic "main.clj" 458]
[clojure.main$repl doInvoke "main.clj" 368]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$apply invoke "core.clj" 660]
[refactor_nrepl.ns.slam.hound.regrow$wrap_clojure_repl$fn__11776 doInvoke "regrow.clj" 20]
[clojure.lang.RestFn invoke "RestFn.java" 1523]
[nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 84]
[nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 56]
[nrepl.middleware.interruptible_eval$interruptible_eval$fn__2573$fn__2577 invoke "interruptible_eval.clj" 152]
[clojure.lang.AFn run "AFn.java" 22]
[nrepl.middleware.session$session_exec$main_loop__2640$fn__2644 invoke "session.clj" 202]
[nrepl.middleware.session$session_exec$main_loop__2640 invoke "session.clj" 201]
[clojure.lang.AFn run "AFn.java" 22]
[java.lang.Thread run "Thread.java" 834]]}
great question 😄
by default JavaFX pulls native libraries for the same platform the build tool is running on and skips others, so uberjar will work only on the platform the uberjar is built on
if you want to create cross-platform uberjar, you’ll need to specify all classifiers explicitly, e.g. like it’s done here https://github.com/defold/defold/blob/editor-dev/editor/project.clj#L81-L108
(note: for cljfx project you won’t need javafx-fxml
and javafx-swing
)
if you want to create cross-platform application, I’d recommend looking at https://github.com/cljfx/hn
it’s not too hard to build an installable app
Thanks again 🙂 I find the documentation a bit obtuse so I have resorted to reading the README very slowly and carefully and working through the examples. Any recommendations on getting started or a development guide? I'm not familiar with JavaFX or UI development at all, and I think what I'm missing the most (maybe?) is discoverability. How do I know tables should have cells? How can I know what attributes are there? That kind of stuff.
I thought about making some sort of a repl helper that answers these questions, but time/priorities…
what I do is:
- use javafx javadoc to see what options are available https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/Node.html in JavaFX
- open cljfx.fx.*
files to get a reminder of what are props
alright, better than nothing, I'll give it a shot
for example, if I want to configure a table, I open cljfx/fx/table_view.clj
to see its props and https://openjfx.io/javadoc/12/javafx.controls/javafx/scene/control/TableView.html to see its docs
What do you think about qualifying each view's props with a ns? for example a table would map to fx.table/prop-name
?
I thought about it at the start, but decided not to do it because you’ll have to deal with inheritance, e.g. some table props are coming from its superclass, so the use will be something like {:fx/type :fx.type/table :fx.table/items [] :fx.control/tooltip {...} :fx.node/style {...}}
maybe that was a mistake to not do it, what do you think? I didn’t like the idea of having to keep this class hierarchy in the code, but perhaps it’s “essential”?
It looks like in any case I'm forced to know and understand JavaFX and consult its documentation, so perhaps reflecting the class taxonomy might not be too harmful. It would also direct the user to the appropriate class they want to configure when referring to the documentation instead of jumping around.
Another option / complement to this idea is using specs to describe the different views.
Even adding a simple utility function which can be called on the :fx/type
for user convenience will go a very long way, something like (api/props-for :table)
which will give a list of keys or a map of key-type.
https://github.com/cljfx/exploration-template/blob/master/src/user.clj
I experimented a bit with it
If I were to do it properly, I’d do it with spec, but time/priorities…
> It looks like in any case I’m forced to know and understand JavaFX that’s correct, there is no way around that, just like with react where you still have to know DOM. react/cljfx is an abstraction layer with better semantics
That makes me lean more towards preferring namespace qualified keywords
regarding spec, you can always open an issue and label it PRs welcome, maybe share it on twitter/clojurians
Hmm, I assuming you call (require 'cljfx.api)
in clj-dedupe-fs.core ns?
(ns clj-dedupe-fs.core
(:require
[cljfx.api :as fx])
(:import
[javafx.stage FileChooser]
[javafx.event ActionEvent]
[javafx.scene Node]
(<http://java.io|java.io> File)
(<http://org.apache.commons.io|org.apache.commons.io> FileUtils)))
If you can require other nses such as cljfx.fx
, it probably means there is some exception during the start of JavaFX runtime
I was hoping you could see it somehow
Maybe I can see it if I clone cljfx and try there
can you try (import 'javafx.application.Platform)
?
and then (Platform/startup #(Platform/setImplicitExit false))
is there an exception at some point?
let's see
Import succeeded, startup failed with exception
Execution error (IllegalStateException) at com.sun.javafx.application.PlatformImpl/startup (PlatformImpl.java:182).
Toolkit already initialized
hmm
that looks okay
okay, cloned cljfx and tried just evaluating the api file
that means JavaFX works and you should be able to require cljfx.api
and voila
Loading library prism_es2 from resource failed: java.lang.UnsatisfiedLinkError: /home/bsless/.openjfx/cache/14/libprism_es2.so: /home/bsless/.openjfx/cache/14/libprism_es2.so: cannot open shared object file: No such file or directory (Possible cause: can't load AMD 64-bit .so on a AARCH64-bit platform)
java.lang.UnsatisfiedLinkError: /home/bsless/.openjfx/cache/14/libprism_es2.so: /home/bsless/.openjfx/cache/14/libprism_es2.so: cannot open shared object file: No such file or directory (Possible cause: can't load AMD 64-bit .so on a AARCH64-bit platform)
It's because I'm on arm?
I would say yes, there are javafx native libs for various platforms, and I don’t remember seeing arm among them…
Well then, it's 1-0 for java vs. one janky arm laptop 🙂
I just googled it and there seems to be something for arm
you are on your own there I’m afraid :)
It's okay, I'll work on it on another machine, thank you for helping 🙂
Also have a preemptive question: uberjars built with javafx work cross platform on windows, linux and mac or do I need to make sure some extra deps are included or have specific builds?